diff options
Diffstat (limited to 'mojo/public/tools/bindings')
102 files changed, 14552 insertions, 0 deletions
diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn new file mode 100644 index 0000000000..153d110332 --- /dev/null +++ b/mojo/public/tools/bindings/BUILD.gn @@ -0,0 +1,77 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//mojo/public/tools/bindings/mojom.gni") + +action("precompile_templates") { + sources = mojom_generator_sources + sources += [ + "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl", + "$mojom_generator_root/generators/cpp_templates/module.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_data_view_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_data_view_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_traits_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_traits_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/validation_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_template_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_template_definition.tmpl", + "$mojom_generator_root/generators/java_templates/constant_definition.tmpl", + "$mojom_generator_root/generators/java_templates/constants.java.tmpl", + "$mojom_generator_root/generators/java_templates/data_types_definition.tmpl", + "$mojom_generator_root/generators/java_templates/enum.java.tmpl", + "$mojom_generator_root/generators/java_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/java_templates/header.java.tmpl", + "$mojom_generator_root/generators/java_templates/interface.java.tmpl", + "$mojom_generator_root/generators/java_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/java_templates/interface_internal.java.tmpl", + "$mojom_generator_root/generators/java_templates/struct.java.tmpl", + "$mojom_generator_root/generators/java_templates/union.java.tmpl", + "$mojom_generator_root/generators/js_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/js_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/js_templates/module.amd.tmpl", + "$mojom_generator_root/generators/js_templates/module_definition.tmpl", + "$mojom_generator_root/generators/js_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/js_templates/union_definition.tmpl", + "$mojom_generator_root/generators/js_templates/validation_macros.tmpl", + ] + script = mojom_generator_script + outputs = [ + "$target_gen_dir/cpp_templates.zip", + "$target_gen_dir/java_templates.zip", + "$target_gen_dir/js_templates.zip", + ] + args = [ + "--use_bundled_pylibs", + "precompile", + "-o", + rebase_path(target_gen_dir), + ] +} diff --git a/mojo/public/tools/bindings/README.md b/mojo/public/tools/bindings/README.md new file mode 100644 index 0000000000..737c7e61df --- /dev/null +++ b/mojo/public/tools/bindings/README.md @@ -0,0 +1,749 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojom IDL and Bindings Generator +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +Mojom is the IDL for Mojo bindings interfaces. Given a `.mojom` file, the +[bindings generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings) +outputs bindings for all supported languages: **C++**, **JavaScript**, and +**Java**. + +For a trivial example consider the following hypothetical Mojom file we write to +`//services/widget/public/interfaces/frobinator.mojom`: + +``` +module widget.mojom; + +interface Frobinator { + Frobinate(); +}; +``` + +This defines a single [interface](#Interfaces) named `Frobinator` in a +[module](#Modules) named `widget.mojom` (and thus fully qualified in Mojom as +`widget.mojom.Frobinator`.) Note that many interfaces and/or other types of +definitions may be included in a single Mojom file. + +If we add a corresponding GN target to +`//services/widget/public/interfaces/BUILD.gn`: + +``` +import("mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "frobinator.mojom", + ] +} +``` + +and then build this target: + +``` +ninja -C out/r services/widget/public/interfaces +``` + +we'll find several generated sources in our output directory: + +``` +out/r/gen/services/widget/public/interfaces/frobinator.mojom.cc +out/r/gen/services/widget/public/interfaces/frobinator.mojom.h +out/r/gen/services/widget/public/interfaces/frobinator.mojom.js +out/r/gen/services/widget/public/interfaces/frobinator.mojom.srcjar +... +``` + +Each of these generated source modules includes a set of definitions +representing the Mojom contents within the target language. For more details +regarding the generated outputs please see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +## Mojom Syntax + +Mojom IDL allows developers to define **structs**, **unions**, **interfaces**, +**constants**, and **enums**, all within the context of a **module**. These +definitions are used to generate code in the supported target languages at build +time. + +Mojom files may **import** other Mojom files in order to reference their +definitions. + +### Primitive Types +Mojom supports a few basic data types which may be composed into structs or used +for message parameters. + +| Type | Description +|-------------------------------|-------------------------------------------------------| +| `bool` | Boolean type (`true` or `false`.) +| `int8`, `uint8` | Signed or unsigned 8-bit integer. +| `int16`, `uint16` | Signed or unsigned 16-bit integer. +| `int32`, `uint32` | Signed or unsigned 32-bit integer. +| `int64`, `uint64` | Signed or unsigned 64-bit integer. +| `float`, `double` | 32- or 64-bit floating point number. +| `string` | UTF-8 encoded string. +| `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`. +| `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant. +| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type. +| `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle. +| `handle<message_pipe>` | Generic message pipe handle. +| `handle<shared_buffer>` | Shared buffer handle. +| `handle<data_pipe_producer>` | Data pipe producer handle. +| `handle<data_pipe_consumer>` | Data pipe consumer handle. +| *`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. +| *`InterfaceType&`* | An interface request 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. +| *`associated InterfaceType`* | An associated interface handle. See [Associated Interfaces](#Associated-Interfaces) +| *`associated InterfaceType&`* | An associated interface request. See [Associated Interfaces](#Associated-Interfaces) +| *T*? | An optional (nullable) value. Primitive numeric types (integers, floats, booleans, and enums) are not nullable. All other types are nullable. + +### Modules + +Every Mojom file may optionally specify a single **module** to which it belongs. + +This is used strictly for aggregaging all defined symbols therein within a +common Mojom namespace. The specific impact this has on generated binidngs code +varies for each target language. For example, if the following Mojom is used to +generate bindings: + +``` +module business.stuff; + +interface MoneyGenerator { + GenerateMoney(); +}; +``` + +Generated C++ bindings will define a class interface `MoneyGenerator` in the +`business::stuff` namespace, while Java bindings will define an interface +`MoneyGenerator` in the `org.chromium.business.stuff` package. JavaScript +bindings at this time are unaffected by module declarations. + +**NOTE:** By convention in the Chromium codebase, **all** Mojom files should +declare a module name with at least (and preferrably exactly) one top-level name +as well as an inner `mojom` module suffix. *e.g.*, `chrome.mojom`, +`business.mojom`, *etc.* + +This convention makes it easy to tell which symbols are generated by Mojom when +reading non-Mojom code, and it also avoids namespace collisions in the fairly +common scenario where you have a real C++ or Java `Foo` along with a +corresponding Mojom `Foo` for its serialized representation. + +### Imports + +If your Mojom references definitions from other Mojom files, you must **import** +those files. Import syntax is as follows: + +``` +import "services/widget/public/interfaces/frobinator.mojom"; +``` + +Import paths are always relative to the top-level directory. + +Note that circular imports are **not** supported. + +### Structs + +Structs are defined using the **struct** keyword, and they provide a way to +group related fields together: + +``` cpp +struct StringPair { + string first; + string second; +}; +``` + +Struct fields may be comprised of any of the types listed above in the +[Primitive Types](#Primitive-Types) section. + +Default values may be specified as long as they are constant: + +``` cpp +struct Request { + int32 id = -1; + string details; +}; +``` + +What follows is a fairly +comprehensive example using the supported field types: + +``` cpp +struct StringPair { + string first; + string second; +}; + +enum AnEnum { + YES, + NO +}; + +interface SampleInterface { + DoStuff(); +}; + +struct AllTheThings { + // Note that these types can never be marked nullable! + bool boolean_value; + int8 signed_8bit_value = 42; + uint8 unsigned_8bit_value; + int16 signed_16bit_value; + uint16 unsigned_16bit_value; + int32 signed_32bit_value; + uint32 unsigned_32bit_value; + int64 signed_64bit_value; + uint64 unsigned_64bit_value; + float float_value_32bit; + double float_value_64bit; + AnEnum enum_value = AnEnum.YES; + + // Strings may be nullable. + string? maybe_a_string_maybe_not; + + // Structs may contain other structs. These may also be nullable. + StringPair some_strings; + StringPair? maybe_some_more_strings; + + // In fact structs can also be nested, though in practice you must always make + // such fields nullable -- otherwise messages would need to be infinitely long + // in order to pass validation! + AllTheThings? more_things; + + // Arrays may be templated over any Mojom type, and are always nullable: + array<int32> numbers; + array<int32>? maybe_more_numbers; + + // Arrays of arrays of arrays... are fine. + array<array<array<AnEnum>>> this_works_but_really_plz_stop; + + // The element type may be nullable if it's a type which is allowed to be + // nullable. + array<AllTheThings?> more_maybe_things; + + // Fixed-size arrays get some extra validation on the receiving end to ensure + // that the correct number of elements is always received. + array<uint64, 2> uuid; + + // Maps follow many of the same rules as arrays. Key types may be any + // non-handle, non-collection type, and value types may be any supported + // struct field type. Maps may also be nullable. + map<string, int32> one_map; + map<AnEnum, string>? maybe_another_map; + map<StringPair, AllTheThings?>? maybe_a_pretty_weird_but_valid_map; + map<StringPair, map<int32, array<map<string, string>?>?>?> ridiculous; + + // And finally, all handle types are valid as struct fields and may be + // nullable. Note that interfaces and interface requests (the "Foo" and + // "Foo&" type syntax respectively) are just strongly-typed message pipe + // handles. + handle generic_handle; + handle<data_pipe_consumer> reader; + handle<data_pipe_producer>? maybe_writer; + handle<shared_buffer> dumping_ground; + handle<message_pipe> raw_message_pipe; + SampleInterface? maybe_a_sample_interface_client_pipe; + SampleInterface& non_nullable_sample_interface_request; + SampleInterface&? nullable_sample_interface_request; + associated SampleInterface associated_interface_client; + associated SampleInterface& associated_interface_request; + assocaited SampleInterface&? maybe_another_associated_request; +}; +``` + +For details on how all of these different types translate to usable generated +code, see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +### Enumeration Types + +Enumeration types may be defined using the **enum** keyword either directly +within a module or within the namespace of some struct or interface: + +``` +module business.mojom; + +enum Department { + SALES = 0, + DEV, +}; + +struct Employee { + enum Type { + FULL_TIME, + PART_TIME, + }; + + Type type; + // ... +}; +``` + +That that similar to C-style enums, individual values may be explicitly assigned +within an enum definition. By default values are based at zero and incremenet by +1 sequentially. + +The effect of nested definitions on generated bindings varies depending on the +target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages) + +### Constants + +Constants may be defined using the **const** keyword either directly within a +module or within the namespace of some struct or interface: + +``` +module business.mojom; + +const string kServiceName = "business"; + +struct Employee { + const uint64 kInvalidId = 0; + + enum Type { + FULL_TIME, + PART_TIME, + }; + + uint64 id = kInvalidId; + Type type; +}; +``` + +The effect of nested definitions on generated bindings varies depending on the +target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages) + +### Interfaces + +An **interface** is a logical bundle of parameterized request messages. Each +request message may optionally define a parameterized response message. Here's +syntax to define an interface `Foo` with various kinds of requests: + +``` +interface Foo { + // A request which takes no arguments and expects no response. + MyMessage(); + + // A request which has some arguments and expects no response. + MyOtherMessage(string name, array<uint8> bytes); + + // A request which expects a single-argument response. + MyMessageWithResponse(string command) => (bool success); + + // A request which expects a response with multiple arguments. + MyMessageWithMoarResponse(string a, string b) => (int8 c, int8 d); +}; +``` + +Anything which is a valid struct field type (see [Structs](#Structs)) is also a +valid request or response argument type. The type notation is the same for both. + +### Attributes + +Mojom definitions may have their meaning altered by **attributes**, specified +with a syntax similar to Java or C# attributes. There are a handle of +interesting attributes supported today. + +**`[Sync]`** +: The `Sync` attribute may be specified for any interface method which expects + a response. This makes it so that callers of the method can wait + synchronously for a response. See + [Synchronous Calls](/mojo/public/cpp/bindings#Synchronous-Calls) in the C++ + bindings documentation. Note that sync calls are not currently supported in + other target languages. + +**`[Extensible]`** +: The `Extensible` attribute may be specified for any enum definition. This + essentially disables builtin range validation when receiving values of the + enum type in a message, allowing older bindings to tolerate unrecognized + values from newer versions of the enum. + +**`[Native]`** +: The `Native` attribute may be specified for an empty struct declaration to + provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or + `IPC_STRUCT_TRAITS*` macros. + See [Using Legacy IPC Traits](/ipc#Using-Legacy-IPC-Traits) for more + details. Note support for this attribute is strictly limited to C++ bindings + generation. + +**`[MinVersion=N]`** +: The `MinVersion` attribute is used to specify the version at which a given + field, enum value, interface method, or method parameter was introduced. + See [Versioning](#Versioning) for more details. + +## Generated Code For Target Languages + +When the bindings generator successfully processes an input Mojom file, it emits +corresponding code for each supported target language. For more details on how +Mojom concepts translate to a given target langauge, please refer to the +bindings API documentation for that language: + +* [C++ Bindings](/mojo/public/cpp/bindings) +* [JavaScript Bindings](/mojo/public/js) +* [Java Bindings](/mojo/public/java/bindings) + +## Message Validation + +Regardless of target language, all interface messages are validated during +deserialization before they are dispatched to a receiving implementation of the +interface. This helps to ensure consitent validation across interfaces without +leaving the burden to developers and security reviewers every time a new message +is added. + +If a message fails validation, it is never dispatched. Instead a **connection +error** is raised on the binding object (see +[C++ Connection Errors](/mojo/public/cpp/bindings#Connection-Errors), +[Java Connection Errors](/mojo/public/java/bindings#Connection-Errors), or +[JavaScript Connection Errors](/mojo/public/js#Connection-Errors) for details.) + +Some baseline level of validation is done automatically for primitive Mojom +types. + +### Non-Nullable Objects + +Mojom fields or parameter values (*e.g.*, structs, interfaces, arrays, *etc.*) +may be marked nullable in Mojom definitions (see +[Primitive Types](#Primitive-Types).) If a field or parameter is **not** marked +nullable but a message is received with a null value in its place, that message +will fail validation. + +### Enums + +Enums declared in Mojom are automatically validated against the range of legal +values. For example if a Mojom declares the enum: + +``` cpp +enum AdvancedBoolean { + TRUE = 0, + FALSE = 1, + FILE_NOT_FOUND = 2, +}; +``` + +and a message is received with the integral value 3 (or anything other than 0, +1, or 2) in place of some `AdvancedBoolean` field or parameter, the message will +fail validation. + +*** note +NOTE: It's possible to avoid this type of validation error by explicitly marking +an enum as [Extensible](#Attributes) if you anticipate your enum being exchanged +between two different versions of the binding interface. See +[Versioning](#Versioning). +*** + +### Other failures + +There are a host of internal validation errors that may occur when a malformed +message is received, but developers should not be concerned with these +specifically; in general they can only result from internal bindings bugs, +compromised processes, or some remote endpoint making a dubious effort to +manually encode their own bindings messages. + +### Custom Validation + +It's also possible for developers to define custom validation logic for specific +Mojom struct types by exploiting the +[type mapping](/mojo/public/cpp/bindings#Type-Mapping) system for C++ bindings. +Messages rejected by custom validation logic trigger the same validation failure +behavior as the built-in type validation routines. + +## Associated Interfaces + +As mentioned in the [Primitive Types](#Primitive-Types) section above, interface +and interface request fields and parameters may be marked as `associated`. This +essentially means that they are piggy-backed on some other interface's message +pipe. + +Because individual interface message pipes operate independently there can be no +relative ordering guarantees among them. Associated interfaces are useful when +one interface needs to guarantee strict FIFO ordering with respect to one or +more other interfaces, as they allow interfaces to share a single pipe. + +Currenly associated interfaces are only supported in generated C++ bindings. +See the documentation for +[C++ Associated Interfaces](/mojo/public/cpp/bindings#Associated-Interfaces). + +## Versioning + +### Overview + +*** note +**NOTE:** You don't need to worry about versioning if you don't care about +backwards compatibility. Specifically, all parts of Chrome are updated +atomically today and there is not yet any possibility of any two Chrome +processes communicating with two different versions of any given Mojom +interface. +*** + +Services extend their interfaces to support new features over time, and clients +want to use those new features when they are available. If services and clients +are not updated at the same time, it's important for them to be able to +communicate with each other using different snapshots (versions) of their +interfaces. + +This document shows how to extend Mojom interfaces in a backwards-compatible +way. Changing interfaces in a non-backwards-compatible way is not discussed, +because in that case communication between different interface versions is +impossible anyway. + +### Versioned Structs + +You can use the `MinVersion` [attribute](#Attributes) to indicate from which +version a struct field is introduced. Assume you have the following struct: + +``` cpp +struct Employee { + uint64 employee_id; + string name; +}; +``` + +and you would like to add a birthday field. You can do: + +``` cpp +struct Employee { + uint64 employee_id; + string name; + [MinVersion=1] Date? birthday; +}; +``` + +By default, fields belong to version 0. New fields must be appended to the +struct definition (*i.e*., existing fields must not change **ordinal value**) +with the `MinVersion` attribute set to a number greater than any previous +existing versions. + +**Ordinal value** refers to the relative positional layout of a struct's fields +(and an interface's methods) when encoded in a message. Implicitly, ordinal +numbers are assigned to fields according to lexical position. In the example +above, `employee_id` has an ordinal value of 0 and `name` has an ordinal value +of 1. + +Ordinal values can be specified explicitly using `**@**` notation, subject to +the following hard constraints: + +* For any given struct or interface, if any field or method explicitly specifies + an ordinal value, all fields or methods must explicitly specify an ordinal + value. +* For an *N*-field struct or *N*-method interface, the set of explicitly + assigned ordinal values must be limited to the range *[0, N-1]*. + +You may reorder fields, but you must ensure that the ordinal values of existing +fields remain unchanged. For example, the following struct remains +backwards-compatible: + +``` cpp +struct Employee { + uint64 employee_id@0; + [MinVersion=1] Date? birthday@2; + string name@1; +}; +``` + +*** note +**NOTE:** Newly added fields of Mojo object or handle types MUST be nullable. +See [Primitive Types](#Primitive-Types). +*** + +### Versioned Interfaces + +There are two dimensions on which an interface can be extended + +**Appending New Parameters To Existing Methods** +: Parameter lists are treated as structs internally, so all the rules of + versioned structs apply to method parameter lists. The only difference is + that the version number is scoped to the whole interface rather than to any + individual parameter list. + + Please note that adding a response to a message which did not previously + expect a response is a not a backwards-compatible change. + +**Appending New Methods** +: Similarly, you can reorder methods with explicit ordinal values as long as + the ordinal values of existing methods are unchanged. + +For example: + +``` cpp +// Old version: +interface HumanResourceDatabase { + AddEmployee(Employee employee) => (bool success); + QueryEmployee(uint64 id) => (Employee? employee); +}; + +// New version: +interface HumanResourceDatabase { + AddEmployee(Employee employee) => (bool success); + + QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print) + => (Employee? employee, + [MinVersion=1] array<uint8>? finger_print); + + [MinVersion=1] + AttachFingerPrint(uint64 id, array<uint8> finger_print) + => (bool success); +}; +``` + +Similar to [versioned structs](#Versioned-Structs), when you pass the parameter +list of a request or response method to a destination using an older version of +an interface, unrecognized fields are silently discarded. However, if the method +call itself is not recognized, it is considered a validation error and the +receiver will close its end of the interface pipe. For example, if a client on +version 1 of the above interface sends an `AttachFingerPrint` request to an +implementation of version 0, the client will be disconnected. + +Bindings target languages that support versioning expose means to query or +assert the remote version from a client handle (*e.g.*, an +`InterfacePtr<T>` in C++ bindings.) + +See +[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations) +and [Java Versioning Considerations](/mojo/public/java/bindings#Versioning-Considerations) + +### Versioned Enums + +**By default, enums are non-extensible**, which means that generated message +validation code does not expect to see new values in the future. When an unknown +value is seen for a non-extensible enum field or parameter, a validation error +is raised. + +If you want an enum to be extensible in the future, you can apply the +`[Extensible]` [attribute](#Attributes): + +``` cpp +[Extensible] +enum Department { + SALES, + DEV, +}; +``` + +And later you can extend this enum without breaking backwards compatibility: + +``` cpp +[Extensible] +enum Department { + SALES, + DEV, + [MinVersion=1] RESEARCH, +}; +``` + +*** note +**NOTE:** For versioned enum definitions, the use of a `[MinVersion]` attribute +is strictly for documentation purposes. It has no impact on the generated code. +*** + +With extensible enums, bound interface implementations may receive unknown enum +values and will need to deal with them gracefully. See +[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations) +for details. + +## Grammar Reference + +Below is the (BNF-ish) context-free grammar of the Mojom language: + +``` +MojomFile = StatementList +StatementList = Statement StatementList | Statement +Statement = ModuleStatement | ImportStatement | Definition + +ModuleStatement = AttributeSection "module" Identifier ";" +ImportStatement = "import" StringLiteral ";" +Definition = Struct Union Interface Enum Const + +AttributeSection = "[" AttributeList "]" +AttributeList = <empty> | NonEmptyAttributeList +NonEmptyAttributeList = Attribute + | Attribute "," NonEmptyAttributeList +Attribute = Name + | Name "=" Name + | Name "=" Literal + +Struct = AttributeSection "struct" Name "{" StructBody "}" ";" + | AttributeSection "struct" Name ";" +StructBody = <empty> + | StructBody Const + | StructBody Enum + | StructBody StructField +StructField = AttributeSection TypeSpec Name Orginal Default ";" + +Union = AttributeSection "union" Name "{" UnionBody "}" ";" +UnionBody = <empty> | UnionBody UnionField +UnionField = AttributeSection TypeSpec Name Ordinal ";" + +Interface = AttributeSection "interface" Name "{" InterfaceBody "}" ";" +InterfaceBody = <empty> + | InterfaceBody Const + | InterfaceBody Enum + | InterfaceBody Method +Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";" +ParameterList = <empty> | NonEmptyParameterList +NonEmptyParameterList = Parameter + | Parameter "," NonEmptyParameterList +Parameter = AttributeSection TypeSpec Name Ordinal +Response = <empty> | "=>" "(" ParameterList ")" + +TypeSpec = TypeName "?" | TypeName +TypeName = BasicTypeName + | Array + | FixedArray + | Map + | InterfaceRequest +BasicTypeName = Identifier | "associated" Identifier | HandleType | NumericType +NumericType = "bool" | "int8" | "uint8" | "int16" | "uint16" | "int32" + | "uint32" | "int64" | "uint64" | "float" | "double" +HandleType = "handle" | "handle" "<" SpecificHandleType ">" +SpecificHandleType = "message_pipe" + | "shared_buffer" + | "data_pipe_consumer" + | "data_pipe_producer" +Array = "array" "<" TypeSpec ">" +FixedArray = "array" "<" TypeSpec "," IntConstDec ">" +Map = "map" "<" Identifier "," TypeSpec ">" +InterfaceRequest = Identifier "&" | "associated" Identifier "&" + +Ordinal = <empty> | OrdinalValue + +Default = <empty> | "=" Constant + +Enum = AttributeSection "enum" Name "{" NonEmptyEnumValueList "}" ";" + | AttributeSection "enum" Name "{" NonEmptyEnumValueList "," "}" ";" +NonEmptyEnumValueList = EnumValue | NonEmptyEnumValueList "," EnumValue +EnumValue = AttributeSection Name + | AttributeSection Name "=" Integer + | AttributeSection Name "=" Identifier + +Const = "const" TypeSpec Name "=" Constant ";" + +Constant = Literal | Identifier ";" + +Identifier = Name | Name "." Identifier + +Literal = Integer | Float | "true" | "false" | "default" | StringLiteral + +Integer = IntConst | "+" IntConst | "-" IntConst +IntConst = IntConstDec | IntConstHex + +Float = FloatConst | "+" FloatConst | "-" FloatConst + +; The rules below are for tokens matched strictly according to the given regexes + +Identifier = /[a-zA-Z_][0-9a-zA-Z_]*/ +IntConstDec = /0|(1-9[0-9]*)/ +IntConstHex = /0[xX][0-9a-fA-F]+/ +OrdinalValue = /@(0|(1-9[0-9]*))/ +FloatConst = ... # Imagine it's close enough to C-style float syntax. +StringLiteral = ... # Imagine it's close enough to C-style string literals, including escapes. +``` + +## Additional Documentation + +[Mojom Message Format](https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio/edit) +: Describes the wire format used by Mojo bindings interfaces over message + pipes. + +[Input Format of Mojom Message Validation Tests](https://docs.google.com/document/d/1-y-2IYctyX2NPaLxJjpJfzVNWCC2SR2MJAD9MpIytHQ/edit) +: Describes a text format used to facilitate bindings message validation + tests. diff --git a/mojo/public/tools/bindings/blink_bindings_configuration.gni b/mojo/public/tools/bindings/blink_bindings_configuration.gni new file mode 100644 index 0000000000..bb0fc432a3 --- /dev/null +++ b/mojo/public/tools/bindings/blink_bindings_configuration.gni @@ -0,0 +1,33 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +variant = "blink" + +for_blink = true + +_typemap_imports = [ + "//mojo/public/cpp/bindings/tests/blink_typemaps.gni", + "//third_party/WebKit/Source/platform/mojo/blink_typemaps.gni", + "//third_party/WebKit/public/blink_typemaps.gni", + "//third_party/WebKit/public/public_typemaps.gni", +] +_typemaps = [] + +foreach(typemap_import, _typemap_imports) { + # Avoid reassignment error by assigning to empty scope first. + _imported = { + } + _imported = read_file(typemap_import, "scope") + _typemaps += _imported.typemaps +} + +typemaps = [] +foreach(typemap, _typemaps) { + typemaps += [ { + filename = typemap + config = read_file(typemap, "scope") + } ] +} + +blacklist = [] diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni new file mode 100644 index 0000000000..ca36723fb0 --- /dev/null +++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni @@ -0,0 +1,83 @@ +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +_typemap_imports = [ + "//ash/public/interfaces/typemaps.gni", + "//cc/ipc/typemaps.gni", + "//chrome/browser/media/router/mojo/typemaps.gni", + "//chrome/common/extensions/typemaps.gni", + "//chrome/common/importer/typemaps.gni", + "//chrome/typemaps.gni", + "//components/arc/common/typemaps.gni", + "//components/metrics/public/cpp/typemaps.gni", + "//components/typemaps.gni", + "//content/common/bluetooth/typemaps.gni", + "//content/common/indexed_db/typemaps.gni", + "//content/common/presentation/typemaps.gni", + "//content/common/typemaps.gni", + "//content/public/common/typemaps.gni", + "//device/bluetooth/public/interfaces/typemaps.gni", + "//device/gamepad/public/interfaces/typemaps.gni", + "//device/generic_sensor/public/interfaces/typemaps.gni", + "//device/usb/public/interfaces/typemaps.gni", + "//extensions/common/typemaps.gni", + "//gpu/ipc/common/typemaps.gni", + "//media/capture/mojo/typemaps.gni", + "//media/mojo/interfaces/typemaps.gni", + "//mojo/common/typemaps.gni", + "//mojo/public/cpp/bindings/tests/chromium_typemaps.gni", + "//net/interfaces/typemaps.gni", + "//services/preferences/public/cpp/typemaps.gni", + "//services/resource_coordinator/public/cpp/typemaps.gni", + "//services/service_manager/public/cpp/typemaps.gni", + "//services/ui/gpu/interfaces/typemaps.gni", + "//services/ui/public/interfaces/cursor/typemaps.gni", + "//services/ui/public/interfaces/ime/typemaps.gni", + "//services/video_capture/public/interfaces/typemaps.gni", + "//skia/public/interfaces/typemaps.gni", + "//third_party/WebKit/public/public_typemaps.gni", + "//ui/base/mojo/typemaps.gni", + "//ui/display/mojo/typemaps.gni", + "//ui/events/devices/mojo/typemaps.gni", + "//ui/events/mojo/typemaps.gni", + "//ui/gfx/typemaps.gni", + "//ui/latency/mojo/typemaps.gni", + "//ui/message_center/mojo/typemaps.gni", + "//url/mojo/typemaps.gni", +] + +_typemap_imports_mac = [ "//content/common/typemaps_mac.gni" ] + +_typemaps = [] +foreach(typemap_import, _typemap_imports) { + # Avoid reassignment error by assigning to empty scope first. + _imported = { + } + _imported = read_file(typemap_import, "scope") + _typemaps += _imported.typemaps +} + +typemaps = [] +foreach(typemap, _typemaps) { + typemaps += [ { + filename = typemap + config = read_file(typemap, "scope") + } ] +} + +_typemaps_mac = [] +foreach(typemap_import, _typemap_imports_mac) { + _imported = { + } + _imported = read_file(typemap_import, "scope") + _typemaps_mac += _imported.typemaps +} + +typemaps_mac = [] +foreach(typemap, _typemaps_mac) { + typemaps_mac += [ { + filename = typemap + config = read_file(typemap, "scope") + } ] +} diff --git a/mojo/public/tools/bindings/format_typemap_generator_args.py b/mojo/public/tools/bindings/format_typemap_generator_args.py new file mode 100755 index 0000000000..5057d6cdac --- /dev/null +++ b/mojo/public/tools/bindings/format_typemap_generator_args.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys + +# This utility converts mojom dependencies into their corresponding typemap +# paths and formats them to be consumed by generate_type_mappings.py. + + +def FormatTypemap(typemap_filename): + # A simple typemap is valid Python with a minor alteration. + with open(typemap_filename) as f: + typemap_content = f.read().replace('=\n', '=') + typemap = {} + exec typemap_content in typemap + + for header in typemap.get('public_headers', []): + yield 'public_headers=%s' % header + for header in typemap.get('traits_headers', []): + yield 'traits_headers=%s' % header + for header in typemap.get('type_mappings', []): + yield 'type_mappings=%s' % header + + +def main(): + typemaps = sys.argv[1:] + print ' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap)) + for typemap in typemaps) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/mojo/public/tools/bindings/generate_type_mappings.py b/mojo/public/tools/bindings/generate_type_mappings.py new file mode 100755 index 0000000000..824f8045ab --- /dev/null +++ b/mojo/public/tools/bindings/generate_type_mappings.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# Copyright 2016 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Generates a JSON typemap from its command-line arguments and dependencies. + +Each typemap should be specified in an command-line argument of the form +key=value, with an argument of "--start-typemap" preceding each typemap. + +For example, +generate_type_mappings.py --output=foo.typemap --start-typemap \\ + public_headers=foo.h traits_headers=foo_traits.h \\ + type_mappings=mojom.Foo=FooImpl + +generates a foo.typemap containing +{ + "c++": { + "mojom.Foo": { + "typename": "FooImpl", + "traits_headers": [ + "foo_traits.h" + ], + "public_headers": [ + "foo.h" + ] + } + } +} + +Then, +generate_type_mappings.py --dependency foo.typemap --output=bar.typemap \\ + --start-typemap public_headers=bar.h traits_headers=bar_traits.h \\ + type_mappings=mojom.Bar=BarImpl + +generates a bar.typemap containing +{ + "c++": { + "mojom.Bar": { + "typename": "BarImpl", + "traits_headers": [ + "bar_traits.h" + ], + "public_headers": [ + "bar.h" + ] + }, + "mojom.Foo": { + "typename": "FooImpl", + "traits_headers": [ + "foo_traits.h" + ], + "public_headers": [ + "foo.h" + ] + } + } +} +""" + +import argparse +import json +import os +import re + + +def ReadTypemap(path): + with open(path) as f: + return json.load(f)['c++'] + + +def ParseTypemapArgs(args): + typemaps = [s for s in '\n'.join(args).split('--start-typemap\n') if s] + result = {} + for typemap in typemaps: + result.update(ParseTypemap(typemap)) + return result + + +def ParseTypemap(typemap): + values = {'type_mappings': [], 'public_headers': [], 'traits_headers': []} + for line in typemap.split('\n'): + if not line: + continue + key, _, value = line.partition('=') + values[key].append(value.lstrip('/')) + result = {} + mapping_pattern = \ + re.compile(r"""^([^=]+) # mojom type + = + ([^[]+) # native type + (?:\[([^]]+)\])?$ # optional attribute in square brackets + """, re.X) + for typename in values['type_mappings']: + match_result = mapping_pattern.match(typename) + assert match_result, ( + "Cannot parse entry in the \"type_mappings\" section: %s" % typename) + + mojom_type = match_result.group(1) + native_type = match_result.group(2) + attributes = [] + if match_result.group(3): + attributes = match_result.group(3).split(',') + + assert mojom_type not in result, ( + "Cannot map multiple native types (%s, %s) to the same mojom type: %s" % + (result[mojom_type]['typename'], native_type, mojom_type)) + + result[mojom_type] = { + 'typename': native_type, + 'non_copyable_non_movable': 'non_copyable_non_movable' in attributes, + 'move_only': 'move_only' in attributes, + 'copyable_pass_by_value': 'copyable_pass_by_value' in attributes, + 'nullable_is_same_type': 'nullable_is_same_type' in attributes, + 'hashable': 'hashable' in attributes, + 'public_headers': values['public_headers'], + 'traits_headers': values['traits_headers'], + } + return result + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + '--dependency', + type=str, + action='append', + default=[], + help=('A path to another JSON typemap to merge into the output. ' + 'This may be repeated to merge multiple typemaps.')) + parser.add_argument('--output', + type=str, + required=True, + help='The path to which to write the generated JSON.') + params, typemap_params = parser.parse_known_args() + typemaps = ParseTypemapArgs(typemap_params) + missing = [path for path in params.dependency if not os.path.exists(path)] + if missing: + raise IOError('Missing dependencies: %s' % ', '.join(missing)) + for path in params.dependency: + typemaps.update(ReadTypemap(path)) + with open(params.output, 'w') as f: + json.dump({'c++': typemaps}, f, indent=2) + + +if __name__ == '__main__': + main() diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl new file mode 100644 index 0000000000..f0d503e5e0 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl @@ -0,0 +1,131 @@ +{#--- + Macro for enum definition, and the declaration of associated functions. +---#} + +{%- macro enum_decl(enum) %} +{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %} +enum class {{enum_name}} : int32_t { +{%- for field in enum.fields %} +{%- if field.value %} + {{field.name}} = {{field.value|expression_to_text}}, +{%- else %} + {{field.name}}, +{%- endif %} +{%- endfor %} +}; + +inline std::ostream& operator<<(std::ostream& os, {{enum_name}} value) { +{%- if enum.fields %} + switch(value) { +{%- for _, values in enum.fields|groupby('numeric_value') %} + case {{enum_name}}::{{values[0].name}}: + return os << "{{enum_name}}:: +{%- if values|length > 1 -%} + {{'{'}} +{%- endif -%} + {{values|map(attribute='name')|join(', ')}} +{%- if values|length > 1 -%} + {{'}'}} +{%- endif -%} + "; +{%- endfor %} + default: + return os << "Unknown {{enum_name}} value: " << static_cast<int32_t>(value); + } +{%- else %} + return os << "Unknown {{enum_name}} value: " << static_cast<int32_t>(value); +{%- endif %} +} + +{#- Returns true if the given enum value exists in this version of enum. #} +inline bool IsKnownEnumValue({{enum_name}} value) { + return {{enum|get_name_for_kind(internal=True, + flatten_nested_kind=True)}}::IsKnownValue( + static_cast<int32_t>(value)); +} +{%- endmacro %} + +{%- macro enum_data_decl(enum) %} +{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %} +struct {{enum_name}}_Data { + public: + static bool constexpr kIsExtensible = {% if enum.extensible %}true{% else %}false{% endif %}; + + static bool IsKnownValue(int32_t value) { +{%- if enum.fields %} + switch (value) { +{%- for enum_field in enum.fields|groupby('numeric_value') %} + case {{enum_field[0]}}: +{%- endfor %} + return true; + } +{%- endif %} + return false; + } + + static bool Validate(int32_t value, + mojo::internal::ValidationContext* validation_context) { + if (kIsExtensible || IsKnownValue(value)) + return true; + + ReportValidationError(validation_context, + mojo::internal::VALIDATION_ERROR_UNKNOWN_ENUM_VALUE); + return false; + } +}; +{%- endmacro %} + +{%- macro enum_hash(enum) %} +{%- set enum_name = enum|get_qualified_name_for_kind( + flatten_nested_kind=True) %} +template <> +struct hash<{{enum_name}}> + : public mojo::internal::EnumHashImpl<{{enum_name}}> {}; +{%- endmacro %} + +{%- macro enum_hash_blink(enum) %} +{%- set enum_name = enum|get_qualified_name_for_kind( + flatten_nested_kind=True, include_variant=False) %} +{%- set hash_fn_name = enum|wtf_hash_fn_name_for_enum %} +{# We need two unused enum values: #} +{%- set empty_value = -1000000 %} +{%- set deleted_value = -1000001 %} +{%- set empty_value_unused = "false" if empty_value in enum|all_enum_values else "true" %} +{%- set deleted_value_unused = "false" if empty_value in enum|all_enum_values else "true" %} +namespace WTF { +struct {{hash_fn_name}} { + static unsigned hash(const {{enum_name}}& value) { + typedef base::underlying_type<{{enum_name}}>::type utype; + return DefaultHash<utype>::Hash().hash(static_cast<utype>(value)); + } + static bool equal(const {{enum_name}}& left, const {{enum_name}}& right) { + return left == right; + } + static const bool safeToCompareToEmptyOrDeleted = true; +}; + +template <> +struct DefaultHash<{{enum_name}}> { + using Hash = {{hash_fn_name}}; +}; + +template <> +struct HashTraits<{{enum_name}}> + : public GenericHashTraits<{{enum_name}}> { + static_assert({{empty_value_unused}}, + "{{empty_value}} is a reserved enum value"); + static_assert({{deleted_value_unused}}, + "{{deleted_value}} is a reserved enum value"); + static const bool hasIsEmptyValueFunction = true; + static bool isEmptyValue(const {{enum_name}}& value) { + return value == static_cast<{{enum_name}}>({{empty_value}}); + } + static void constructDeletedValue({{enum_name}}& slot, bool) { + slot = static_cast<{{enum_name}}>({{deleted_value}}); + } + static bool isDeletedValue(const {{enum_name}}& value) { + return value == static_cast<{{enum_name}}>({{deleted_value}}); + } +}; +} // namespace WTF +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl new file mode 100644 index 0000000000..d7d0e5d873 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl @@ -0,0 +1,29 @@ +{%- set mojom_type = enum|get_qualified_name_for_kind( + flatten_nested_kind=True) %} + +template <> +struct EnumTraits<{{mojom_type}}, {{mojom_type}}> { + static {{mojom_type}} ToMojom({{mojom_type}} input) { return input; } + static bool FromMojom({{mojom_type}} input, {{mojom_type}}* output) { + *output = input; + return true; + } +}; + +namespace internal { + +template <typename MaybeConstUserType> +struct Serializer<{{mojom_type}}, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = EnumTraits<{{mojom_type}}, UserType>; + + static void Serialize(UserType input, int32_t* output) { + *output = static_cast<int32_t>(Traits::ToMojom(input)); + } + + static bool Deserialize(int32_t input, UserType* output) { + return Traits::FromMojom(static_cast<{{mojom_type}}>(input), output); + } +}; + +} // namespace internal diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl new file mode 100644 index 0000000000..7f6497475a --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl @@ -0,0 +1,65 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +class {{interface.name}}Proxy; + +template <typename ImplRefTraits> +class {{interface.name}}Stub; + +class {{interface.name}}RequestValidator; +{%- if interface|has_callbacks %} +class {{interface.name}}ResponseValidator; +{%- endif %} + +class {{export_attribute}} {{interface.name}} + : public {{interface.name}}InterfaceBase { + public: + static const char Name_[]; + static constexpr uint32_t Version_ = {{interface.version}}; + static constexpr bool PassesAssociatedKinds_ = {% if interface|passes_associated_kinds %}true{% else %}false{% endif %}; + static constexpr bool HasSyncMethods_ = {% if interface|has_sync_methods %}true{% else %}false{% endif %}; + + using Proxy_ = {{interface.name}}Proxy; + + template <typename ImplRefTraits> + using Stub_ = {{interface.name}}Stub<ImplRefTraits>; + + using RequestValidator_ = {{interface.name}}RequestValidator; +{%- if interface|has_callbacks %} + using ResponseValidator_ = {{interface.name}}ResponseValidator; +{%- else %} + using ResponseValidator_ = mojo::PassThroughFilter; +{%- endif %} + +{#--- Metadata #} + enum MethodMinVersions : uint32_t { +{%- for method in interface.methods %} + k{{method.name}}MinVersion = {{method.min_version|default(0, true)}}, +{%- endfor %} + }; + +{#--- Enums #} +{%- for enum in interface.enums %} + using {{enum.name}} = {{enum|get_name_for_kind(flatten_nested_kind=True)}}; +{%- endfor %} + +{#--- Constants #} +{%- for constant in interface.constants %} + static {{constant|format_constant_declaration(nested=True)}}; +{%- endfor %} + +{#--- Methods #} + virtual ~{{interface.name}}() {} + +{%- for method in interface.methods %} +{% if method.response_parameters != None %} +{%- if method.sync %} + // Sync method. This signature is used by the client side; the service side + // should implement the signature with callback below. + virtual bool {{method.name}}({{interface_macros.declare_sync_method_params("", method)}}); +{%- endif %} + + using {{method.name}}Callback = {{interface_macros.declare_callback(method, + for_blink, use_once_callback)}}; +{%- endif %} + virtual void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) = 0; +{%- endfor %} +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl new file mode 100644 index 0000000000..aba18380af --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl @@ -0,0 +1,448 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +{%- import "struct_macros.tmpl" as struct_macros %} + +{%- set class_name = interface.name %} +{%- set proxy_name = interface.name ~ "Proxy" %} +{%- set namespace_as_string = "%s"|format(namespace|replace(".","::")) %} + +{%- macro alloc_params(struct, params, message, description) %} + mojo::internal::SerializationContext serialization_context; + serialization_context.handles.Swap(({{message}})->mutable_handles()); + serialization_context.associated_endpoint_handles.swap( + *({{message}})->mutable_associated_endpoint_handles()); + bool success = true; +{%- for param in struct.packed.packed_fields_in_ordinal_order %} + {{param.field.kind|cpp_wrapper_type}} p_{{param.field.name}}{}; +{%- endfor %} + {{struct.name}}DataView input_data_view({{params}}, &serialization_context); + {{struct_macros.deserialize(struct, "input_data_view", "p_%s", "success")}} + if (!success) { + ReportValidationErrorForMessage( + {{message}}, + mojo::internal::VALIDATION_ERROR_DESERIALIZATION_FAILED, + "{{description}} deserializer"); + return false; + } +{%- endmacro %} + +{%- macro pass_params(parameters) %} +{%- for param in parameters %} +std::move(p_{{param.name}}) +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro build_message(struct, input_pattern, struct_display_name, + serialization_context) -%} + {{struct_macros.serialize(struct, struct_display_name, input_pattern, + "params", "builder.buffer()", + serialization_context)}} + ({{serialization_context}})->handles.Swap( + builder.message()->mutable_handles()); + ({{serialization_context}})->associated_endpoint_handles.swap( + *builder.message()->mutable_associated_endpoint_handles()); +{%- endmacro %} + +{#--- Begin #} +const char {{class_name}}::Name_[] = "{{namespace_as_string}}::{{class_name}}"; + +{#--- Constants #} +{%- for constant in interface.constants %} +{%- if constant.kind|is_string_kind %} +const char {{interface.name}}::{{constant.name}}[] = {{constant|constant_value}}; +{%- endif %} +{%- endfor %} + + +{%- for method in interface.methods %} +{%- if method.sync %} +bool {{class_name}}::{{method.name}}({{interface_macros.declare_sync_method_params("", method)}}) { + NOTREACHED(); + return false; +} +{%- endif %} +{%- endfor %} + +{#--- ForwardToCallback definition #} +{%- for method in interface.methods -%} +{%- if method.response_parameters != None %} +{%- if method.sync %} +class {{class_name}}_{{method.name}}_HandleSyncResponse + : public mojo::MessageReceiver { + public: + {{class_name}}_{{method.name}}_HandleSyncResponse( + bool* result +{%- for param in method.response_parameters -%} + , {{param.kind|cpp_wrapper_type}}* out_{{param.name}} +{%- endfor %}) + : result_(result) +{%- for param in method.response_parameters -%} + , out_{{param.name}}_(out_{{param.name}}) +{%- endfor %} { + DCHECK(!*result_); + } + bool Accept(mojo::Message* message) override; + private: + bool* result_; +{%- for param in method.response_parameters %} + {{param.kind|cpp_wrapper_type}}* out_{{param.name}}_; +{%- endfor -%} + DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_HandleSyncResponse); +}; +bool {{class_name}}_{{method.name}}_HandleSyncResponse::Accept( + mojo::Message* message) { + internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>( + message->mutable_payload()); + +{%- set desc = class_name~"::"~method.name~" response" %} + {{alloc_params(method.response_param_struct, "params", "message", desc)}} + +{%- for param in method.response_parameters %} + *out_{{param.name}}_ = std::move(p_{{param.name}}); +{%- endfor %} + mojo::internal::SyncMessageResponseSetup::SetCurrentSyncResponseMessage( + message); + *result_ = true; + return true; +} +{%- endif %} + +class {{class_name}}_{{method.name}}_ForwardToCallback + : public mojo::MessageReceiver { + public: + {{class_name}}_{{method.name}}_ForwardToCallback( +{%- if use_once_callback %} + {{class_name}}::{{method.name}}Callback callback +{%- else %} + const {{class_name}}::{{method.name}}Callback& callback +{%- endif %} + ) : callback_(std::move(callback)) { + } + bool Accept(mojo::Message* message) override; + private: + {{class_name}}::{{method.name}}Callback callback_; + DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ForwardToCallback); +}; +bool {{class_name}}_{{method.name}}_ForwardToCallback::Accept( + mojo::Message* message) { + internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>( + message->mutable_payload()); + +{%- set desc = class_name~"::"~method.name~" response" %} + {{alloc_params(method.response_param_struct, "params", "message", desc)}} + if (!callback_.is_null()) { + mojo::internal::MessageDispatchContext context(message); + std::move(callback_).Run({{pass_params(method.response_parameters)}}); + } + return true; +} +{%- endif %} +{%- endfor %} + +{{proxy_name}}::{{proxy_name}}(mojo::MessageReceiverWithResponder* receiver) + : receiver_(receiver) { +} + +{#--- Proxy definitions #} + +{%- for method in interface.methods %} +{%- set message_name = + "internal::k%s_%s_Name"|format(interface.name, method.name) %} +{%- set params_struct = method.param_struct %} +{%- set params_description = + "%s.%s request"|format(interface.name, method.name) %} +{%- if method.sync %} +bool {{proxy_name}}::{{method.name}}( + {{interface_macros.declare_sync_method_params("param_", method)}}) { + mojo::internal::SerializationContext serialization_context; + {{struct_macros.get_serialized_size(params_struct, "param_%s", + "&serialization_context")}} + + mojo::internal::MessageBuilder builder( + {{message_name}}, + mojo::Message::kFlagIsSync | mojo::Message::kFlagExpectsResponse, + size, serialization_context.associated_endpoint_count); + + {{build_message(params_struct, "param_%s", params_description, + "&serialization_context")}} + + bool result = false; + std::unique_ptr<mojo::MessageReceiver> responder( + new {{class_name}}_{{method.name}}_HandleSyncResponse( + &result +{%- for param in method.response_parameters -%} + , param_{{param.name}} +{%- endfor %})); + ignore_result(receiver_->AcceptWithResponder(builder.message(), + std::move(responder))); + return result; +} +{%- endif %} + +void {{proxy_name}}::{{method.name}}( + {{interface_macros.declare_request_params("in_", method, use_once_callback)}}) { + mojo::internal::SerializationContext serialization_context; + {{struct_macros.get_serialized_size(params_struct, "in_%s", + "&serialization_context")}} + +{%- if method.response_parameters != None %} + constexpr uint32_t kFlags = mojo::Message::kFlagExpectsResponse; +{%- else %} + constexpr uint32_t kFlags = 0; +{%- endif %} + mojo::internal::MessageBuilder builder( + {{message_name}}, kFlags, size, + serialization_context.associated_endpoint_count); + + {{build_message(params_struct, "in_%s", params_description, + "&serialization_context")}} + +{%- if method.response_parameters != None %} + std::unique_ptr<mojo::MessageReceiver> responder( + new {{class_name}}_{{method.name}}_ForwardToCallback( + std::move(callback))); + ignore_result(receiver_->AcceptWithResponder(builder.message(), + std::move(responder))); +{%- else %} + // This return value may be ignored as false implies the Connector has + // encountered an error, which will be visible through other means. + ignore_result(receiver_->Accept(builder.message())); +{%- endif %} +} +{%- endfor %} + +{#--- ProxyToResponder definition #} +{%- for method in interface.methods -%} +{%- if method.response_parameters != None %} +{%- set message_name = + "internal::k%s_%s_Name"|format(interface.name, method.name) %} +{%- set response_params_struct = method.response_param_struct %} +{%- set params_description = + "%s.%s response"|format(interface.name, method.name) %} +class {{class_name}}_{{method.name}}_ProxyToResponder { + public: + static {{class_name}}::{{method.name}}Callback CreateCallback( + uint64_t request_id, + bool is_sync, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) { + std::unique_ptr<{{class_name}}_{{method.name}}_ProxyToResponder> proxy( + new {{class_name}}_{{method.name}}_ProxyToResponder( + request_id, is_sync, std::move(responder))); + return base::Bind(&{{class_name}}_{{method.name}}_ProxyToResponder::Run, + base::Passed(&proxy)); + } + + ~{{class_name}}_{{method.name}}_ProxyToResponder() { +#if DCHECK_IS_ON() + if (responder_) { + // Is the Service destroying the callback without running it + // and without first closing the pipe? + responder_->DCheckInvalid("The callback passed to " + "{{class_name}}::{{method.name}}() was never run."); + } +#endif + // If the Callback was dropped then deleting the responder will close + // the pipe so the calling application knows to stop waiting for a reply. + responder_ = nullptr; + } + + private: + {{class_name}}_{{method.name}}_ProxyToResponder( + uint64_t request_id, + bool is_sync, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) + : request_id_(request_id), + is_sync_(is_sync), + responder_(std::move(responder)) { + } + + void Run( + {{interface_macros.declare_responder_params( + "in_", method.response_parameters, for_blink)}}); + + uint64_t request_id_; + bool is_sync_; + std::unique_ptr<mojo::MessageReceiverWithStatus> responder_; + + DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ProxyToResponder); +}; + +void {{class_name}}_{{method.name}}_ProxyToResponder::Run( + {{interface_macros.declare_responder_params( + "in_", method.response_parameters, for_blink)}}) { + mojo::internal::SerializationContext serialization_context; + {{struct_macros.get_serialized_size(response_params_struct, "in_%s", + "&serialization_context")}} + + uint32_t flags = (is_sync_ ? mojo::Message::kFlagIsSync : 0) | + mojo::Message::kFlagIsResponse; + mojo::internal::MessageBuilder builder( + {{message_name}}, flags, size, + serialization_context.associated_endpoint_count); + builder.message()->set_request_id(request_id_); + + {{build_message(response_params_struct, "in_%s", params_description, + "&serialization_context")}} + ignore_result(responder_->Accept(builder.message())); + // TODO(darin): Accept() returning false indicates a malformed message, and + // that may be good reason to close the connection. However, we don't have a + // way to do that from here. We should add a way. + responder_ = nullptr; +} +{%- endif -%} +{%- endfor %} + +{#--- StubDispatch definition #} + +// static +bool {{class_name}}StubDispatch::Accept( + {{interface.name}}* impl, + mojo::Message* message) { +{%- if interface.methods %} + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters == None %} + internal::{{class_name}}_{{method.name}}_Params_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>( + message->mutable_payload()); + +{%- set desc = class_name~"::"~method.name %} + {{alloc_params(method.param_struct, "params", "message", desc)| + indent(4)}} + // A null |impl| means no implementation was bound. + assert(impl); + TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}"); + mojo::internal::MessageDispatchContext context(message); + impl->{{method.name}}({{pass_params(method.parameters)}}); + return true; +{%- else %} + break; +{%- endif %} + } +{%- endfor %} + } +{%- endif %} + return false; +} + +// static +bool {{class_name}}StubDispatch::AcceptWithResponder( + {{interface.name}}* impl, + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) { +{%- if interface.methods %} + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters != None %} + internal::{{class_name}}_{{method.name}}_Params_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>( + message->mutable_payload()); + +{%- set desc = class_name~"::"~method.name %} + {{alloc_params(method.param_struct, "params", "message", desc)| + indent(4)}} + {{class_name}}::{{method.name}}Callback callback = + {{class_name}}_{{method.name}}_ProxyToResponder::CreateCallback( + message->request_id(), + message->has_flag(mojo::Message::kFlagIsSync), + std::move(responder)); + // A null |impl| means no implementation was bound. + assert(impl); + TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}"); + mojo::internal::MessageDispatchContext context(message); + impl->{{method.name}}( +{%- if method.parameters -%}{{pass_params(method.parameters)}}, {% endif -%}std::move(callback)); + return true; +{%- else %} + break; +{%- endif %} + } +{%- endfor %} + } +{%- endif %} + return false; +} + +{#--- Request validator definitions #} + +bool {{class_name}}RequestValidator::Accept(mojo::Message* message) { + if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) + return true; + + mojo::internal::ValidationContext validation_context( + message->payload(), message->payload_num_bytes(), + message->handles()->size(), message->payload_num_interface_ids(), message, + "{{class_name}} RequestValidator"); + + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters != None %} + if (!mojo::internal::ValidateMessageIsRequestExpectingResponse( + message, &validation_context)) { + return false; + } +{%- else %} + if (!mojo::internal::ValidateMessageIsRequestWithoutResponse( + message, &validation_context)) { + return false; + } +{%- endif %} + if (!mojo::internal::ValidateMessagePayload< + internal::{{class_name}}_{{method.name}}_Params_Data>( + message, &validation_context)) { + return false; + } + return true; + } +{%- endfor %} + default: + break; + } + + // Unrecognized message. + ReportValidationError( + &validation_context, + mojo::internal::VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD); + return false; +} + +{#--- Response validator definitions #} +{% if interface|has_callbacks %} +bool {{class_name}}ResponseValidator::Accept(mojo::Message* message) { + if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) + return true; + + mojo::internal::ValidationContext validation_context( + message->payload(), message->payload_num_bytes(), + message->handles()->size(), message->payload_num_interface_ids(), message, + "{{class_name}} ResponseValidator"); + + if (!mojo::internal::ValidateMessageIsResponse(message, &validation_context)) + return false; + switch (message->header()->name) { +{%- for method in interface.methods if method.response_parameters != None %} + case internal::k{{class_name}}_{{method.name}}_Name: { + if (!mojo::internal::ValidateMessagePayload< + internal::{{class_name}}_{{method.name}}_ResponseParams_Data>( + message, &validation_context)) { + return false; + } + return true; + } +{%- endfor %} + default: + break; + } + + // Unrecognized message. + ReportValidationError( + &validation_context, + mojo::internal::VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD); + return false; +} +{%- endif -%} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl new file mode 100644 index 0000000000..8649273633 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl @@ -0,0 +1,49 @@ +{%- macro declare_params(prefix, parameters) %} +{%- for param in parameters -%} +{{param.kind|cpp_wrapper_param_type}} {{prefix}}{{param.name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro declare_responder_params(prefix, parameters, for_blink) %} +{%- for param in parameters -%} +{{param.kind|cpp_wrapper_param_type}} {{prefix}}{{param.name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro declare_callback(method, for_blink, use_once_callback) -%} +{%- if use_once_callback -%} +base::OnceCallback<void( +{%- else -%} +base::Callback<void( +{%- endif -%} +{%- for param in method.response_parameters -%} +{{param.kind|cpp_wrapper_param_type}} +{%- if not loop.last %}, {% endif %} +{%- endfor -%} +)> +{%- endmacro -%} + +{%- macro declare_request_params(prefix, method, use_once_callback) -%} +{{declare_params(prefix, method.parameters)}} +{%- if method.response_parameters != None -%} +{%- if method.parameters %}, {% endif -%} +{%- if use_once_callback -%} +{{method.name}}Callback callback +{%- else -%} +const {{method.name}}Callback& callback +{%- endif -%} +{%- endif -%} +{%- endmacro -%} + +{%- macro declare_sync_method_params(prefix, method) -%} +{{declare_params(prefix, method.parameters)}} +{%- if method.response_parameters %} +{%- if method.parameters %}, {% endif %} +{%- for param in method.response_parameters -%} +{{param.kind|cpp_wrapper_type}}* {{prefix}}{{param.name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endif -%} +{%- endmacro -%} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl new file mode 100644 index 0000000000..0a158ec3e8 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl @@ -0,0 +1,16 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +class {{export_attribute}} {{interface.name}}Proxy + : public {{interface.name}} { + public: + explicit {{interface.name}}Proxy(mojo::MessageReceiverWithResponder* receiver); + +{%- for method in interface.methods %} +{%- if method.sync %} + bool {{method.name}}({{interface_macros.declare_sync_method_params("", method)}}) override; +{%- endif %} + void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) override; +{%- endfor %} + + private: + mojo::MessageReceiverWithResponder* receiver_; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl new file mode 100644 index 0000000000..a00d14886d --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl @@ -0,0 +1,4 @@ +class {{export_attribute}} {{interface.name}}RequestValidator : public NON_EXPORTED_BASE(mojo::MessageReceiver) { + public: + bool Accept(mojo::Message* message) override; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl new file mode 100644 index 0000000000..e2caa02c79 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl @@ -0,0 +1,4 @@ +class {{export_attribute}} {{interface.name}}ResponseValidator : public NON_EXPORTED_BASE(mojo::MessageReceiver) { + public: + bool Accept(mojo::Message* message) override; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl new file mode 100644 index 0000000000..79ab46f337 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl @@ -0,0 +1,41 @@ +class {{export_attribute}} {{interface.name}}StubDispatch { + public: + static bool Accept({{interface.name}}* impl, mojo::Message* message); + static bool AcceptWithResponder( + {{interface.name}}* impl, + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder); +}; + +template <typename ImplRefTraits = + mojo::RawPtrImplRefTraits<{{interface.name}}>> +class {{interface.name}}Stub + : public NON_EXPORTED_BASE(mojo::MessageReceiverWithResponderStatus) { + public: + using ImplPointerType = typename ImplRefTraits::PointerType; + + {{interface.name}}Stub() {} + ~{{interface.name}}Stub() override {} + + void set_sink(ImplPointerType sink) { sink_ = std::move(sink); } + ImplPointerType& sink() { return sink_; } + + bool Accept(mojo::Message* message) override { + if (ImplRefTraits::IsNull(sink_)) + return false; + return {{interface.name}}StubDispatch::Accept( + ImplRefTraits::GetRawPointer(&sink_), message); + } + + bool AcceptWithResponder( + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) override { + if (ImplRefTraits::IsNull(sink_)) + return false; + return {{interface.name}}StubDispatch::AcceptWithResponder( + ImplRefTraits::GetRawPointer(&sink_), message, std::move(responder)); + } + + private: + ImplPointerType sink_; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl new file mode 100644 index 0000000000..964b25438e --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl @@ -0,0 +1,96 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +{%- set header_guard = "%s_SHARED_INTERNAL_H_"|format( + module.path|upper|replace("/","_")|replace(".","_")| + replace("-", "_")) %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include <stdint.h> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/map_data_internal.h" +#include "mojo/public/cpp/bindings/lib/native_enum_data.h" +#include "mojo/public/cpp/bindings/lib/native_struct_data.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" + +{%- for import in imports %} +#include "{{import.module.path}}-shared-internal.h" +{%- endfor %} + +namespace mojo { +namespace internal { +class ValidationContext; +} +} + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +namespace internal { + +{#--- Internal forward declarations #} +{%- for struct in structs %} +{%- if struct|is_native_only_kind %} +using {{struct.name}}_Data = mojo::internal::NativeStruct_Data; +{%- else %} +class {{struct.name}}_Data; +{%- endif %} +{%- endfor %} + +{%- for union in unions %} +class {{union.name}}_Data; +{%- endfor %} + +{#--- Enums #} +{%- from "enum_macros.tmpl" import enum_data_decl -%} +{%- for enum in all_enums %} +{%- if enum|is_native_only_kind %} +using {{enum|get_name_for_kind(flatten_nested_kind=True)}}_Data = + mojo::internal::NativeEnum_Data; +{%- else %} +{{enum_data_decl(enum)}} +{%- endif %} +{%- endfor %} + +#pragma pack(push, 1) + +{#--- Unions must be declared first because they can be members of structs #} +{#--- Union class declarations #} +{%- for union in unions %} +{% include "union_declaration.tmpl" %} +{%- endfor %} + +{#--- Struct class declarations #} +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Interface parameter definitions #} +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %} +constexpr uint32_t {{method_name}} = {{method.ordinal}}; +{%- set struct = method.param_struct %} +{% include "struct_declaration.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method.response_param_struct %} +{% include "struct_declaration.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +#pragma pack(pop) + +} // namespace internal +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +#endif // {{header_guard}} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl new file mode 100644 index 0000000000..645bb692b0 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl @@ -0,0 +1,64 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4065) +#endif + +#include "{{module.path}}-shared.h" + +#include <utility> + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +{%- for header in extra_traits_headers %} +#include "{{header}}" +{%- endfor %} + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} + +namespace internal { + +{#--- Union definitions #} +{%- for union in unions %} +{% include "union_definition.tmpl" %} +{%- endfor %} + +{#--- Struct definitions #} +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Interface parameter definitions #} +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %} +{%- set struct = method.param_struct %} +{% include "struct_definition.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method.response_param_struct %} +{% include "struct_definition.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +} // namespace internal + +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl new file mode 100644 index 0000000000..dd13466de1 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl @@ -0,0 +1,212 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +{%- set header_guard = "%s_SHARED_H_"|format( + module.path|upper|replace("/","_")|replace(".","_")| + replace("-", "_")) %} + +{%- macro mojom_type_traits(kind) %} +template <> +struct MojomTypeTraits<{{kind|get_qualified_name_for_kind}}DataView> { + using Data = {{kind|get_qualified_name_for_kind(internal=True)}}; +{%- if kind|is_union_kind %} + using DataAsArrayElement = Data; + static constexpr MojomTypeCategory category = MojomTypeCategory::UNION; +{%- else %} + using DataAsArrayElement = Pointer<Data>; + static constexpr MojomTypeCategory category = MojomTypeCategory::STRUCT; +{%- endif %} +}; +{%- endmacro %} + +{%- macro namespace_begin() %} +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +{%- endmacro %} + +{%- macro namespace_end() %} +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} +{%- endmacro %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include <stdint.h> + +#include <functional> +#include <ostream> +#include <type_traits> +#include <utility> + +#include "base/compiler_specific.h" +#include "mojo/public/cpp/bindings/array_data_view.h" +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "mojo/public/cpp/bindings/interface_data_view.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/map_data_view.h" +#include "mojo/public/cpp/bindings/native_enum.h" +#include "mojo/public/cpp/bindings/native_struct_data_view.h" +#include "mojo/public/cpp/bindings/string_data_view.h" +#include "{{module.path}}-shared-internal.h" +{%- for import in imports %} +#include "{{import.module.path}}-shared.h" +{%- endfor %} + +{{namespace_begin()}} + +{#--- Struct Forward Declarations -#} +{%- for struct in structs %} +{%- if struct|is_native_only_kind %} +using {{struct.name}}DataView = mojo::NativeStructDataView; +{%- else %} +class {{struct.name}}DataView; +{%- endif %} +{% endfor %} + +{#--- Union Forward Declarations -#} +{%- for union in unions %} +class {{union.name}}DataView; +{%- endfor %} + +{{namespace_end()}} + +namespace mojo { +namespace internal { + +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{{mojom_type_traits(struct)}} +{%- endif %} +{%- endfor %} + +{%- for union in unions %} +{{mojom_type_traits(union)}} +{%- endfor %} + +} // namespace internal +} // namespace mojo + +{{namespace_begin()}} + +{#--- Enums #} +{%- from "enum_macros.tmpl" import enum_decl%} +{%- for enum in all_enums %} +{%- if enum|is_native_only_kind %} +using {{enum|get_name_for_kind(flatten_nested_kind=True)}} = mojo::NativeEnum; +{%- else %} +{{enum_decl(enum)}} +{%- endif %} +{%- endfor %} + +{#--- Interfaces #} +{%- if interfaces %} +// Interface base classes. They are used for type safety check. +{%- endif %} +{%- for interface in interfaces %} +class {{interface.name}}InterfaceBase {}; + +using {{interface.name}}PtrDataView = + mojo::InterfacePtrDataView<{{interface.name}}InterfaceBase>; +using {{interface.name}}RequestDataView = + mojo::InterfaceRequestDataView<{{interface.name}}InterfaceBase>; +using {{interface.name}}AssociatedPtrInfoDataView = + mojo::AssociatedInterfacePtrInfoDataView<{{interface.name}}InterfaceBase>; +using {{interface.name}}AssociatedRequestDataView = + mojo::AssociatedInterfaceRequestDataView<{{interface.name}}InterfaceBase>; + +{%- endfor %} + +{#--- Structs #} +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_data_view_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Interface parameter definitions #} +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set struct = method.param_struct %} +{% include "struct_data_view_declaration.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method.response_param_struct %} +{% include "struct_data_view_declaration.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +{#--- Unions #} +{%- for union in unions %} +{% include "union_data_view_declaration.tmpl" %} +{%- endfor %} + +{{namespace_end()}} + +namespace std { + +{%- from "enum_macros.tmpl" import enum_hash %} +{%- for enum in all_enums %} +{%- if not enum|is_native_only_kind %} +{{enum_hash(enum)}} +{%- endif %} +{%- endfor %} + +} // namespace std + +namespace mojo { + +{#--- Enum Serialization Helpers -#} +{%- for enum in all_enums %} +{%- if not enum|is_native_only_kind %} +{% include "enum_serialization_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Struct Serialization Helpers -#} +{% for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_serialization_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Union Serialization Helpers -#} +{% if unions %} +{%- for union in unions %} +{% include "union_serialization_declaration.tmpl" %} +{%- endfor %} +{%- endif %} + +} // namespace mojo + +{{namespace_begin()}} + +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_data_view_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set struct = method.param_struct %} +{% include "struct_data_view_definition.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method.response_param_struct %} +{% include "struct_data_view_definition.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +{%- for union in unions %} +{% include "union_data_view_definition.tmpl" %} +{%- endfor %} + +{{namespace_end()}} + +#endif // {{header_guard}} + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl new file mode 100644 index 0000000000..2c66a85f87 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl @@ -0,0 +1,111 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +{%- if variant -%} +{%- set variant_path = "%s-%s"|format(module.path, variant) -%} +{%- else -%} +{%- set variant_path = module.path -%} +{%- endif %} + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#elif defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4056) +#pragma warning(disable:4065) +#pragma warning(disable:4756) +#endif + +#include "{{variant_path}}.h" + +#include <math.h> +#include <stdint.h> +#include <utility> + +#include "base/logging.h" +#include "base/trace_event/trace_event.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +{%- if for_blink %} +#include "mojo/public/cpp/bindings/lib/wtf_serialization.h" +{%- endif %} + +{%- for header in extra_traits_headers %} +#include "{{header}}" +{%- endfor %} + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +{%- if variant %} +namespace {{variant}} { +{%- endif %} + +{#--- Constants #} +{%- for constant in module.constants %} +{%- if constant.kind|is_string_kind %} +const char {{constant.name}}[] = {{constant|constant_value}}; +{%- endif %} +{%- endfor %} + +{#--- Struct Constants #} +{%- for struct in structs %} +{%- for constant in struct.constants %} +{%- if constant.kind|is_string_kind %} +const char {{struct.name}}::{{constant.name}}[] = {{constant|constant_value}}; +{%- endif %} +{%- endfor %} +{%- endfor %} + +{#--- Struct builder definitions #} +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{%- include "wrapper_class_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Union builder definitions #} +{%- for union in unions %} +{%- include "wrapper_union_class_definition.tmpl" %} +{%- endfor %} + +{#--- Interface definitions #} +{%- for interface in interfaces %} +{%- include "interface_definition.tmpl" %} +{%- endfor %} + +{%- if variant %} +} // namespace {{variant}} +{%- endif %} +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +namespace mojo { + +{#--- Struct Serialization Helpers -#} +{% for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_traits_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Union Serialization Helpers #} +{%- for union in unions %} +{%- include "union_traits_definition.tmpl" %} +{%- endfor %} + +} // namespace mojo + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl new file mode 100644 index 0000000000..804a46b6f8 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl @@ -0,0 +1,236 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +{%- if variant -%} +{%- set variant_path = "%s-%s"|format(module.path, variant) -%} +{%- else -%} +{%- set variant_path = module.path -%} +{%- endif -%} + +{%- set header_guard = "%s_H_"|format( + variant_path|upper|replace("/","_")|replace(".","_")| + replace("-", "_")) %} + +{%- macro namespace_begin() %} +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +{%- if variant %} +namespace {{variant}} { +{%- endif %} +{%- endmacro %} + +{%- macro namespace_end() %} +{%- if variant %} +} // namespace {{variant}} +{%- endif %} +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} +{%- endmacro %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include <stdint.h> + +#include <limits> +#include <type_traits> +#include <utility> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/optional.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/clone_traits.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/equals_traits.h" +#include "mojo/public/cpp/bindings/lib/control_message_handler.h" +#include "mojo/public/cpp/bindings/lib/control_message_proxy.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/union_accessor.h" +#include "mojo/public/cpp/bindings/native_struct.h" +#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h" +#include "mojo/public/cpp/bindings/struct_ptr.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h" +#include "mojo/public/cpp/bindings/union_traits.h" +#include "{{module.path}}-shared.h" +{%- for import in imports %} +{%- if variant %} +#include "{{"%s-%s.h"|format(import.module.path, variant)}}" +{%- else %} +#include "{{import.module.path}}.h" +{%- endif %} +{%- endfor %} +{%- if not for_blink %} +#include <string> +#include <vector> +{%- else %} +{# hash_util.h includes template specializations that should be present for + every use of {Inlined}StructPtr. #} +#include "mojo/public/cpp/bindings/lib/wtf_hash_util.h" +#include "third_party/WebKit/Source/wtf/HashFunctions.h" +#include "third_party/WebKit/Source/wtf/Optional.h" +#include "third_party/WebKit/Source/wtf/text/WTFString.h" +{%- endif %} + +{%- for header in extra_public_headers %} +#include "{{header}}" +{%- endfor %} + +{%- if export_header %} +#include "{{export_header}}" +{%- endif %} + +{#--- WTF enum hashing #} +{%- from "enum_macros.tmpl" import enum_hash_blink%} +{%- if for_blink %} +{%- for enum in all_enums %} +{%- if not enum|is_native_only_kind %} +{{enum_hash_blink(enum)}} +{%- endif %} +{%- endfor %} +{%- endif %} + +{{namespace_begin()}} + +{#--- Enums #} +{%- if variant %} +{%- for enum in enums %} +using {{enum.name}} = {{enum.name}}; // Alias for definition in the parent namespace. +{%- endfor %} +{%- endif %} + +{#--- Constants #} +{%- for constant in module.constants %} +{{constant|format_constant_declaration}}; +{%- endfor %} + +{#--- Interface Forward Declarations -#} +{% for interface in interfaces %} +class {{interface.name}}; +using {{interface.name}}Ptr = mojo::InterfacePtr<{{interface.name}}>; +using {{interface.name}}PtrInfo = mojo::InterfacePtrInfo<{{interface.name}}>; +using ThreadSafe{{interface.name}}Ptr = + mojo::ThreadSafeInterfacePtr<{{interface.name}}>; +using {{interface.name}}Request = mojo::InterfaceRequest<{{interface.name}}>; +using {{interface.name}}AssociatedPtr = + mojo::AssociatedInterfacePtr<{{interface.name}}>; +using ThreadSafe{{interface.name}}AssociatedPtr = + mojo::ThreadSafeAssociatedInterfacePtr<{{interface.name}}>; +using {{interface.name}}AssociatedPtrInfo = + mojo::AssociatedInterfacePtrInfo<{{interface.name}}>; +using {{interface.name}}AssociatedRequest = + mojo::AssociatedInterfaceRequest<{{interface.name}}>; +{% endfor %} + +{#--- Struct Forward Declarations -#} +{% for struct in structs %} +{%- if struct|is_native_only_kind %} +using {{struct.name}} = mojo::NativeStruct; +using {{struct.name}}Ptr = mojo::NativeStructPtr; +{%- else %} +class {{struct.name}}; +{%- if struct|should_inline %} +using {{struct.name}}Ptr = mojo::InlinedStructPtr<{{struct.name}}>; +{%- else %} +using {{struct.name}}Ptr = mojo::StructPtr<{{struct.name}}>; +{%- endif %} +{%- endif %} +{% endfor %} + +{#--- Union Forward Declarations -#} +{% for union in unions %} +class {{union.name}}; +{% if union|should_inline_union %} +typedef mojo::InlinedStructPtr<{{union.name}}> {{union.name}}Ptr; +{% else %} +typedef mojo::StructPtr<{{union.name}}> {{union.name}}Ptr; +{% endif %} +{%- endfor %} + +{#--- Interfaces -#} +{% for interface in interfaces %} +{% include "interface_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Proxies -#} +{% for interface in interfaces %} +{% include "interface_proxy_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Stubs -#} +{% for interface in interfaces %} +{% include "interface_stub_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Request Validators -#} +{% for interface in interfaces %} +{% include "interface_request_validator_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Response Validators -#} +{% for interface in interfaces if interface|has_callbacks %} +{% include "interface_response_validator_declaration.tmpl" %} +{%- endfor %} + +{#--- NOTE: Unions and non-inlined structs may have pointers to inlined structs, + so we need to fully define inlined structs ahead of the others. #} + +{#--- Inlined structs #} +{% for struct in structs %} +{% if struct|should_inline and not struct|is_native_only_kind %} +{% include "wrapper_class_declaration.tmpl" %} +{% endif %} +{%- endfor %} + +{#--- Unions must be declared before non-inlined structs because they can be + members of structs. #} +{#--- Unions #} +{% for union in unions %} +{% include "wrapper_union_class_declaration.tmpl" %} +{%- endfor %} + +{#--- Non-inlined structs #} +{% for struct in structs %} +{% if not struct|should_inline and not struct|is_native_only_kind %} +{% include "wrapper_class_declaration.tmpl" %} +{% endif %} +{%- endfor %} + +{%- for union in unions %} +{% include "wrapper_union_class_template_definition.tmpl" %} +{%- endfor %} + +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "wrapper_class_template_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{{namespace_end()}} + +namespace mojo { + +{#--- Struct Serialization Helpers -#} +{% for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_traits_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Union Serialization Helpers -#} +{% if unions %} +{%- for union in unions %} +{% include "union_traits_declaration.tmpl" %} +{%- endfor %} +{%- endif %} + +} // namespace mojo + +#endif // {{header_guard}} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl new file mode 100644 index 0000000000..96e0d614d8 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl @@ -0,0 +1,118 @@ +class {{struct.name}}DataView { + public: + {{struct.name}}DataView() {} + + {{struct.name}}DataView( + internal::{{struct.name}}_Data* data, + mojo::internal::SerializationContext* context) +{%- if struct|requires_context_for_data_view %} + : data_(data), context_(context) {} +{%- else %} + : data_(data) {} +{%- endif %} + + bool is_null() const { return !data_; } + +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set kind = pf.field.kind %} +{%- set name = pf.field.name %} +{%- if kind|is_union_kind %} + inline void Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output); + + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) { +{%- if pf.min_version != 0 %} + auto* pointer = data_->header_.version >= {{pf.min_version}} + ? &data_->{{name}} : nullptr; +{%- else %} + auto* pointer = &data_->{{name}}; +{%- endif %} + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + pointer, output, context_); + } + +{%- elif kind|is_object_kind %} + inline void Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output); + + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) { +{%- if pf.min_version != 0 %} + auto* pointer = data_->header_.version >= {{pf.min_version}} + ? data_->{{name}}.Get() : nullptr; +{%- else %} + auto* pointer = data_->{{name}}.Get(); +{%- endif %} + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + pointer, output, context_); + } + +{%- elif kind|is_enum_kind %} + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const { +{%- if pf.min_version != 0 %} + auto data_value = data_->header_.version >= {{pf.min_version}} + ? data_->{{name}} : 0; +{%- else %} + auto data_value = data_->{{name}}; +{%- endif %} + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + data_value, output); + } + + {{kind|cpp_data_view_type}} {{name}}() const { +{%- if pf.min_version != 0 %} + if (data_->header_.version < {{pf.min_version}}) + return {{kind|get_qualified_name_for_kind}}{}; +{%- endif %} + return static_cast<{{kind|cpp_data_view_type}}>(data_->{{name}}); + } + +{%- elif kind|is_any_handle_kind %} + {{kind|cpp_data_view_type}} Take{{name|under_to_camel}}() { + {{kind|cpp_data_view_type}} result; +{%- if pf.min_version != 0 %} + if (data_->header_.version < {{pf.min_version}}) + return result; +{%- endif %} + bool ret = + mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + &data_->{{name}}, &result, context_); + DCHECK(ret); + return result; + } + +{%- elif kind|is_any_interface_kind %} + template <typename UserType> + UserType Take{{name|under_to_camel}}() { + UserType result; +{%- if pf.min_version != 0 %} + if (data_->header_.version < {{pf.min_version}}) + return result; +{%- endif %} + bool ret = + mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + &data_->{{name}}, &result, context_); + DCHECK(ret); + return result; + } + +{%- else %} + {{kind|cpp_data_view_type}} {{name}}() const { +{%- if pf.min_version != 0 %} + if (data_->header_.version < {{pf.min_version}}) + return {{kind|cpp_data_view_type}}{}; +{%- endif %} + return data_->{{name}}; + } + +{%- endif %} +{%- endfor %} + private: + internal::{{struct.name}}_Data* data_ = nullptr; +{%- if struct|requires_context_for_data_view %} + mojo::internal::SerializationContext* context_ = nullptr; +{%- endif %} +}; + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl new file mode 100644 index 0000000000..95311dc124 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl @@ -0,0 +1,30 @@ +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set kind = pf.field.kind %} +{%- set name = pf.field.name %} + +{%- if kind|is_union_kind %} +inline void {{struct.name}}DataView::Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output) { +{%- if pf.min_version != 0 %} + auto pointer = data_->header_.version >= {{pf.min_version}} + ? &data_->{{name}} : nullptr; +{%- else %} + auto pointer = &data_->{{name}}; +{%- endif %} + *output = {{kind|cpp_data_view_type}}(pointer, context_); +} + +{%- elif kind|is_object_kind %} +inline void {{struct.name}}DataView::Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output) { +{%- if pf.min_version != 0 %} + auto pointer = data_->header_.version >= {{pf.min_version}} + ? data_->{{name}}.Get() : nullptr; +{%- else %} + auto pointer = data_->{{name}}.Get(); +{%- endif %} + *output = {{kind|cpp_data_view_type}}(pointer, context_); +} +{%- endif %} +{%- endfor %} + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl new file mode 100644 index 0000000000..156f7742c4 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl @@ -0,0 +1,46 @@ +{%- set class_name = struct.name ~ "_Data" -%} + +class {{class_name}} { + public: + static {{class_name}}* New(mojo::internal::Buffer* buf) { + return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}(); + } + + static bool Validate(const void* data, + mojo::internal::ValidationContext* validation_context); + + mojo::internal::StructHeader header_; +{%- for packed_field in struct.packed.packed_fields %} +{%- set name = packed_field.field.name %} +{%- set kind = packed_field.field.kind %} +{%- if kind.spec == 'b' %} + uint8_t {{name}} : 1; +{%- else %} + {{kind|cpp_field_type}} {{name}}; +{%- endif %} +{%- if not loop.last %} +{%- set next_pf = struct.packed.packed_fields[loop.index0 + 1] %} +{%- set pad = next_pf.offset - (packed_field.offset + packed_field.size) %} +{%- if pad > 0 %} + uint8_t pad{{loop.index0}}_[{{pad}}]; +{%- endif %} +{%- endif %} +{%- endfor %} + +{%- set num_fields = struct.versions[-1].num_fields %} +{%- if num_fields > 0 %} +{%- set last_field = struct.packed.packed_fields[num_fields - 1] %} +{%- set offset = last_field.offset + last_field.size %} +{%- set pad = offset|get_pad(8) %} +{%- if pad > 0 %} + uint8_t padfinal_[{{pad}}]; +{%- endif %} +{%- endif %} + + private: + {{class_name}}() : header_({sizeof(*this), {{struct.versions[-1].version}}}) { + } + ~{{class_name}}() = delete; +}; +static_assert(sizeof({{class_name}}) == {{struct.versions[-1].num_bytes}}, + "Bad sizeof({{class_name}})"); diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl new file mode 100644 index 0000000000..60dca4010e --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl @@ -0,0 +1,70 @@ +{%- import "validation_macros.tmpl" as validation_macros %} +{%- set class_name = struct.name ~ "_Data" %} + +// static +bool {{class_name}}::Validate( + const void* data, + mojo::internal::ValidationContext* validation_context) { + if (!data) + return true; + + if (!ValidateStructHeaderAndClaimMemory(data, validation_context)) + return false; + + // NOTE: The memory backing |object| may be smaller than |sizeof(*object)| if + // the message comes from an older version. + const {{class_name}}* object = static_cast<const {{class_name}}*>(data); + + static constexpr struct { + uint32_t version; + uint32_t num_bytes; + } kVersionSizes[] = { +{%- for version in struct.versions -%} + { {{version.version}}, {{version.num_bytes}} }{% if not loop.last %}, {% endif -%} +{%- endfor -%} + }; + + if (object->header_.version <= + kVersionSizes[arraysize(kVersionSizes) - 1].version) { + // Scan in reverse order to optimize for more recent versions. + for (int i = arraysize(kVersionSizes) - 1; i >= 0; --i) { + if (object->header_.version >= kVersionSizes[i].version) { + if (object->header_.num_bytes == kVersionSizes[i].num_bytes) + break; + + ReportValidationError( + validation_context, + mojo::internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + } + } else if (object->header_.num_bytes < + kVersionSizes[arraysize(kVersionSizes) - 1].num_bytes) { + ReportValidationError( + validation_context, + mojo::internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + +{#- Before validating fields introduced at a certain version, we need to add + a version check, which makes sure we skip further validation if |object| + is from an earlier version. |last_checked_version| records the last + version that we have added such version check. #} +{%- set last_checked_version = 0 %} +{%- for packed_field in struct.packed.packed_fields_in_ordinal_order %} +{%- set kind = packed_field.field.kind %} +{%- if kind|is_object_kind or kind|is_any_handle_or_interface_kind or + kind|is_enum_kind %} +{%- if packed_field.min_version > last_checked_version %} +{%- set last_checked_version = packed_field.min_version %} + if (object->header_.version < {{packed_field.min_version}}) + return true; +{%- endif %} +{%- set field_expr = "object->" ~ packed_field.field.name %} +{{validation_macros.validate_field(packed_field.field, field_expr, struct.name, true)}} +{%- endif %} +{%- endfor %} + + return true; +} + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl new file mode 100644 index 0000000000..bb5fb9c496 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl @@ -0,0 +1,161 @@ +{# TODO(yzshen): Make these templates more readable. #} + +{# Computes the serialized size for the specified struct. + |struct| is the struct definition. + |input_field_pattern| should be a pattern that contains one string + placeholder, for example, "input->%s", "p_%s". The placeholder will be + substituted with struct field names to refer to the input fields. + |context| is the name of the serialization context. + |input_may_be_temp| indicates whether any input may be temporary obejcts. + We need to assign temporary objects to local variables before passing it to + Serializer, because it is illegal to pass temporary objects as non-const + references. + This macro is expanded to compute seriailized size for both: + - user-defined structs: the input is an instance of the corresponding struct + wrapper class. + - method parameters/response parameters: the input is a list of + arguments. + It declares |size| of type size_t to store the resulting size. #} +{%- macro get_serialized_size(struct, input_field_pattern, context, + input_may_be_temp=False) -%} + size_t size = sizeof({{struct|get_qualified_name_for_kind(internal=True)}}); +{%- for pf in struct.packed.packed_fields_in_ordinal_order + if pf.field.kind|is_object_kind or pf.field.kind|is_associated_kind %} +{%- set name = pf.field.name -%} +{%- set kind = pf.field.kind -%} +{%- set original_input_field = input_field_pattern|format(name) %} +{%- set input_field = "in_%s"|format(name) if input_may_be_temp + else original_input_field %} +{%- if input_may_be_temp %} + decltype({{original_input_field}}) in_{{name}} = {{original_input_field}}; +{%- endif %} + +{%- set serializer_type = kind|unmapped_type_for_serializer %} +{%- if kind|is_union_kind %} + size += mojo::internal::PrepareToSerialize<{{serializer_type}}>( + {{input_field}}, true, {{context}}); +{%- else %} + size += mojo::internal::PrepareToSerialize<{{serializer_type}}>( + {{input_field}}, {{context}}); +{%- endif %} +{%- endfor %} +{%- endmacro -%} + +{# Serializes the specified struct. + |struct| is the struct definition. + |struct_display_name| is the display name for the struct that can be showed + in error/log messages, for example, "FooStruct", "FooMethod request". + |input_field_pattern| should be a pattern that contains one string + placeholder, for example, "input->%s", "p_%s". The placeholder will be + substituted with struct field names to refer to the input fields. + |output| is the name of the output struct instance. + |buffer| is the name of the Buffer instance used. + |context| is the name of the serialization context. + |input_may_be_temp|: please see the comments of get_serialized_size. + This macro is expanded to do serialization for both: + - user-defined structs: the input is an instance of the corresponding struct + wrapper class. + - method parameters/response parameters: the input is a list of + arguments. #} +{%- macro serialize(struct, struct_display_name, input_field_pattern, output, + buffer, context, input_may_be_temp=False) -%} + auto {{output}} = + {{struct|get_qualified_name_for_kind(internal=True)}}::New({{buffer}}); + ALLOW_UNUSED_LOCAL({{output}}); +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set input_field = input_field_pattern|format(pf.field.name) %} +{%- set name = pf.field.name %} +{%- set kind = pf.field.kind %} +{%- set serializer_type = kind|unmapped_type_for_serializer %} + +{%- if kind|is_object_kind or kind|is_any_handle_or_interface_kind %} +{%- set original_input_field = input_field_pattern|format(name) %} +{%- set input_field = "in_%s"|format(name) if input_may_be_temp + else original_input_field %} +{%- if input_may_be_temp %} + decltype({{original_input_field}}) in_{{name}} = {{original_input_field}}; +{%- endif %} +{%- endif %} + +{%- if kind|is_object_kind %} +{%- if kind|is_array_kind or kind|is_map_kind %} + typename decltype({{output}}->{{name}})::BaseType* {{name}}_ptr; + const mojo::internal::ContainerValidateParams {{name}}_validate_params( + {{kind|get_container_validate_params_ctor_args|indent(10)}}); + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, {{buffer}}, &{{name}}_ptr, &{{name}}_validate_params, + {{context}}); + {{output}}->{{name}}.Set({{name}}_ptr); +{%- elif kind|is_union_kind %} + auto {{name}}_ptr = &{{output}}->{{name}}; + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, {{buffer}}, &{{name}}_ptr, true, {{context}}); +{%- else %} + typename decltype({{output}}->{{name}})::BaseType* {{name}}_ptr; + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, {{buffer}}, &{{name}}_ptr, {{context}}); + {{output}}->{{name}}.Set({{name}}_ptr); +{%- endif %} +{%- if not kind|is_nullable_kind %} + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + {{output}}->{{name}}.is_null(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + "null {{name}} in {{struct_display_name}}"); +{%- endif %} + +{%- elif kind|is_any_handle_or_interface_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, &{{output}}->{{name}}, {{context}}); +{%- if not kind|is_nullable_kind %} + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !mojo::internal::IsHandleOrInterfaceValid({{output}}->{{name}}), +{%- if kind|is_associated_kind %} + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID, +{%- else %} + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, +{%- endif %} + "invalid {{name}} in {{struct_display_name}}"); +{%- endif %} + +{%- elif kind|is_enum_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, &{{output}}->{{name}}); + +{%- else %} + {{output}}->{{name}} = {{input_field}}; +{%- endif %} +{%- endfor %} +{%- endmacro -%} + +{# Deserializes the specified struct. + |struct| is the struct definition. + |input| is the name of the input struct data view. It is expected to be + non-null. + |output_field_pattern| should be a pattern that contains one string + placeholder, for example, "result->%s", "p_%s". The placeholder will be + substituted with struct field names to refer to the output fields. + |context| is the name of the serialization context. + |success| is the name of a bool variable to track success of the operation. + This macro is expanded to do deserialization for both: + - user-defined structs: the output is an instance of the corresponding + struct wrapper class. + - method parameters/response parameters: the output is a list of + arguments. #} +{%- macro deserialize(struct, input, output_field_pattern, success) -%} +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set output_field = output_field_pattern|format(pf.field.name) %} +{%- set name = pf.field.name %} +{%- set kind = pf.field.kind %} +{%- if kind|is_object_kind or kind|is_enum_kind %} + if (!{{input}}.Read{{name|under_to_camel}}(&{{output_field}})) + {{success}} = false; +{%- elif kind|is_any_handle_kind %} + {{output_field}} = {{input}}.Take{{name|under_to_camel}}(); +{%- elif kind|is_any_interface_kind %} + {{output_field}} = + {{input}}.Take{{name|under_to_camel}}<decltype({{output_field}})>(); +{%- else %} + {{output_field}} = {{input}}.{{name}}(); +{%- endif %} +{%- endfor %} +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl new file mode 100644 index 0000000000..835178beda --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl @@ -0,0 +1,57 @@ +{%- import "struct_macros.tmpl" as struct_macros %} +{%- set data_view = struct|get_qualified_name_for_kind ~ "DataView" %} +{%- set data_type = struct|get_qualified_name_for_kind(internal=True) %} + +namespace internal { + +template <typename MaybeConstUserType> +struct Serializer<{{data_view}}, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = StructTraits<{{data_view}}, UserType>; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) + return 0; + + void* custom_context = CustomContextHelper<Traits>::SetUp(input, context); + ALLOW_UNUSED_LOCAL(custom_context); + + {{struct_macros.get_serialized_size( + struct, "CallWithContext(Traits::%s, input, custom_context)", + "context", True)|indent(2)}} + return size; + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buffer, + {{data_type}}** output, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) { + *output = nullptr; + return; + } + + void* custom_context = CustomContextHelper<Traits>::GetNext(context); + + {{struct_macros.serialize( + struct, struct.name ~ " struct", + "CallWithContext(Traits::%s, input, custom_context)", "result", + "buffer", "context", True)|indent(2)}} + *output = result; + + CustomContextHelper<Traits>::TearDown(input, custom_context); + } + + static bool Deserialize({{data_type}}* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists<Traits>(output); + + {{data_view}} data_view(input, context); + return Traits::Read(data_view, output); + } +}; + +} // namespace internal diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl new file mode 100644 index 0000000000..1b7cf8954b --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl @@ -0,0 +1,32 @@ +{%- set mojom_type = struct|get_qualified_name_for_kind %} + +template <> +struct {{export_attribute}} StructTraits<{{mojom_type}}::DataView, + {{mojom_type}}Ptr> { + static bool IsNull(const {{mojom_type}}Ptr& input) { return !input; } + static void SetToNull({{mojom_type}}Ptr* output) { output->reset(); } + +{%- for field in struct.fields %} +{%- set return_ref = field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} +{# We want the field accessor to be const whenever possible to allow + structs to be used as map keys. + TODO(tibell): Make this check more precise to deal with e.g. + custom types which don't contain handles but require non-const + reference for serialization. #} +{%- set maybe_const = "" if field.kind|contains_handles_or_interfaces else "const" %} +{%- if return_ref %} + static {{maybe_const}} decltype({{mojom_type}}::{{field.name}})& {{field.name}}( + {{maybe_const}} {{mojom_type}}Ptr& input) { + return input->{{field.name}}; + } +{%- else %} + static decltype({{mojom_type}}::{{field.name}}) {{field.name}}( + const {{mojom_type}}Ptr& input) { + return input->{{field.name}}; + } +{%- endif %} +{%- endfor %} + + static bool Read({{mojom_type}}::DataView input, {{mojom_type}}Ptr* output); +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl new file mode 100644 index 0000000000..f84337f5bf --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl @@ -0,0 +1,14 @@ +{%- import "struct_macros.tmpl" as struct_macros %} +{%- set mojom_type = struct|get_qualified_name_for_kind %} + +// static +bool StructTraits<{{mojom_type}}::DataView, {{mojom_type}}Ptr>::Read( + {{mojom_type}}::DataView input, + {{mojom_type}}Ptr* output) { + bool success = true; + {{mojom_type}}Ptr result({{mojom_type}}::New()); + {{struct_macros.deserialize(struct, "input", "result->%s", + "success")|indent(4)}} + *output = std::move(result); + return success; +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl new file mode 100644 index 0000000000..5973ba294b --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl @@ -0,0 +1,92 @@ +class {{union.name}}DataView { + public: + using Tag = internal::{{union.name}}_Data::{{union.name}}_Tag; + + {{union.name}}DataView() {} + + {{union.name}}DataView( + internal::{{union.name}}_Data* data, + mojo::internal::SerializationContext* context) +{%- if union|requires_context_for_data_view %} + : data_(data), context_(context) {} +{%- else %} + : data_(data) {} +{%- endif %} + + bool is_null() const { + // For inlined unions, |data_| is always non-null. In that case we need to + // check |data_->is_null()|. + return !data_ || data_->is_null(); + } + + Tag tag() const { return data_->tag; } + +{%- for field in union.fields %} +{%- set kind = field.kind %} +{%- set name = field.name %} + bool is_{{name}}() const { return data_->tag == Tag::{{name|upper}}; } + +{%- if kind|is_object_kind %} + inline void Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output); + + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) { + DCHECK(is_{{name}}()); + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + data_->data.f_{{name}}.Get(), output, context_); + } + +{%- elif kind|is_enum_kind %} + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const { + DCHECK(is_{{name}}()); + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + data_->data.f_{{name}}, output); + } + + {{kind|cpp_data_view_type}} {{name}}() const { + DCHECK(is_{{name}}()); + return static_cast<{{kind|cpp_data_view_type}}>( + data_->data.f_{{name}}); + } + +{%- elif kind|is_any_handle_kind %} + {{kind|cpp_data_view_type}} Take{{name|under_to_camel}}() { + DCHECK(is_{{name}}()); + {{kind|cpp_data_view_type}} result; + bool ret = + mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + &data_->data.f_{{name}}, &result, context_); + DCHECK(ret); + return result; + } + +{%- elif kind|is_any_interface_kind %} + template <typename UserType> + UserType Take{{name|under_to_camel}}() { + DCHECK(is_{{name}}()); + UserType result; + bool ret = + mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + &data_->data.f_{{name}}, &result, context_); + DCHECK(ret); + return result; + } + +{%- else %} + {{kind|cpp_data_view_type}} {{name}}() const { + DCHECK(is_{{name}}()); + return data_->data.f_{{name}}; + } + +{%- endif %} +{%- endfor %} + + private: + internal::{{union.name}}_Data* data_ = nullptr; +{%- if union|requires_context_for_data_view %} + mojo::internal::SerializationContext* context_ = nullptr; +{%- endif %} +}; + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl new file mode 100644 index 0000000000..6da9280a73 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl @@ -0,0 +1,12 @@ +{%- for field in union.fields %} +{%- set kind = field.kind %} +{%- set name = field.name %} + +{%- if kind|is_object_kind %} +inline void {{union.name}}DataView::Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output) { + DCHECK(is_{{name}}()); + *output = {{kind|cpp_data_view_type}}(data_->data.f_{{name}}.Get(), context_); +} +{%- endif %} +{%- endfor %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl new file mode 100644 index 0000000000..005ba76b61 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl @@ -0,0 +1,56 @@ +{%- set class_name = union.name ~ "_Data" -%} +{%- set enum_name = union.name ~ "_Tag" -%} +{%- import "struct_macros.tmpl" as struct_macros %} + +class {{class_name}} { + public: + // Used to identify Mojom Union Data Classes. + typedef void MojomUnionDataType; + + {{class_name}}() {} + // Do nothing in the destructor since it won't be called when it is a + // non-inlined union. + ~{{class_name}}() {} + + static {{class_name}}* New(mojo::internal::Buffer* buf) { + return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}(); + } + + static bool Validate(const void* data, + mojo::internal::ValidationContext* validation_context, + bool inlined); + + bool is_null() const { return size == 0; } + + void set_null() { + size = 0U; + tag = static_cast<{{enum_name}}>(0); + data.unknown = 0U; + } + + enum class {{enum_name}} : uint32_t { +{% for field in union.fields %} + {{field.name|upper}}, +{%- endfor %} + }; + + // A note on layout: + // "Each non-static data member is allocated as if it were the sole member of + // a struct." - Section 9.5.2 ISO/IEC 14882:2011 (The C++ Spec) + union MOJO_ALIGNAS(8) Union_ { +{%- for field in union.fields %} +{%- if field.kind.spec == 'b' %} + uint8_t f_{{field.name}} : 1; +{%- else %} + {{field.kind|cpp_union_field_type}} f_{{field.name}}; +{%- endif %} +{%- endfor %} + uint64_t unknown; + }; + + uint32_t size; + {{enum_name}} tag; + Union_ data; +}; +static_assert(sizeof({{class_name}}) == mojo::internal::kUnionDataSize, + "Bad sizeof({{class_name}})"); diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl new file mode 100644 index 0000000000..af5ea9f8a8 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl @@ -0,0 +1,47 @@ +{%- import "validation_macros.tmpl" as validation_macros %} +{%- set class_name = union.name ~ "_Data" %} +{%- set enum_name = union.name ~ "_Tag" -%} + +// static +bool {{class_name}}::Validate( + const void* data, + mojo::internal::ValidationContext* validation_context, + bool inlined) { + if (!data) { + DCHECK(!inlined); + return true; + } + + // If it is inlined, the alignment is already enforced by its enclosing + // object. We don't have to validate that. + DCHECK(!inlined || mojo::internal::IsAligned(data)); + + if (!inlined && + !mojo::internal::ValidateNonInlinedUnionHeaderAndClaimMemory( + data, validation_context)) { + return false; + } + + const {{class_name}}* object = static_cast<const {{class_name}}*>(data); + ALLOW_UNUSED_LOCAL(object); + + if (inlined && object->is_null()) + return true; + + switch (object->tag) { +{% for field in union.fields %} + case {{enum_name}}::{{field.name|upper}}: { +{%- set field_expr = "object->data.f_" ~ field.name %} +{{validation_macros.validate_field(field, field_expr, union.name, false)|indent(4)}} + return true; + } +{%- endfor %} + default: { + ReportValidationError( + validation_context, + mojo::internal::VALIDATION_ERROR_UNKNOWN_UNION_TAG, + "unknown tag in {{union.name}}"); + return false; + } + } +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl new file mode 100644 index 0000000000..b589ae9147 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl @@ -0,0 +1,141 @@ +{%- set data_view = union|get_qualified_name_for_kind ~ "DataView" %} +{%- set data_type = union|get_qualified_name_for_kind(internal=True) %} + +namespace internal { + +template <typename MaybeConstUserType> +struct Serializer<{{data_view}}, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = UnionTraits<{{data_view}}, UserType>; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + bool inlined, + SerializationContext* context) { + size_t size = inlined ? 0 : sizeof({{data_type}}); + + if (CallIsNullIfExists<Traits>(input)) + return size; + + void* custom_context = CustomContextHelper<Traits>::SetUp(input, context); + ALLOW_UNUSED_LOCAL(custom_context); + + switch (CallWithContext(Traits::GetTag, input, custom_context)) { +{%- for field in union.fields %} +{%- set name = field.name %} + case {{data_view}}::Tag::{{name|upper}}: { +{%- if field.kind|is_object_kind or field.kind|is_associated_kind %} +{%- set kind = field.kind %} +{%- set serializer_type = kind|unmapped_type_for_serializer %} + decltype(CallWithContext(Traits::{{name}}, input, custom_context)) + in_{{name}} = CallWithContext(Traits::{{name}}, input, + custom_context); +{%- if kind|is_union_kind %} + size += mojo::internal::PrepareToSerialize<{{serializer_type}}>( + in_{{name}}, false, context); +{%- else %} + size += mojo::internal::PrepareToSerialize<{{serializer_type}}>( + in_{{name}}, context); +{%- endif %} +{%- endif %} + break; + } +{%- endfor %} + } + return size; + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buffer, + {{data_type}}** output, + bool inlined, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) { + if (inlined) + (*output)->set_null(); + else + *output = nullptr; + return; + } + + void* custom_context = CustomContextHelper<Traits>::GetNext(context); + + if (!inlined) + *output = {{data_type}}::New(buffer); + + {{data_type}}* result = *output; + ALLOW_UNUSED_LOCAL(result); + // TODO(azani): Handle unknown and objects. + // Set the not-null flag. + result->size = kUnionDataSize; + result->tag = CallWithContext(Traits::GetTag, input, custom_context); + switch (result->tag) { +{%- for field in union.fields %} +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- set serializer_type = kind|unmapped_type_for_serializer %} + case {{data_view}}::Tag::{{field.name|upper}}: { + decltype(CallWithContext(Traits::{{name}}, input, custom_context)) + in_{{name}} = CallWithContext(Traits::{{name}}, input, + custom_context); +{%- if kind|is_object_kind %} + typename decltype(result->data.f_{{name}})::BaseType* ptr; +{%- if kind|is_union_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, buffer, &ptr, false, context); +{%- elif kind|is_array_kind or kind|is_map_kind %} + const ContainerValidateParams {{name}}_validate_params( + {{kind|get_container_validate_params_ctor_args|indent(16)}}); + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, buffer, &ptr, &{{name}}_validate_params, context); +{%- else %} + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, buffer, &ptr, context); +{%- endif %} + result->data.f_{{name}}.Set(ptr); +{%- if not kind|is_nullable_kind %} + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !ptr, mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + "null {{name}} in {{union.name}} union"); +{%- endif %} + +{%- elif kind|is_any_handle_or_interface_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, &result->data.f_{{name}}, context); +{%- if not kind|is_nullable_kind %} + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !mojo::internal::IsHandleOrInterfaceValid(result->data.f_{{name}}), +{%- if kind|is_associated_kind %} + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID, +{%- else %} + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, +{%- endif %} + "invalid {{name}} in {{union.name}} union"); +{%- endif %} + +{%- elif kind|is_enum_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, &result->data.f_{{name}}); + +{%- else %} + result->data.f_{{name}} = in_{{name}}; +{%- endif %} + break; + } +{%- endfor %} + } + + CustomContextHelper<Traits>::TearDown(input, custom_context); + } + + static bool Deserialize({{data_type}}* input, + UserType* output, + SerializationContext* context) { + if (!input || input->is_null()) + return CallSetToNullIfExists<Traits>(output); + + {{data_view}} data_view(input, context); + return Traits::Read(data_view, output); + } +}; + +} // namespace internal diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl new file mode 100644 index 0000000000..4933e57871 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl @@ -0,0 +1,24 @@ +{%- set mojom_type = union|get_qualified_name_for_kind %} + +template <> +struct {{export_attribute}} UnionTraits<{{mojom_type}}::DataView, + {{mojom_type}}Ptr> { + static bool IsNull(const {{mojom_type}}Ptr& input) { return !input; } + static void SetToNull({{mojom_type}}Ptr* output) { output->reset(); } + + static {{mojom_type}}::Tag GetTag(const {{mojom_type}}Ptr& input) { + return input->which(); + } + +{%- for field in union.fields %} +{%- set maybe_const_in = "" if field.kind|contains_handles_or_interfaces else "const" %} +{%- set maybe_const_out = "" if field.kind|contains_handles_or_interfaces or not field.kind|is_reference_kind else "const" %} +{# We want the field accessor to be const whenever possible to allow + structs to be used as map keys. #} + static {{maybe_const_out}} {{field.kind|cpp_union_trait_getter_return_type}} {{field.name}}({{maybe_const_in}} {{mojom_type}}Ptr& input) { + return input->get_{{field.name}}(); + } +{%- endfor %} + + static bool Read({{mojom_type}}::DataView input, {{mojom_type}}Ptr* output); +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl new file mode 100644 index 0000000000..cde3f95669 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl @@ -0,0 +1,47 @@ +{%- set mojom_type = union|get_qualified_name_for_kind %} + +// static +bool UnionTraits<{{mojom_type}}::DataView, {{mojom_type}}Ptr>::Read( + {{mojom_type}}::DataView input, + {{mojom_type}}Ptr* output) { + *output = {{mojom_type}}::New(); + {{mojom_type}}Ptr& result = *output; + + internal::UnionAccessor<{{mojom_type}}> result_acc(result.get()); + switch (input.tag()) { +{%- for field in union.fields %} + case {{mojom_type}}::Tag::{{field.name|upper}}: { +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- set serializer_type = kind|unmapped_type_for_serializer %} +{%- if kind|is_object_kind %} + result_acc.SwitchActive({{mojom_type}}::Tag::{{name|upper}}); + if (!input.Read{{name|under_to_camel}}(result_acc.data()->{{name}})) + return false; + +{%- elif kind|is_any_handle_kind %} + auto result_{{name}} = input.Take{{name|under_to_camel}}(); + result->set_{{name}}(std::move(result_{{name}})); + +{%- elif kind|is_any_interface_kind %} + auto result_{{name}} = + input.Take{{name|under_to_camel}}<typename std::remove_reference<decltype(result->get_{{name}}())>::type>(); + result->set_{{name}}(std::move(result_{{name}})); + +{%- elif kind|is_enum_kind %} + decltype(result->get_{{name}}()) result_{{name}}; + if (!input.Read{{name|under_to_camel}}(&result_{{name}})) + return false; + result->set_{{name}}(result_{{name}}); + +{%- else %} + result->set_{{name}}(input.{{name}}()); +{%- endif %} + break; + } +{%- endfor %} + default: + return false; + } + return true; +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl new file mode 100644 index 0000000000..a50a585c09 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl @@ -0,0 +1,82 @@ +{#- Validates the specified field, which is supposed to be an object + (struct/array/string/map/union). If it is a union, |union_is_inlined| + indicates whether the union is inlined. (Nested unions are not inlined.) + This macro is expanded by the Validate() method. #} +{%- macro validate_object(field, field_expr, object_name, union_is_inlined) %} +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- if not kind|is_nullable_kind %} +{%- if kind|is_union_kind and union_is_inlined %} + if (!mojo::internal::ValidateInlinedUnionNonNullable( + {{field_expr}}, "null {{name}} field in {{object_name}}", + validation_context)) { + return false; + } +{%- else %} + if (!mojo::internal::ValidatePointerNonNullable( + {{field_expr}}, "null {{name}} field in {{object_name}}", + validation_context)) { + return false; + } +{%- endif %} +{%- endif %} +{%- if kind|is_array_kind or kind|is_string_kind or kind|is_map_kind %} + const mojo::internal::ContainerValidateParams {{name}}_validate_params( + {{kind|get_container_validate_params_ctor_args|indent(6)}}); + if (!mojo::internal::ValidateContainer({{field_expr}}, validation_context, + &{{name}}_validate_params)) { + return false; + } +{%- elif kind|is_struct_kind %} + if (!mojo::internal::ValidateStruct({{field_expr}}, validation_context)) + return false; +{%- elif kind|is_union_kind %} +{%- if union_is_inlined %} + if (!mojo::internal::ValidateInlinedUnion({{field_expr}}, validation_context)) + return false; +{%- else %} + if (!mojo::internal::ValidateNonInlinedUnion({{field_expr}}, + validation_context)) + return false; +{%- endif %} +{%- else %} +#error Not reached! +{%- endif %} +{%- endmacro %} + +{#- Validates the specified field, which is supposed to be a handle, + an interface, an associated interface or an associated interface request. + This macro is expanded by the Validate() method. #} +{%- macro validate_handle_or_interface(field, field_expr, object_name) %} +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- if not kind|is_nullable_kind %} + if (!mojo::internal::ValidateHandleOrInterfaceNonNullable( + {{field_expr}}, + "invalid {{name}} field in {{object_name}}", validation_context)) { + return false; + } +{%- endif %} + if (!mojo::internal::ValidateHandleOrInterface({{field_expr}}, + validation_context)) { + return false; + } +{%- endmacro %} + +{#- Validates the specified field, which is supposed to be an enum. + This macro is expanded by the Validate() method. #} +{%- macro validate_enum(field, field_expr) %} + if (!{{field.kind|get_qualified_name_for_kind(internal=True,flatten_nested_kind=True)}} + ::Validate({{field_expr}}, validation_context)) + return false; +{%- endmacro %} + +{%- macro validate_field(field, field_expr, object_name, union_is_inlined) %} +{%- if field.kind|is_object_kind -%} +{{validate_object(field, field_expr, object_name, union_is_inlined)}} +{%- elif field.kind|is_any_handle_or_interface_kind -%} +{{validate_handle_or_interface(field, field_expr, object_name)}} +{%- elif field.kind|is_enum_kind %} +{{validate_enum(field, field_expr)}} +{%- endif %} +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl new file mode 100644 index 0000000000..7ad9b4e1bc --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl @@ -0,0 +1,94 @@ +class {{export_attribute}} {{struct.name}} { + public: + using DataView = {{struct.name}}DataView; + using Data_ = internal::{{struct.name}}_Data; + +{#--- Enums #} +{%- for enum in struct.enums -%} + using {{enum.name}} = {{enum|get_name_for_kind(flatten_nested_kind=True)}}; +{%- endfor %} + +{#--- Constants #} +{%- for constant in struct.constants %} + static {{constant|format_constant_declaration(nested=True)}}; +{%- endfor %} + + template <typename... Args> + static {{struct.name}}Ptr New(Args&&... args) { + return {{struct.name}}Ptr( + base::in_place, + std::forward<Args>(args)...); + } + + template <typename U> + static {{struct.name}}Ptr From(const U& u) { + return mojo::TypeConverter<{{struct.name}}Ptr, U>::Convert(u); + } + + template <typename U> + U To() const { + return mojo::TypeConverter<U, {{struct.name}}>::Convert(*this); + } + +{% for constructor in struct|struct_constructors %} + {% if constructor.params|length == 1 %}explicit {% endif %}{{struct.name}}( +{%- for field in constructor.params %} +{%- set type = field.kind|cpp_wrapper_param_type %} +{%- set name = field.name %} + {{type}} {{name}} +{%- if not loop.last -%},{%- endif %} +{%- endfor %}); +{% endfor %} + ~{{struct.name}}(); + + // Clone() is a template so it is only instantiated if it is used. Thus, the + // bindings generator does not need to know whether Clone() or copy + // constructor/assignment are available for members. + template <typename StructPtrType = {{struct.name}}Ptr> + {{struct.name}}Ptr Clone() const; + + // Equals() is a template so it is only instantiated if it is used. Thus, the + // bindings generator does not need to know whether Equals() or == operator + // are available for members. + template <typename T, + typename std::enable_if<std::is_same< + T, {{struct.name}}>::value>::type* = nullptr> + bool Equals(const T& other) const; + +{%- if struct|is_hashable %} + size_t Hash(size_t seed) const; +{%- endif %} + +{%- set serialization_result_type = "WTF::Vector<uint8_t>" + if for_blink else "std::vector<uint8_t>" %} + + template <typename UserType> + static {{serialization_result_type}} Serialize(UserType* input) { + return mojo::internal::StructSerializeImpl< + {{struct.name}}::DataView, {{serialization_result_type}}>(input); + } + + template <typename UserType> + static bool Deserialize(const {{serialization_result_type}}& input, + UserType* output) { + return mojo::internal::StructDeserializeImpl< + {{struct.name}}::DataView, {{serialization_result_type}}>( + input, output, Validate); + } + +{#--- Struct members #} +{% for field in struct.fields %} +{%- set type = field.kind|cpp_wrapper_type %} +{%- set name = field.name %} + {{type}} {{name}}; +{%- endfor %} + + private: + static bool Validate(const void* data, + mojo::internal::ValidationContext* validation_context); + +{%- if struct|contains_move_only_members %} + DISALLOW_COPY_AND_ASSIGN({{struct.name}}); +{%- endif %} +}; + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl new file mode 100644 index 0000000000..ab8c22d49c --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl @@ -0,0 +1,39 @@ +{% for constructor in struct|struct_constructors %} +{{struct.name}}::{{struct.name}}( +{%- for field in constructor.params %} +{%- set type = field.kind|cpp_wrapper_param_type %} +{%- set name = field.name %} + {{type}} {{name}}_in +{%- if not loop.last -%},{%- endif %} +{%- endfor %}) +{%- for field, is_parameter in constructor.fields %} +{%- set name = field.name %} + {% if loop.first %}:{% else %} {% endif %} {{name}}( +{%- if is_parameter -%} +std::move({{name}}_in) +{%- else -%} +{{ field|default_value }} +{%- endif -%} +){% if not loop.last %},{% endif %} +{%- endfor %} {} +{% endfor %} +{{struct.name}}::~{{struct.name}}() = default; + +{%- if struct|is_hashable %} +size_t {{struct.name}}::Hash(size_t seed) const { +{%- for field in struct.fields %} +{%- if for_blink %} + seed = mojo::internal::WTFHash(seed, this->{{field.name}}); +{%- else %} + seed = mojo::internal::Hash(seed, this->{{field.name}}); +{%- endif %} +{%- endfor %} + return seed; +} +{%- endif %} + +bool {{struct.name}}::Validate( + const void* data, + mojo::internal::ValidationContext* validation_context) { + return Data_::Validate(data, validation_context); +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl new file mode 100644 index 0000000000..feb861569f --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl @@ -0,0 +1,20 @@ +template <typename StructPtrType> +{{struct.name}}Ptr {{struct.name}}::Clone() const { + return New( +{%- for field in struct.fields %} + mojo::Clone({{field.name}}) +{%- if not loop.last -%},{%- endif %} +{%- endfor %} + ); +} + +template <typename T, + typename std::enable_if<std::is_same< + T, {{struct.name}}>::value>::type*> +bool {{struct.name}}::Equals(const T& other) const { +{%- for field in struct.fields %} + if (!mojo::internal::Equals(this->{{field.name}}, other.{{field.name}})) + return false; +{%- endfor %} + return true; +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl new file mode 100644 index 0000000000..8b7cf9e6b1 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl @@ -0,0 +1,80 @@ +class {{export_attribute}} {{union.name}} { + public: + using DataView = {{union.name}}DataView; + using Data_ = internal::{{union.name}}_Data; + using Tag = Data_::{{union.name}}_Tag; + + static {{union.name}}Ptr New(); + + template <typename U> + static {{union.name}}Ptr From(const U& u) { + return mojo::TypeConverter<{{union.name}}Ptr, U>::Convert(u); + } + + template <typename U> + U To() const { + return mojo::TypeConverter<U, {{union.name}}>::Convert(*this); + } + + {{union.name}}(); + ~{{union.name}}(); + + // Clone() is a template so it is only instantiated if it is used. Thus, the + // bindings generator does not need to know whether Clone() or copy + // constructor/assignment are available for members. + template <typename UnionPtrType = {{union.name}}Ptr> + {{union.name}}Ptr Clone() const; + + // Equals() is a template so it is only instantiated if it is used. Thus, the + // bindings generator does not need to know whether Equals() or == operator + // are available for members. + template <typename T, + typename std::enable_if<std::is_same< + T, {{union.name}}>::value>::type* = nullptr> + bool Equals(const T& other) const; + +{%- if union|is_hashable %} + size_t Hash(size_t seed) const; +{%- endif %} + + Tag which() const { + return tag_; + } + +{% for field in union.fields %} + bool is_{{field.name}}() const { return tag_ == Tag::{{field.name|upper}}; } + + {{field.kind|cpp_union_getter_return_type}} get_{{field.name}}() const { + DCHECK(tag_ == Tag::{{field.name|upper}}); +{%- if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + return *(data_.{{field.name}}); +{%- else %} + return data_.{{field.name}}; +{%- endif %} + } + + void set_{{field.name}}({{field.kind|cpp_wrapper_param_type}} {{field.name}}); +{%- endfor %} + + private: + friend class mojo::internal::UnionAccessor<{{union.name}}>; + union Union_ { + Union_() {} + ~Union_() {} + +{%- for field in union.fields %} +{%- if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + {{field.kind|cpp_wrapper_type}}* {{field.name}}; +{%- else %} + {{field.kind|cpp_wrapper_type}} {{field.name}}; +{%- endif %} +{%- endfor %} + }; + void SwitchActive(Tag new_active); + void SetActive(Tag new_active); + void DestroyActive(); + Tag tag_; + Union_ data_; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl new file mode 100644 index 0000000000..b9e416a9f4 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl @@ -0,0 +1,85 @@ +// static +{{union.name}}Ptr {{union.name}}::New() { + return {{union.name}}Ptr(base::in_place); +} + +{{union.name}}::{{union.name}}() { + // TODO(azani): Implement default values here when/if we support them. + // TODO(azani): Set to UNKNOWN when unknown is implemented. + SetActive(static_cast<Tag>(0)); +} + +{{union.name}}::~{{union.name}}() { + DestroyActive(); +} + +{% for field in union.fields %} +void {{union.name}}::set_{{field.name}}({{field.kind|cpp_wrapper_param_type}} {{field.name}}) { + SwitchActive(Tag::{{field.name|upper}}); +{% if field.kind|is_string_kind %} + *(data_.{{field.name}}) = {{field.name}}; +{% elif field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + *(data_.{{field.name}}) = std::move({{field.name}}); +{%- else %} + data_.{{field.name}} = {{field.name}}; +{%- endif %} +} +{%- endfor %} + +void {{union.name}}::SwitchActive(Tag new_active) { + if (new_active == tag_) { + return; + } + + DestroyActive(); + SetActive(new_active); +} + +void {{union.name}}::SetActive(Tag new_active) { + switch (new_active) { +{% for field in union.fields %} + case Tag::{{field.name|upper}}: +{% if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + data_.{{field.name}} = new {{field.kind|cpp_wrapper_type}}(); +{%- endif %} + break; +{%- endfor %} + } + + tag_ = new_active; +} + +void {{union.name}}::DestroyActive() { + switch (tag_) { +{% for field in union.fields %} + case Tag::{{field.name|upper}}: +{% if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + delete data_.{{field.name}}; +{%- endif %} + break; +{%- endfor %} + } +} + +{%- if union|is_hashable %} +size_t {{union.name}}::Hash(size_t seed) const { + seed = mojo::internal::HashCombine(seed, static_cast<uint32_t>(tag_)); + switch (tag_) { +{% for field in union.fields %} + case Tag::{{field.name|upper}}: +{%- if for_blink %} + return mojo::internal::WTFHash(seed, data_.{{field.name}}); +{%- else %} + return mojo::internal::Hash(seed, data_.{{field.name}}); +{%- endif %} +{%- endfor %} + default: + NOTREACHED(); + return seed; + } +} + +{%- endif %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl new file mode 100644 index 0000000000..4c4851fa83 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl @@ -0,0 +1,41 @@ +template <typename UnionPtrType> +{{union.name}}Ptr {{union.name}}::Clone() const { + // Use UnionPtrType to prevent the compiler from trying to compile this + // without being asked. + UnionPtrType rv(New()); + switch (tag_) { +{%- for field in union.fields %} + case Tag::{{field.name|upper}}: +{%- if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + rv->set_{{field.name}}(mojo::Clone(*data_.{{field.name}})); +{%- else %} + rv->set_{{field.name}}(mojo::Clone(data_.{{field.name}})); +{%- endif %} + break; +{%- endfor %} + }; + return rv; +} + +template <typename T, + typename std::enable_if<std::is_same< + T, {{union.name}}>::value>::type*> +bool {{union.name}}::Equals(const T& other) const { + if (tag_ != other.which()) + return false; + + switch (tag_) { +{%- for field in union.fields %} + case Tag::{{field.name|upper}}: +{%- if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + return mojo::internal::Equals(*(data_.{{field.name}}), *(other.data_.{{field.name}})); +{%- else %} + return mojo::internal::Equals(data_.{{field.name}}, other.data_.{{field.name}}); +{%- endif %} +{%- endfor %} + }; + + return false; +} diff --git a/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl new file mode 100644 index 0000000000..db193e29a3 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl @@ -0,0 +1,3 @@ +{% macro constant_def(constant) %} +public static final {{constant.kind|java_type}} {{constant|name}} = {{constant|constant_value}}; +{% endmacro %} diff --git a/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl new file mode 100644 index 0000000000..0a4e29956b --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl @@ -0,0 +1,12 @@ +{% from "constant_definition.tmpl" import constant_def %} +{% include "header.java.tmpl" %} + +public final class {{main_entity}} { +{% for constant in constants %} + + {{constant_def(constant)|indent(4)}} +{% endfor %} + + private {{main_entity}}() {} + +} diff --git a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl new file mode 100644 index 0000000000..4c0823cce6 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl @@ -0,0 +1,418 @@ +{%- from "constant_definition.tmpl" import constant_def %} +{%- from "enum_definition.tmpl" import enum_def %} + +{%- macro equality(kind, v1, v2, ne=False) -%} +{%- if kind|is_reference_kind -%} +{%- if kind|is_array_kind -%} +{%- if kind.kind|is_reference_kind -%} +{%- if ne %}!{%- endif %}java.util.Arrays.deepEquals({{v1}}, {{v2}}) +{%- else -%} +{%- if ne %}!{%- endif %}java.util.Arrays.equals({{v1}}, {{v2}}) +{%- endif -%} +{%- else -%} +{%- if ne %}!{%- endif %}org.chromium.mojo.bindings.BindingsHelper.equals({{v1}}, {{v2}}) +{%- endif -%} +{%- else -%} +{{v1}} {%- if ne %}!={%- else %}=={%- endif %} {{v2}} +{%- endif -%} +{%- endmacro -%} + +{%- macro hash(kind, v) -%} +{%- if kind|is_array_kind -%} +{%- if kind.kind|is_reference_kind -%} +java.util.Arrays.deepHashCode({{v}}) +{%- else -%} +java.util.Arrays.hashCode({{v}}) +{%- endif -%} +{%- else -%} +org.chromium.mojo.bindings.BindingsHelper.hashCode({{v}}) +{%- endif -%} +{%- endmacro -%} + +{%- macro array_element_size(kind) -%} +{%- if kind|is_union_kind %} +org.chromium.mojo.bindings.BindingsHelper.UNION_SIZE +{%- else -%} +org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE +{%- endif -%} +{%- endmacro -%} + +{%- macro encode(variable, kind, offset, bit, level=0, check_for_null=True) %} +{%- if kind|is_pointer_array_kind or kind|is_union_array_kind %} +{%- set sub_kind = kind.kind %} +{%- if check_for_null %} +if ({{variable}} == null) { + encoder{{level}}.encodeNullPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}}); +} else { +{%- else %} +{ +{%- endif %} +{%- if kind|is_pointer_array_kind %} +{%- set encodePointer = 'encodePointerArray' %} +{%- else %} +{%- set encodePointer = 'encodeUnionArray' %} +{%- endif %} + org.chromium.mojo.bindings.Encoder encoder{{level + 1}} = encoder{{level}}.{{encodePointer}}({{variable}}.length, {{offset}}, {{kind|array_expected_length}}); + for (int i{{level}} = 0; i{{level}} < {{variable}}.length; ++i{{level}}) { + {{encode(variable~'[i'~level~']', sub_kind, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + ' ~ array_element_size(sub_kind) ~ ' * i'~level, 0, level+1)|indent(8)}} + } +} +{%- elif kind|is_map_kind %} +if ({{variable}} == null) { + encoder{{level}}.encodeNullPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}}); +} else { + org.chromium.mojo.bindings.Encoder encoder{{level + 1}} = encoder{{level}}.encoderForMap({{offset}}); + int size{{level}} = {{variable}}.size(); + {{kind.key_kind|java_type}}[] keys{{level}} = {{kind.key_kind|array|new_array('size'~level)}}; + {{kind.value_kind|java_type}}[] values{{level}} = {{kind.value_kind|array|new_array('size'~level)}}; + int index{{level}} = 0; + for (java.util.Map.Entry<{{kind.key_kind|java_type(true)}}, {{kind.value_kind|java_type(true)}}> entry{{level}} : {{variable}}.entrySet()) { + keys{{level}}[index{{level}}] = entry{{level}}.getKey(); + values{{level}}[index{{level}}] = entry{{level}}.getValue(); + ++index{{level}}; + } + {{encode('keys'~level, kind.key_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0, level+1, False)|indent(4)}} + {{encode('values'~level, kind.value_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE', 0, level+1, False)|indent(4)}} +} +{%- else %} +encoder{{level}}.{{kind|encode_method(variable, offset, bit)}}; +{%- endif %} +{%- endmacro %} + +{%- macro decode(variable, kind, offset, bit, level=0) %} +{%- if kind|is_struct_kind or + kind|is_pointer_array_kind or + kind|is_union_array_kind or + kind|is_map_kind %} +org.chromium.mojo.bindings.Decoder decoder{{level+1}} = decoder{{level}}.readPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}}); +{%- if kind|is_struct_kind %} +{{variable}} = {{kind|java_type}}.decode(decoder{{level+1}}); +{%- else %}{# kind|is_pointer_array_kind or is_map_kind #} +{%- if kind|is_nullable_kind %} +if (decoder{{level+1}} == null) { + {{variable}} = null; +} else { +{%- else %} +{ +{%- endif %} +{%- if kind|is_map_kind %} + decoder{{level+1}}.readDataHeaderForMap(); + {{kind.key_kind|java_type}}[] keys{{level}}; + {{kind.value_kind|java_type}}[] values{{level}}; + { + {{decode('keys'~level, kind.key_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0, level+1)|indent(8)}} + } + { + {{decode('values'~level, kind.value_kind|array('keys'~level~'.length'), 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE', 0, level+1)|indent(8)}} + } + {{variable}} = new java.util.HashMap<{{kind.key_kind|java_type(true)}}, {{kind.value_kind|java_type(true)}}>(); + for (int index{{level}} = 0; index{{level}} < keys{{level}}.length; ++index{{level}}) { + {{variable}}.put(keys{{level}}[index{{level}}], values{{level}}[index{{level}}]); + } +{%- else %} + org.chromium.mojo.bindings.DataHeader si{{level+1}} = decoder{{level+1}}.readDataHeaderForPointerArray({{kind|array_expected_length}}); + {{variable}} = {{kind|new_array('si'~(level+1)~'.elementsOrVersion')}}; + for (int i{{level+1}} = 0; i{{level+1}} < si{{level+1}}.elementsOrVersion; ++i{{level+1}}) { + {{decode(variable~'[i'~(level+1)~']', kind.kind, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + ' ~ array_element_size(kind.kind) ~' * i'~(level+1), 0, level+1)|indent(8)}} + } +{%- endif %} +} +{%- endif %} +{%- elif kind|is_union_kind %} +{{variable}} = {{kind|java_type}}.decode(decoder{{level}}, {{offset}}); +{%- else %} +{{variable}} = decoder{{level}}.{{kind|decode_method(offset, bit)}}; +{%- if kind|is_array_kind and kind.kind|is_enum_kind %} +{%- if kind|is_nullable_kind %} +if ({{variable}} != null) { +{%- else %} +{ +{%- endif %} + for (int i{{level}} = 0; i{{level}} < {{variable}}.length; ++i{{level}}) { + {{kind.kind|java_class_for_enum}}.validate({{variable}}[i{{level}}]); + } +} +{%- elif kind|is_enum_kind %} + {{kind|java_class_for_enum}}.validate({{variable}}); +{%- endif %} +{%- endif %} +{%- endmacro %} + +{%- macro struct_def(struct, inner_class=False) %} +{{'static' if inner_class else 'public'}} final class {{struct|name}} extends org.chromium.mojo.bindings.Struct { + + private static final int STRUCT_SIZE = {{struct.versions[-1].num_bytes}}; + private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] { +{%- for version in struct.versions -%} + new org.chromium.mojo.bindings.DataHeader({{version.num_bytes}}, {{version.version}}){%- if not loop.last %}, {%- endif -%} +{%- endfor -%} + }; + private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[{{struct.versions|length - 1}}]; +{%- for constant in struct.constants %} + + {{constant_def(constant)|indent(4)}} +{%- endfor %} +{%- for enum in struct.enums %} + + {{enum_def(enum, false)|indent(4)}} +{%- endfor %} +{%- if struct.fields %} + +{%- for field in struct.fields %} + public {{field.kind|java_type}} {{field|name}}; +{%- endfor %} +{%- endif %} + + private {{struct|name}}(int version) { + super(STRUCT_SIZE, version); +{%- for field in struct.fields %} +{%- if field.default %} + {{field|name}} = {{field|default_value}}; +{%- elif field.kind|is_any_handle_kind %} + {{field|name}} = org.chromium.mojo.system.InvalidHandle.INSTANCE; +{%- endif %} +{%- endfor %} + } + + public {{struct|name}}() { + this({{struct.versions[-1].version}}); + } + + public static {{struct|name}} deserialize(org.chromium.mojo.bindings.Message message) { + return decode(new org.chromium.mojo.bindings.Decoder(message)); + } + + /** + * Similar to the method above, but deserializes from a |ByteBuffer| instance. + * + * @throws org.chromium.mojo.bindings.DeserializationException on deserialization failure. + */ + public static {{struct|name}} deserialize(java.nio.ByteBuffer data) { + if (data == null) + return null; + + return deserialize(new org.chromium.mojo.bindings.Message( + data, new java.util.ArrayList<org.chromium.mojo.system.Handle>())); + } + + @SuppressWarnings("unchecked") + public static {{struct|name}} decode(org.chromium.mojo.bindings.Decoder decoder0) { + if (decoder0 == null) { + return null; + } + decoder0.increaseStackDepth(); + {{struct|name}} result; + try { + org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY); + result = new {{struct|name}}(mainDataHeader.elementsOrVersion); +{%- for byte in struct.bytes %} +{%- for packed_field in byte.packed_fields %} + if (mainDataHeader.elementsOrVersion >= {{packed_field.min_version}}) { + {{decode('result.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(16)}} + } +{%- endfor %} +{%- endfor %} + } finally { + decoder0.decreaseStackDepth(); + } + return result; + } + + @SuppressWarnings("unchecked") + @Override + protected final void encode(org.chromium.mojo.bindings.Encoder encoder) { +{%- if not struct.bytes %} + encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO); +{%- else %} + org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO); +{%- endif %} +{%- for byte in struct.bytes %} +{%- for packed_field in byte.packed_fields %} + {{encode(packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(8)}} +{%- endfor %} +{%- endfor %} + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object object) { + if (object == this) + return true; + if (object == null) + return false; + if (getClass() != object.getClass()) + return false; +{%- if struct.fields|length %} + {{struct|name}} other = ({{struct|name}}) object; +{%- for field in struct.fields %} + if ({{equality(field.kind, 'this.'~field|name, 'other.'~field|name, True)}}) + return false; +{%- endfor %} +{%- endif %} + return true; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = prime + getClass().hashCode(); +{%- for field in struct.fields %} + result = prime * result + {{hash(field.kind, field|name)}}; +{%- endfor %} + return result; + } +} +{%- endmacro %} + + +{%- macro union_def(union) %} +public final class {{union|name}} extends org.chromium.mojo.bindings.Union { + + public static final class Tag { +{%- for field in union.fields %} + public static final int {{field|ucc}} = {{loop.index0}}; +{%- endfor %} + }; + + private int mTag_ = -1; +{%- for field in union.fields %} + private {{field.kind|java_type}} m{{field|ucc}}; +{%- endfor %} + + public int which() { + return mTag_; + } + + public boolean isUnknown() { + return mTag_ == -1; + } +{%- for field in union.fields %} + + // TODO(rockot): Fix the findbugs error and remove this suppression. + // See http://crbug.com/570386. + @SuppressFBWarnings("EI_EXPOSE_REP2") + public void set{{field|ucc}}({{field.kind|java_type}} {{field|name}}) { + mTag_ = Tag.{{field|ucc}}; + m{{field|ucc}} = {{field|name}}; + } + + // TODO(rockot): Fix the findbugs error and remove this suppression. + // See http://crbug.com/570386. + @SuppressFBWarnings("EI_EXPOSE_REP") + public {{field.kind|java_type}} get{{field|ucc}}() { + assert mTag_ == Tag.{{field|ucc}}; + return m{{field|ucc}}; + } +{%- endfor %} + + + @Override + protected final void encode(org.chromium.mojo.bindings.Encoder encoder0, int offset) { + encoder0.encode(org.chromium.mojo.bindings.BindingsHelper.UNION_SIZE, offset); + encoder0.encode(mTag_, offset + 4); + switch (mTag_) { +{%- for field in union.fields %} + case Tag.{{field|ucc}}: { +{%- if field.kind|is_union_kind %} + if (m{{field|ucc}} == null) { + encoder0.encodeNullPointer(offset + 8, {{field.kind|is_nullable_kind|java_true_false}}); + } else { + m{{field|ucc}}.encode(encoder0.encoderForUnionPointer(offset + 8), 0); + } +{%- else %} + {{encode('m' ~ field|ucc, field.kind, 'offset + 8', 0)|indent(16)}} +{%- endif %} + break; + } +{%- endfor %} + default: { + break; + } + } + } + + public static {{union|name}} deserialize(org.chromium.mojo.bindings.Message message) { + return decode(new org.chromium.mojo.bindings.Decoder(message).decoderForSerializedUnion(), 0); + } + + public static final {{union|name}} decode(org.chromium.mojo.bindings.Decoder decoder0, int offset) { + org.chromium.mojo.bindings.DataHeader dataHeader = decoder0.readDataHeaderForUnion(offset); + if (dataHeader.size == 0) { + return null; + } + {{union|name}} result = new {{union|name}}(); + switch (dataHeader.elementsOrVersion) { +{%- for field in union.fields %} + case Tag.{{field|ucc}}: { +{%- if field.kind|is_union_kind %} + org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(offset + org.chromium.mojo.bindings.DataHeader.HEADER_SIZE, {{field.kind|is_nullable_kind|java_true_false}}); + if (decoder1 != null) { + result.m{{field|ucc}} = {{field.kind|name}}.decode(decoder1, 0); + } +{%- else %} + {{decode('result.m'~field|ucc, field.kind, 'offset + org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0)|indent(16)}} +{%- endif %} + result.mTag_ = Tag.{{field|ucc}}; + break; + } +{%- endfor %} + default: { + break; + } + } + return result; + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object object) { + if (object == this) + return true; + if (object == null) + return false; + if (getClass() != object.getClass()) + return false; + {{union|name}} other = ({{union|name}}) object; + if (mTag_ != other.mTag_) + return false; + switch (mTag_) { +{%- for field in union.fields %} + case Tag.{{field|ucc}}: + return {{equality(field.kind, 'm'~field|ucc, 'other.m'~field|ucc)}}; +{%- endfor %} + default: + break; + } + return false; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = prime + getClass().hashCode(); + result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(mTag_); + switch (mTag_) { +{%- for field in union.fields %} + case Tag.{{field|ucc}}: { + result = prime * result + {{hash(field.kind, 'm'~field|ucc)}}; + break; + } +{%- endfor %} + default: { + break; + } + } + return result; + } +} +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl new file mode 100644 index 0000000000..7096a18747 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl @@ -0,0 +1,4 @@ +{% from "enum_definition.tmpl" import enum_def %} +{% include "header.java.tmpl" %} + +{{enum_def(enum, true)}} diff --git a/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl new file mode 100644 index 0000000000..d37288ac5d --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl @@ -0,0 +1,42 @@ +{%- macro enum_value(enum, field, index) -%} +{%- if field.value -%} +(int) ({{field.value|expression_to_text('i32')}}) +{%- elif index == 0 -%} +0 +{%- else -%} +{{enum.fields[index - 1]|name}} + 1 +{%- endif -%} +{%- endmacro -%} + +{%- macro enum_def(enum, top_level) -%} +public {{ 'static ' if not top_level }}final class {{enum|name}} { + +{% for field in enum.fields %} + public static final int {{field|name}} = {{enum_value(enum, field, loop.index0)}}; +{% endfor %} + + private static final boolean IS_EXTENSIBLE = {% if enum.extensible %}true{% else %}false{% endif %}; + + public static boolean isKnownValue(int value) { +{%- if enum.fields %} + switch (value) { +{%- for enum_field in enum.fields|groupby('numeric_value') %} + case {{enum_field[0]}}: +{%- endfor %} + return true; + } +{%- endif %} + return false; + } + + public static void validate(int value) { + if (IS_EXTENSIBLE || isKnownValue(value)) + return; + + throw new DeserializationException("Invalid enum value."); + } + + private {{enum|name}}() {} + +} +{%- endmacro -%} diff --git a/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl new file mode 100644 index 0000000000..1d67890452 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl @@ -0,0 +1,14 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is autogenerated by: +// mojo/public/tools/bindings/mojom_bindings_generator.py +// For: +// {{module.path}} +// + +package {{package}}; + +import org.chromium.base.annotations.SuppressFBWarnings; +import org.chromium.mojo.bindings.DeserializationException;
\ No newline at end of file diff --git a/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl new file mode 100644 index 0000000000..a13be3ef60 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl @@ -0,0 +1,4 @@ +{% from "interface_definition.tmpl" import interface_def %} +{% include "header.java.tmpl" %} + +{{ interface_def(interface) }} diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl new file mode 100644 index 0000000000..a723f8c393 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl @@ -0,0 +1,297 @@ +{% from "constant_definition.tmpl" import constant_def %} +{% from "enum_definition.tmpl" import enum_def %} +{% from "data_types_definition.tmpl" import struct_def %} + +{%- macro declare_params(parameters, boxed=false) %} +{%- for param in parameters -%} +{{param.kind|java_type(boxed)}} {{param|name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{% macro declare_request_params(method) %} +{{declare_params(method.parameters)}} +{%- if method.response_parameters != None -%} +{%- if method.parameters %}, {% endif %} +{{method|interface_response_name}} callback +{%- endif -%} +{% endmacro %} + +{%- macro declare_callback(method) -%} + +interface {{method|interface_response_name}} extends org.chromium.mojo.bindings.Callbacks.Callback{{method.response_parameters|length}}{% if method.response_parameters %}< +{%- for param in method.response_parameters -%} +{{param.kind|java_type(True)}} +{%- if not loop.last %}, {% endif %} +{%- endfor -%} +>{% endif %} { } +{%- endmacro -%} + +{%- macro run_callback(variable, parameters) -%} +{%- if parameters -%} +{%- for param in parameters -%} +{{variable}}.{{param|name}} +{%- if not loop.last %}, {% endif %} +{%- endfor -%} +{%- endif -%} +{%- endmacro -%} + +{%- macro flags_for_method(method, is_request) -%} +{{flags(method.response_parameters != None, is_request)}} +{%- endmacro -%} + +{%- macro flags(use_response_flag, is_request) -%} +{%- if not use_response_flag -%} +org.chromium.mojo.bindings.MessageHeader.NO_FLAG +{%- elif is_request: -%} +org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG +{%- else -%} +org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG +{%- endif -%} +{%- endmacro -%} + +{%- macro manager_class(interface, fully_qualified=False) -%} +{% if fully_qualified %}org.chromium.mojo.bindings.Interface.{% endif %}Manager<{{interface|name}}, {{interface|name}}.Proxy> +{%- endmacro -%} + +{%- macro manager_def(interface) -%} +public static final {{manager_class(interface, True)}} MANAGER = + new {{manager_class(interface, True)}}() { + + public String getName() { + return "{{namespace|replace(".","::")}}::{{interface.name}}"; + } + + public int getVersion() { + return {{interface.version}}; + } + + public Proxy buildProxy(org.chromium.mojo.system.Core core, + org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) { + return new Proxy(core, messageReceiver); + } + + public Stub buildStub(org.chromium.mojo.system.Core core, {{interface|name}} impl) { + return new Stub(core, impl); + } + + public {{interface|name}}[] buildArray(int size) { + return new {{interface|name}}[size]; + } +}; +{%- endmacro -%} + +{%- macro accept_body(interface, with_response) -%} +try { + org.chromium.mojo.bindings.ServiceMessage messageWithHeader = + message.asServiceMessage(); + org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader(); + if (!header.validateHeader({{flags(with_response, True)}})) { + return false; + } + switch(header.getType()) { +{% if with_response %} + case org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants.RUN_MESSAGE_ID: + return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRun( + getCore(), {{interface|name}}_Internal.MANAGER, messageWithHeader, receiver); +{% else %} + case org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID: + return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRunOrClosePipe( + {{interface|name}}_Internal.MANAGER, messageWithHeader); +{% endif %} +{% for method in interface.methods %} +{% if (with_response and method.response_parameters != None) or + (not with_response and method.response_parameters == None) %} +{% set request_struct = method.param_struct %} +{% if with_response %} +{% set response_struct = method.response_param_struct %} +{% endif %} + case {{method|method_ordinal_name}}: { +{% if method.parameters %} + {{request_struct|name}} data = + {{request_struct|name}}.deserialize(messageWithHeader.getPayload()); +{% else %} + {{request_struct|name}}.deserialize(messageWithHeader.getPayload()); +{% endif %} + try { + getImpl().{{method|name}}({{run_callback('data', method.parameters)}}{% if with_response %}{% if method.parameters %}, {% endif %}new {{response_struct|name}}ProxyToResponder(getCore(), receiver, header.getRequestId()){% endif %}); + } catch (RuntimeException e) { + // TODO(lhchavez): Remove this hack. See b/28814913 for details. + android.util.Log.wtf("{{namespace}}.{{interface.name}}", "Uncaught runtime exception", e); + } + return true; + } +{% endif %} +{% endfor %} + default: + return false; + } +} catch (org.chromium.mojo.bindings.DeserializationException e) { + System.err.println(e.toString()); + return false; +} +{%- endmacro -%} + +{% macro interface_def(interface) %} +public interface {{interface|name}} extends org.chromium.mojo.bindings.Interface { +{% for constant in interface.constants %} + + {{constant_def(constant)|indent(4)}} +{% endfor %} +{% for enum in interface.enums %} + + {{enum_def(enum, false)|indent(4)}} +{% endfor %} + + public interface Proxy extends {{interface|name}}, org.chromium.mojo.bindings.Interface.Proxy { + } + + {{manager_class(interface)}} MANAGER = {{interface|name}}_Internal.MANAGER; +{% for method in interface.methods %} + + void {{method|name}}({{declare_request_params(method)}}); +{% if method.response_parameters != None %} + {{declare_callback(method)|indent(4)}} +{% endif %} +{% endfor %} +} +{% endmacro %} + +{% macro interface_internal_def(interface) %} +class {{interface|name}}_Internal { + + {{manager_def(interface)|indent(4)}} + +{% for method in interface.methods %} + private static final int {{method|method_ordinal_name}} = {{method.ordinal}}; +{% endfor %} + + static final class Proxy extends org.chromium.mojo.bindings.Interface.AbstractProxy implements {{interface|name}}.Proxy { + + Proxy(org.chromium.mojo.system.Core core, + org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) { + super(core, messageReceiver); + } +{% for method in interface.methods %} + + @Override + public void {{method|name}}({{declare_request_params(method)}}) { +{% set request_struct = method.param_struct %} + {{request_struct|name}} _message = new {{request_struct|name}}(); +{% for param in method.parameters %} + _message.{{param|name}} = {{param|name}}; +{% endfor %} +{% if method.response_parameters != None %} + getProxyHandler().getMessageReceiver().acceptWithResponder( + _message.serializeWithHeader( + getProxyHandler().getCore(), + new org.chromium.mojo.bindings.MessageHeader( + {{method|method_ordinal_name}}, + {{flags_for_method(method, True)}}, + 0)), + new {{method.response_param_struct|name}}ForwardToCallback(callback)); +{% else %} + getProxyHandler().getMessageReceiver().accept( + _message.serializeWithHeader( + getProxyHandler().getCore(), + new org.chromium.mojo.bindings.MessageHeader({{method|method_ordinal_name}}))); +{% endif %} + } +{% endfor %} + + } + + static final class Stub extends org.chromium.mojo.bindings.Interface.Stub<{{interface|name}}> { + + Stub(org.chromium.mojo.system.Core core, {{interface|name}} impl) { + super(core, impl); + } + + @Override + public boolean accept(org.chromium.mojo.bindings.Message message) { + {{accept_body(interface, False)|indent(12)}} + } + + @Override + public boolean acceptWithResponder(org.chromium.mojo.bindings.Message message, org.chromium.mojo.bindings.MessageReceiver receiver) { + {{accept_body(interface, True)|indent(12)}} + } + } +{% for method in interface.methods %} + + {{ struct_def(method.param_struct, True)|indent(4) }} +{% if method.response_parameters != None %} +{% set response_struct = method.response_param_struct %} + + {{ struct_def(response_struct, True)|indent(4) }} + + static class {{response_struct|name}}ForwardToCallback extends org.chromium.mojo.bindings.SideEffectFreeCloseable + implements org.chromium.mojo.bindings.MessageReceiver { + private final {{interface|name}}.{{method|interface_response_name}} mCallback; + + {{response_struct|name}}ForwardToCallback({{interface|name}}.{{method|interface_response_name}} callback) { + this.mCallback = callback; + } + + @Override + public boolean accept(org.chromium.mojo.bindings.Message message) { + try { + org.chromium.mojo.bindings.ServiceMessage messageWithHeader = + message.asServiceMessage(); + org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader(); + if (!header.validateHeader({{method|method_ordinal_name}}, + {{flags_for_method(method, False)}})) { + return false; + } +{% if method.response_parameters|length %} + {{response_struct|name}} response = {{response_struct|name}}.deserialize(messageWithHeader.getPayload()); +{% endif %} + try { + mCallback.call({{run_callback('response', method.response_parameters)}}); + } catch (RuntimeException e) { + // TODO(lhchavez): Remove this hack. See b/28814913 for details. + android.util.Log.wtf("{{namespace}}.{{interface.name}}", "Uncaught runtime exception", e); + } + return true; + } catch (org.chromium.mojo.bindings.DeserializationException e) { + return false; + } + } + } + + static class {{response_struct|name}}ProxyToResponder implements {{interface|name}}.{{method|interface_response_name}} { + + private final org.chromium.mojo.system.Core mCore; + private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver; + private final long mRequestId; + + {{response_struct|name}}ProxyToResponder( + org.chromium.mojo.system.Core core, + org.chromium.mojo.bindings.MessageReceiver messageReceiver, + long requestId) { + mCore = core; + mMessageReceiver = messageReceiver; + mRequestId = requestId; + } + + @Override + public void call({{declare_params(method.response_parameters, true)}}) { + {{response_struct|name}} _response = new {{response_struct|name}}(); +{% for param in method.response_parameters %} + _response.{{param|name}} = {{param|name}}; +{% endfor %} + org.chromium.mojo.bindings.ServiceMessage _message = + _response.serializeWithHeader( + mCore, + new org.chromium.mojo.bindings.MessageHeader( + {{method|method_ordinal_name}}, + {{flags_for_method(method, False)}}, + mRequestId)); + mMessageReceiver.accept(_message); + } + } +{% endif %} +{% endfor %} + +} +{% endmacro %} diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl new file mode 100644 index 0000000000..50c7a7bf94 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl @@ -0,0 +1,4 @@ +{% from "interface_definition.tmpl" import interface_internal_def %} +{% include "header.java.tmpl" %} + +{{ interface_internal_def(interface) }} diff --git a/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl new file mode 100644 index 0000000000..e28ba19c8b --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl @@ -0,0 +1,4 @@ +{% from "data_types_definition.tmpl" import struct_def %} +{% include "header.java.tmpl" %} + +{{ struct_def(struct) }} diff --git a/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl new file mode 100644 index 0000000000..b8cd4aa2e0 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl @@ -0,0 +1,4 @@ +{% from "data_types_definition.tmpl" import union_def %} +{% include "header.java.tmpl" %} + +{{ union_def(union) }} diff --git a/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl new file mode 100644 index 0000000000..019b1b6383 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl @@ -0,0 +1,33 @@ +{%- macro enum_def(enum_name, enum) -%} + {{enum_name}} = {}; +{%- set prev_enum = 0 %} +{%- for field in enum.fields %} +{%- if field.value %} + {{enum_name}}.{{field.name}} = {{field.value|expression_to_text}}; +{%- elif loop.first %} + {{enum_name}}.{{field.name}} = 0; +{%- else %} + {{enum_name}}.{{field.name}} = {{enum_name}}.{{enum.fields[loop.index0 - 1].name}} + 1; +{%- endif %} +{%- endfor %} + + {{enum_name}}.isKnownEnumValue = function(value) { +{%- if enum.fields %} + switch (value) { +{%- for enum_field in enum.fields|groupby('numeric_value') %} + case {{enum_field[0]}}: +{%- endfor %} + return true; + } +{%- endif %} + return false; + }; + + {{enum_name}}.validate = function(enumValue) { + var isExtensible = {% if enum.extensible %}true{% else %}false{% endif %}; + if (isExtensible || this.isKnownEnumValue(enumValue)) + return validator.validationError.NONE; + + return validator.validationError.UNKNOWN_ENUM_VALUE; + }; +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl new file mode 100644 index 0000000000..11e319c1f7 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl @@ -0,0 +1,198 @@ +{%- for method in interface.methods %} + var k{{interface.name}}_{{method.name}}_Name = {{method.ordinal}}; +{%- endfor %} + + function {{interface.name}}Ptr(handleOrPtrInfo) { + this.ptr = new bindings.InterfacePtrController({{interface.name}}, + handleOrPtrInfo); + } + + function {{interface.name}}Proxy(receiver) { + this.receiver_ = receiver; + } + +{%- for method in interface.methods %} + {{interface.name}}Ptr.prototype.{{method.name|stylize_method}} = function() { + return {{interface.name}}Proxy.prototype.{{method.name|stylize_method}} + .apply(this.ptr.getProxy(), arguments); + }; + + {{interface.name}}Proxy.prototype.{{method.name|stylize_method}} = function( +{%- for parameter in method.parameters -%} +{{parameter.name}}{% if not loop.last %}, {% endif %} +{%- endfor -%} +) { + var params = new {{interface.name}}_{{method.name}}_Params(); +{%- for parameter in method.parameters %} + params.{{parameter.name}} = {{parameter.name}}; +{%- endfor %} + +{%- if method.response_parameters == None %} + var builder = new codec.MessageBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_Params.encodedSize)); + builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params); + var message = builder.finish(); + this.receiver_.accept(message); +{%- else %} + return new Promise(function(resolve, reject) { + var builder = new codec.MessageWithRequestIDBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_Params.encodedSize), + codec.kMessageExpectsResponse, 0); + builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params); + var message = builder.finish(); + this.receiver_.acceptAndExpectResponse(message).then(function(message) { + var reader = new codec.MessageReader(message); + var responseParams = + reader.decodeStruct({{interface.name}}_{{method.name}}_ResponseParams); + resolve(responseParams); + }).catch(function(result) { + reject(Error("Connection error: " + result)); + }); + }.bind(this)); +{%- endif %} + }; +{%- endfor %} + + function {{interface.name}}Stub(delegate) { + this.delegate_ = delegate; + } + +{%- for method in interface.methods %} +{%- set js_method_name = method.name|stylize_method %} + {{interface.name}}Stub.prototype.{{js_method_name}} = function({{method.parameters|map(attribute='name')|join(', ')}}) { + return this.delegate_ && this.delegate_.{{js_method_name}} && this.delegate_.{{js_method_name}}({{method.parameters|map(attribute='name')|join(', ')}}); + } +{%- endfor %} + + {{interface.name}}Stub.prototype.accept = function(message) { + var reader = new codec.MessageReader(message); + switch (reader.messageName) { +{%- for method in interface.methods %} +{%- if method.response_parameters == None %} + case k{{interface.name}}_{{method.name}}_Name: + var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params); + this.{{method.name|stylize_method}}( +{%- for parameter in method.parameters -%} + params.{{parameter.name}}{% if not loop.last %}, {% endif %} +{%- endfor %}); + return true; +{%- endif %} +{%- endfor %} + default: + return false; + } + }; + + {{interface.name}}Stub.prototype.acceptWithResponder = + function(message, responder) { + var reader = new codec.MessageReader(message); + switch (reader.messageName) { +{%- for method in interface.methods %} +{%- if method.response_parameters != None %} + case k{{interface.name}}_{{method.name}}_Name: + var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params); + this.{{method.name|stylize_method}}( +{%- for parameter in method.parameters -%} +params.{{parameter.name}}{% if not loop.last %}, {% endif -%} +{%- endfor %}).then(function(response) { + var responseParams = + new {{interface.name}}_{{method.name}}_ResponseParams(); +{%- for parameter in method.response_parameters %} + responseParams.{{parameter.name}} = response.{{parameter.name}}; +{%- endfor %} + var builder = new codec.MessageWithRequestIDBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_ResponseParams.encodedSize), + codec.kMessageIsResponse, reader.requestID); + builder.encodeStruct({{interface.name}}_{{method.name}}_ResponseParams, + responseParams); + var message = builder.finish(); + responder.accept(message); + }); + return true; +{%- endif %} +{%- endfor %} + default: + return false; + } + }; + +{#--- Validation #} + + function validate{{interface.name}}Request(messageValidator) { +{%- if not(interface.methods) %} + return validator.validationError.NONE; +{%- else %} + var message = messageValidator.message; + var paramsClass = null; + switch (message.getName()) { +{%- for method in interface.methods %} + case k{{interface.name}}_{{method.name}}_Name: +{%- if method.response_parameters == None %} + if (!message.expectsResponse() && !message.isResponse()) + paramsClass = {{interface.name}}_{{method.name}}_Params; +{%- else %} + if (message.expectsResponse()) + paramsClass = {{interface.name}}_{{method.name}}_Params; +{%- endif %} + break; +{%- endfor %} + } + if (paramsClass === null) + return validator.validationError.NONE; + return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes()); +{%- endif %} + } + + function validate{{interface.name}}Response(messageValidator) { +{%- if not(interface|has_callbacks) %} + return validator.validationError.NONE; +{%- else %} + var message = messageValidator.message; + var paramsClass = null; + switch (message.getName()) { +{%- for method in interface.methods %} +{%- if method.response_parameters != None %} + case k{{interface.name}}_{{method.name}}_Name: + if (message.isResponse()) + paramsClass = {{interface.name}}_{{method.name}}_ResponseParams; + break; +{%- endif %} +{%- endfor %} + } + if (paramsClass === null) + return validator.validationError.NONE; + return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes()); +{%- endif %} + } + + var {{interface.name}} = { + name: '{{namespace|replace(".","::")}}::{{interface.name}}', + kVersion: {{interface.version}}, + ptrClass: {{interface.name}}Ptr, + proxyClass: {{interface.name}}Proxy, + stubClass: {{interface.name}}Stub, + validateRequest: validate{{interface.name}}Request, +{%- if interface|has_callbacks %} + validateResponse: validate{{interface.name}}Response, +{%- else %} + validateResponse: null, +{%- endif %} + }; +{#--- Interface Constants #} +{%- for constant in interface.constants %} + {{interface.name}}.{{constant.name}} = {{constant.value|expression_to_text}}, +{%- endfor %} +{#--- Interface Enums #} +{%- from "enum_definition.tmpl" import enum_def -%} +{%- for enum in interface.enums %} + {{ enum_def("%s.%s"|format(interface.name, enum.name), enum) }} +{%- endfor %} + {{interface.name}}Stub.prototype.validator = validate{{interface.name}}Request; +{%- if interface|has_callbacks %} + {{interface.name}}Proxy.prototype.validator = validate{{interface.name}}Response; +{%- else %} + {{interface.name}}Proxy.prototype.validator = null; +{%- endif %} diff --git a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl new file mode 100644 index 0000000000..3637b196ac --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl @@ -0,0 +1,70 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +{%- if use_new_js_bindings %} + +'use strict'; + +(function() { + var mojomId = '{{module.path}}'; + if (mojo.internal.isMojomLoaded(mojomId)) { + console.warn('The following mojom is loaded multiple times: ' + mojomId); + return; + } + mojo.internal.markMojomLoaded(mojomId); + + // TODO(yzshen): Define these aliases to minimize the differences between the + // old/new modes. Remove them when the old mode goes away. + var bindings = mojo; + var codec = mojo.internal; + var validator = mojo.internal; + +{%- for import in imports %} + var {{import.unique_name}} = + mojo.internal.exposeNamespace('{{import.module.namespace}}'); + if (mojo.config.autoLoadMojomDeps) { + mojo.internal.loadMojomIfNecessary( + '{{import.module.path}}', + new URL( + '{{import.module|get_relative_path(module)}}.js', + document.currentScript.src).href); + } +{%- endfor %} + +{% include "module_definition.tmpl" %} +})(); + +{%- else %} + +define("{{module.path}}", [ +{%- if module.path != + "mojo/public/interfaces/bindings/interface_control_messages.mojom" and + module.path != + "mojo/public/interfaces/bindings/pipe_control_messages.mojom" %} + "mojo/public/js/bindings", +{%- endif %} + "mojo/public/js/codec", + "mojo/public/js/core", + "mojo/public/js/validator", +{%- for import in imports %} + "{{import.module.path}}", +{%- endfor %} +], function( +{%- if module.path != + "mojo/public/interfaces/bindings/interface_control_messages.mojom" and + module.path != + "mojo/public/interfaces/bindings/pipe_control_messages.mojom" -%} +bindings, {% endif -%} +codec, core, validator +{%- for import in imports -%} + , {{import.unique_name}} +{%- endfor -%} +) { + +{%- include "module_definition.tmpl" %} + + return exports; +}); + +{%- endif %} diff --git a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl new file mode 100644 index 0000000000..a119ee9480 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl @@ -0,0 +1,49 @@ +{#--- Constants #} +{%- for constant in module.constants %} + var {{constant.name}} = {{constant.value|expression_to_text}}; +{%- endfor %} + +{#--- Enums #} +{%- from "enum_definition.tmpl" import enum_def %} +{%- for enum in enums %} + var {{ enum_def(enum.name, enum) }} +{%- endfor %} + +{#--- Struct definitions #} +{% for struct in structs %} +{%- include "struct_definition.tmpl" %} +{%- endfor -%} + +{#--- Union definitions #} +{%- from "union_definition.tmpl" import union_def %} +{%- for union in unions %} +{{union_def(union)|indent(2)}} +{%- endfor %} + +{#--- Interface definitions #} +{%- for interface in interfaces -%} +{%- include "interface_definition.tmpl" %} +{%- endfor %} + +{%- if use_new_js_bindings %} + var exports = mojo.internal.exposeNamespace("{{module.namespace}}"); +{%- else %} + var exports = {}; +{%- endif %} + +{%- for constant in module.constants %} + exports.{{constant.name}} = {{constant.name}}; +{%- endfor %} +{%- for enum in enums %} + exports.{{enum.name}} = {{enum.name}}; +{%- endfor %} +{%- for struct in structs if struct.exported %} + exports.{{struct.name}} = {{struct.name}}; +{%- endfor %} +{%- for union in unions %} + exports.{{union.name}} = {{union.name}}; +{%- endfor %} +{%- for interface in interfaces %} + exports.{{interface.name}} = {{interface.name}}; + exports.{{interface.name}}Ptr = {{interface.name}}Ptr; +{%- endfor %} diff --git a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl new file mode 100644 index 0000000000..e823e46155 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl @@ -0,0 +1,126 @@ +{#--- Begin #} + function {{struct.name}}(values) { + this.initDefaults_(); + this.initFields_(values); + } + +{#--- Enums #} +{%- from "enum_definition.tmpl" import enum_def %} +{% for enum in struct.enums %} + {{enum_def("%s.%s"|format(struct.name, enum.name), enum)}} +{%- endfor %} + +{#--- Constants #} +{% for constant in struct.constants %} + {{struct.name}}.{{constant.name}} = {{constant.value|expression_to_text}}; +{%- endfor %} + +{#--- initDefaults() #} + {{struct.name}}.prototype.initDefaults_ = function() { +{%- for packed_field in struct.packed.packed_fields %} + this.{{packed_field.field.name}} = {{packed_field.field|default_value}}; +{%- endfor %} + }; + +{#--- initFields() #} + {{struct.name}}.prototype.initFields_ = function(fields) { + for(var field in fields) { + if (this.hasOwnProperty(field)) + this[field] = fields[field]; + } + }; + +{#--- Validation #} + + {{struct.name}}.validate = function(messageValidator, offset) { + var err; + err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize); + if (err !== validator.validationError.NONE) + return err; + + var kVersionSizes = [ +{%- for version in struct.versions %} + {version: {{version.version}}, numBytes: {{version.num_bytes}}}{% if not loop.last %}, + {%- endif -%} +{% endfor %} + ]; + err = messageValidator.validateStructVersion(offset, kVersionSizes); + if (err !== validator.validationError.NONE) + return err; + +{#- Before validating fields introduced at a certain version, we need to add + a version check, which makes sure we skip further validation if |object| + is from an earlier version. |last_checked_version| records the last + version that we have added such version check. #} +{%- from "validation_macros.tmpl" import validate_struct_field %} +{%- set last_checked_version = 0 %} +{%- for packed_field in struct.packed.packed_fields_in_ordinal_order %} +{%- set offset = packed_field|field_offset %} +{%- set field = packed_field.field %} +{%- set name = struct.name ~ '.' ~ field.name %} +{% if field|is_object_field or field|is_any_handle_or_interface_field or + field|is_enum_field %} +{% if packed_field.min_version > last_checked_version %} +{% set last_checked_version = packed_field.min_version %} + // version check {{name}} + if (!messageValidator.isFieldInStructVersion(offset, {{packed_field.min_version}})) + return validator.validationError.NONE; +{%- endif -%} +{{validate_struct_field(field, offset, name)|indent(4)}} +{%- endif %} +{%- endfor %} + + return validator.validationError.NONE; + }; + +{#--- Encoding and decoding #} + + {{struct.name}}.encodedSize = codec.kStructHeaderSize + {{struct.packed|payload_size}}; + + {{struct.name}}.decode = function(decoder) { + var packed; + var val = new {{struct.name}}(); + var numberOfBytes = decoder.readUint32(); + var version = decoder.readUint32(); +{%- for byte in struct.bytes %} +{%- if byte.packed_fields|length >= 1 and + byte.packed_fields[0].field|is_bool_field %} + packed = decoder.readUint8(); +{%- for packed_field in byte.packed_fields %} + val.{{packed_field.field.name}} = (packed >> {{packed_field.bit}}) & 1 ? true : false; +{%- endfor %} +{%- else %} +{%- for packed_field in byte.packed_fields %} + val.{{packed_field.field.name}} = decoder.{{packed_field.field.kind|decode_snippet}}; +{%- endfor %} +{%- endif %} +{%- if byte.is_padding %} + decoder.skip(1); +{%- endif %} +{%- endfor %} + return val; + }; + + {{struct.name}}.encode = function(encoder, val) { + var packed; + encoder.writeUint32({{struct.name}}.encodedSize); + encoder.writeUint32({{struct.versions[-1].version}}); + +{%- for byte in struct.bytes %} +{%- if byte.packed_fields|length >= 1 and + byte.packed_fields[0].field|is_bool_field %} + packed = 0; +{%- for packed_field in byte.packed_fields %} + packed |= (val.{{packed_field.field.name}} & 1) << {{packed_field.bit}} +{%- endfor %} + encoder.writeUint8(packed); +{%- else %} +{%- for packed_field in byte.packed_fields %} + encoder.{{packed_field.field.kind|encode_snippet}}val.{{packed_field.field.name}}); +{%- endfor %} +{%- endif %} +{%- if byte.is_padding %} + encoder.skip(1); +{%- endif %} +{%- endfor %} + }; diff --git a/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl new file mode 100644 index 0000000000..4823febeca --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl @@ -0,0 +1,154 @@ +{%- macro union_def(union) %} +function {{union.name}}(value) { + this.initDefault_(); + this.initValue_(value); +} + +{{tags(union)}} + +{{union.name}}.prototype.initDefault_ = function() { + this.$data = null; + this.$tag = undefined; +} + +{{union.name}}.prototype.initValue_ = function(value) { + if (value == undefined) { + return; + } + + var keys = Object.keys(value); + if (keys.length == 0) { + return; + } + + if (keys.length > 1) { + throw new TypeError("You may set only one member on a union."); + } + + var fields = [ +{%- for field in union.fields %} + "{{field.name}}", +{%- endfor %} + ]; + + if (fields.indexOf(keys[0]) < 0) { + throw new ReferenceError(keys[0] + " is not a {{union.name}} member."); + + } + + this[keys[0]] = value[keys[0]]; +} + +{%- for field in union.fields %} +Object.defineProperty({{union.name}}.prototype, "{{field.name}}", { + get: function() { + if (this.$tag != {{union.name}}.Tags.{{field.name}}) { + throw new ReferenceError( + "{{union.name}}.{{field.name}} is not currently set."); + } + return this.$data; + }, + + set: function(value) { + this.$tag = {{union.name}}.Tags.{{field.name}}; + this.$data = value; + } +}); +{%- endfor %} + +{{encode(union)|indent(2)}} + +{{decode(union)|indent(2)}} + +{{validate(union)|indent(2)}} + +{{union.name}}.encodedSize = 16; +{%- endmacro %} + +{%- macro tags(union) %} +{{union.name}}.Tags = { +{%- for field in union.fields %} + {{field.name}}: {{field.ordinal}}, +{%- endfor %} +}; +{%- endmacro %} + +{%- macro encode(union) %} +{{union.name}}.encode = function(encoder, val) { + if (val == null) { + encoder.writeUint64(0); + encoder.writeUint64(0); + return; + } + if (val.$tag == undefined) { + throw new TypeError("Cannot encode unions with an unknown member set."); + } + + encoder.writeUint32(16); + encoder.writeUint32(val.$tag); + switch (val.$tag) { +{%- for field in union.fields %} + case {{union.name}}.Tags.{{field.name}}: +{%- if field|is_bool_field %} + encoder.writeUint8(val.{{field.name}} ? 1 : 0); +{%- else %} + encoder.{{field.kind|union_encode_snippet}}val.{{field.name}}); +{%- endif %} + break; +{%- endfor %} + } + encoder.align(); +}; +{%- endmacro %} + +{%- macro decode(union) %} +{{union.name}}.decode = function(decoder) { + var size = decoder.readUint32(); + if (size == 0) { + decoder.readUint32(); + decoder.readUint64(); + return null; + } + + var result = new {{union.name}}(); + var tag = decoder.readUint32(); + switch (tag) { +{%- for field in union.fields %} + case {{union.name}}.Tags.{{field.name}}: +{%- if field|is_bool_field %} + result.{{field.name}} = decoder.readUint8() ? true : false; +{%- else %} + result.{{field.name}} = decoder.{{field.kind|union_decode_snippet}}; +{%- endif %} + break; +{%- endfor %} + } + decoder.align(); + + return result; +}; +{%- endmacro %} + +{%- from "validation_macros.tmpl" import validate_union_field %} +{%- macro validate(union) %} +{{union.name}}.validate = function(messageValidator, offset) { + var size = messageValidator.decodeUnionSize(offset); + if (size != 16) { + return validator.validationError.INVALID_UNION_SIZE; + } + + var tag = messageValidator.decodeUnionTag(offset); + var data_offset = offset + 8; + var err; + switch (tag) { +{%- for field in union.fields %} +{%- set name = union.name ~ '.' ~ field.name %} + case {{union.name}}.Tags.{{field.name}}: + {{validate_union_field(field, "data_offset", name)}} + break; +{%- endfor %} + } + + return validator.validationError.NONE; +}; +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl new file mode 100644 index 0000000000..d4e15a7859 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl @@ -0,0 +1,60 @@ +{% macro _check_err() -%} +if (err !== validator.validationError.NONE) + return err; +{%- endmacro %} + +{%- macro _validate_field(field, offset, name) %} +{%- if field|is_string_pointer_field %} +// validate {{name}} +err = messageValidator.validateStringPointer({{offset}}, {{field|validate_nullable_params}}) +{{_check_err()}} +{%- elif field|is_array_pointer_field %} +// validate {{name}} +err = messageValidator.validateArrayPointer({{offset}}, {{field|validate_array_params}}); +{{_check_err()}} +{%- elif field|is_struct_pointer_field %} +// validate {{name}} +err = messageValidator.validateStructPointer({{offset}}, {{field|validate_struct_params}}); +{{_check_err()}} +{%- elif field|is_map_pointer_field %} +// validate {{name}} +err = messageValidator.validateMapPointer({{offset}}, {{field|validate_map_params}}); +{{_check_err()}} +{%- elif field|is_interface_field %} +// validate {{name}} +err = messageValidator.validateInterface({{offset}}, {{field|validate_nullable_params}}); +{{_check_err()}} +{%- elif field|is_interface_request_field %} +// validate {{name}} +err = messageValidator.validateInterfaceRequest({{offset}}, {{field|validate_nullable_params}}) +{{_check_err()}} +{%- elif field|is_handle_field %} +// validate {{name}} +err = messageValidator.validateHandle({{offset}}, {{field|validate_nullable_params}}) +{{_check_err()}} +{%- elif field|is_enum_field %} +// validate {{name}} +err = messageValidator.validateEnum({{offset}}, {{field|validate_enum_params}}); +{{_check_err()}} +{%- endif %} +{%- endmacro %} + +{%- macro validate_struct_field(field, offset, name) %} +{%- if field|is_union_field %} +// validate {{name}} +err = messageValidator.validateUnion({{offset}}, {{field|validate_union_params}}); +{{_check_err()}} +{%- else %} +{{_validate_field(field, offset, name)}} +{%- endif %} +{%- endmacro %} + +{%- macro validate_union_field(field, offset, name) %} +{%- if field|is_union_field %} +// validate {{name}} +err = messageValidator.validateNestedUnion({{offset}}, {{field|validate_union_params}}); +{{_check_err()}} +{%- else %} +{{_validate_field(field, offset, name)}} +{%- endif %} +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py new file mode 100644 index 0000000000..38d222b136 --- /dev/null +++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py @@ -0,0 +1,818 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generates C++ source files from a mojom.Module.""" + +import mojom.generate.generator as generator +import mojom.generate.module as mojom +import mojom.generate.pack as pack +from mojom.generate.template_expander import UseJinja + + +_kind_to_cpp_type = { + mojom.BOOL: "bool", + mojom.INT8: "int8_t", + mojom.UINT8: "uint8_t", + mojom.INT16: "int16_t", + mojom.UINT16: "uint16_t", + mojom.INT32: "int32_t", + mojom.UINT32: "uint32_t", + mojom.FLOAT: "float", + mojom.INT64: "int64_t", + mojom.UINT64: "uint64_t", + mojom.DOUBLE: "double", +} + +_kind_to_cpp_literal_suffix = { + mojom.UINT8: "U", + mojom.UINT16: "U", + mojom.UINT32: "U", + mojom.FLOAT: "f", + mojom.UINT64: "ULL", +} + +# TODO(rockot): Get rid of these globals. This requires some refactoring of the +# generator library code so that filters can use the generator as context. +_current_typemap = {} +_for_blink = False +# TODO(rockot, yzshen): The variant handling is kind of a hack currently. Make +# it right. +_variant = None +_export_attribute = None + + +class _NameFormatter(object): + """A formatter for the names of kinds or values.""" + + def __init__(self, token, variant): + self._token = token + self._variant = variant + + def Format(self, separator, prefixed=False, internal=False, + include_variant=False, add_same_module_namespaces=False, + flatten_nested_kind=False): + """Formats the name according to the given configuration. + + Args: + separator: Separator between different parts of the name. + prefixed: Whether a leading separator should be added. + internal: Returns the name in the "internal" namespace. + include_variant: Whether to include variant as namespace. If |internal| is + True, then this flag is ignored and variant is not included. + add_same_module_namespaces: Includes all namespaces even if the token is + from the same module as the current mojom file. + flatten_nested_kind: It is allowed to define enums inside structs and + interfaces. If this flag is set to True, this method concatenates the + parent kind and the nested kind with '_', instead of treating the + parent kind as a scope.""" + + parts = [] + if self._ShouldIncludeNamespace(add_same_module_namespaces): + if prefixed: + parts.append("") + parts.extend(self._GetNamespace()) + if include_variant and self._variant and not internal: + parts.append(self._variant) + parts.extend(self._GetName(internal, flatten_nested_kind)) + return separator.join(parts) + + def FormatForCpp(self, add_same_module_namespaces=False, internal=False, + flatten_nested_kind=False): + return self.Format( + "::", prefixed=True, + add_same_module_namespaces=add_same_module_namespaces, + internal=internal, include_variant=True, + flatten_nested_kind=flatten_nested_kind) + + def FormatForMojom(self): + return self.Format(".", add_same_module_namespaces=True) + + def _MapKindName(self, token, internal): + if not internal: + return token.name + if (mojom.IsStructKind(token) or mojom.IsUnionKind(token) or + mojom.IsEnumKind(token)): + return token.name + "_Data" + return token.name + + def _GetName(self, internal, flatten_nested_kind): + if isinstance(self._token, mojom.EnumValue): + name_parts = _NameFormatter(self._token.enum, self._variant)._GetName( + internal, flatten_nested_kind) + name_parts.append(self._token.name) + return name_parts + + name_parts = [] + if internal: + name_parts.append("internal") + + if (flatten_nested_kind and mojom.IsEnumKind(self._token) and + self._token.parent_kind): + name = "%s_%s" % (self._token.parent_kind.name, + self._MapKindName(self._token, internal)) + name_parts.append(name) + return name_parts + + if self._token.parent_kind: + name_parts.append(self._MapKindName(self._token.parent_kind, internal)) + name_parts.append(self._MapKindName(self._token, internal)) + return name_parts + + def _ShouldIncludeNamespace(self, add_same_module_namespaces): + return add_same_module_namespaces or self._token.imported_from + + def _GetNamespace(self): + if self._token.imported_from: + return NamespaceToArray(self._token.imported_from["namespace"]) + elif hasattr(self._token, "module"): + return NamespaceToArray(self._token.module.namespace) + return [] + + +def ConstantValue(constant): + return ExpressionToText(constant.value, kind=constant.kind) + +# TODO(yzshen): Revisit the default value feature. It was designed prior to +# custom type mapping. +def DefaultValue(field): + if field.default: + if mojom.IsStructKind(field.kind): + assert field.default == "default" + if not IsTypemappedKind(field.kind): + return "%s::New()" % GetNameForKind(field.kind) + return ExpressionToText(field.default, kind=field.kind) + return "" + +def NamespaceToArray(namespace): + return namespace.split(".") if namespace else [] + +def GetNameForKind(kind, internal=False, flatten_nested_kind=False, + add_same_module_namespaces=False): + return _NameFormatter(kind, _variant).FormatForCpp( + internal=internal, flatten_nested_kind=flatten_nested_kind, + add_same_module_namespaces=add_same_module_namespaces) + +def GetQualifiedNameForKind(kind, internal=False, flatten_nested_kind=False, + include_variant=True): + return _NameFormatter( + kind, _variant if include_variant else None).FormatForCpp( + internal=internal, add_same_module_namespaces=True, + flatten_nested_kind=flatten_nested_kind) + + +def GetWtfHashFnNameForEnum(enum): + return _NameFormatter( + enum, None).Format("_", internal=True, add_same_module_namespaces=True, + flatten_nested_kind=True) + "HashFn" + + +def GetFullMojomNameForKind(kind): + return _NameFormatter(kind, _variant).FormatForMojom() + +def IsTypemappedKind(kind): + return hasattr(kind, "name") and \ + GetFullMojomNameForKind(kind) in _current_typemap + +def IsNativeOnlyKind(kind): + return (mojom.IsStructKind(kind) or mojom.IsEnumKind(kind)) and \ + kind.native_only + + +def IsHashableKind(kind): + """Check if the kind can be hashed. + + Args: + kind: {Kind} The kind to check. + + Returns: + {bool} True if a value of this kind can be hashed. + """ + checked = set() + def Check(kind): + if kind.spec in checked: + return True + checked.add(kind.spec) + if mojom.IsNullableKind(kind): + return False + elif mojom.IsStructKind(kind): + if (IsTypemappedKind(kind) and + not _current_typemap[GetFullMojomNameForKind(kind)]["hashable"]): + return False + return all(Check(field.kind) for field in kind.fields) + elif mojom.IsEnumKind(kind): + return not IsTypemappedKind(kind) or _current_typemap[ + GetFullMojomNameForKind(kind)]["hashable"] + elif mojom.IsUnionKind(kind): + return all(Check(field.kind) for field in kind.fields) + elif mojom.IsAnyHandleKind(kind): + return False + elif mojom.IsAnyInterfaceKind(kind): + return False + # TODO(tibell): Arrays and maps could be made hashable. We just don't have a + # use case yet. + elif mojom.IsArrayKind(kind): + return False + elif mojom.IsMapKind(kind): + return False + else: + return True + return Check(kind) + + +def AllEnumValues(enum): + """Return all enum values associated with an enum. + + Args: + enum: {mojom.Enum} The enum type. + + Returns: + {Set[int]} The values. + """ + return set(field.numeric_value for field in enum.fields) + + +def GetNativeTypeName(typemapped_kind): + return _current_typemap[GetFullMojomNameForKind(typemapped_kind)]["typename"] + +def GetCppPodType(kind): + return _kind_to_cpp_type[kind] + +def FormatConstantDeclaration(constant, nested=False): + if mojom.IsStringKind(constant.kind): + if nested: + return "const char %s[]" % constant.name + return "%sextern const char %s[]" % \ + ((_export_attribute + " ") if _export_attribute else "", constant.name) + return "constexpr %s %s = %s" % (GetCppPodType(constant.kind), constant.name, + ConstantValue(constant)) + +def GetCppWrapperType(kind, add_same_module_namespaces=False): + def _AddOptional(type_name): + pattern = "WTF::Optional<%s>" if _for_blink else "base::Optional<%s>" + return pattern % type_name + + if IsTypemappedKind(kind): + type_name = GetNativeTypeName(kind) + if (mojom.IsNullableKind(kind) and + not _current_typemap[GetFullMojomNameForKind(kind)][ + "nullable_is_same_type"]): + type_name = _AddOptional(type_name) + return type_name + if mojom.IsEnumKind(kind): + return GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return "%sPtr" % GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsArrayKind(kind): + pattern = "WTF::Vector<%s>" if _for_blink else "std::vector<%s>" + if mojom.IsNullableKind(kind): + pattern = _AddOptional(pattern) + return pattern % GetCppWrapperType( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsMapKind(kind): + pattern = ("WTF::HashMap<%s, %s>" if _for_blink else + "std::unordered_map<%s, %s>") + if mojom.IsNullableKind(kind): + pattern = _AddOptional(pattern) + return pattern % ( + GetCppWrapperType( + kind.key_kind, + add_same_module_namespaces=add_same_module_namespaces), + GetCppWrapperType( + kind.value_kind, + add_same_module_namespaces=add_same_module_namespaces)) + if mojom.IsInterfaceKind(kind): + return "%sPtr" % GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsInterfaceRequestKind(kind): + return "%sRequest" % GetNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsAssociatedInterfaceKind(kind): + return "%sAssociatedPtrInfo" % GetNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "%sAssociatedRequest" % GetNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsStringKind(kind): + if _for_blink: + return "WTF::String" + type_name = "std::string" + return _AddOptional(type_name) if mojom.IsNullableKind(kind) else type_name + if mojom.IsGenericHandleKind(kind): + return "mojo::ScopedHandle" + if mojom.IsDataPipeConsumerKind(kind): + return "mojo::ScopedDataPipeConsumerHandle" + if mojom.IsDataPipeProducerKind(kind): + return "mojo::ScopedDataPipeProducerHandle" + if mojom.IsMessagePipeKind(kind): + return "mojo::ScopedMessagePipeHandle" + if mojom.IsSharedBufferKind(kind): + return "mojo::ScopedSharedBufferHandle" + if not kind in _kind_to_cpp_type: + raise Exception("Unrecognized kind %s" % kind.spec) + return _kind_to_cpp_type[kind] + +def IsMoveOnlyKind(kind): + if IsTypemappedKind(kind): + if mojom.IsEnumKind(kind): + return False + return _current_typemap[GetFullMojomNameForKind(kind)]["move_only"] + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return True + if mojom.IsArrayKind(kind): + return IsMoveOnlyKind(kind.kind) + if mojom.IsMapKind(kind): + return IsMoveOnlyKind(kind.value_kind) + if mojom.IsAnyHandleOrInterfaceKind(kind): + return True + return False + +def IsCopyablePassByValue(kind): + if not IsTypemappedKind(kind): + return False + return _current_typemap[GetFullMojomNameForKind(kind)][ + "copyable_pass_by_value"] + +def ShouldPassParamByValue(kind): + return ((not mojom.IsReferenceKind(kind)) or IsMoveOnlyKind(kind) or + IsCopyablePassByValue(kind)) + +def GetCppWrapperParamType(kind): + cpp_wrapper_type = GetCppWrapperType(kind) + return (cpp_wrapper_type if ShouldPassParamByValue(kind) + else "const %s&" % cpp_wrapper_type) + +def GetCppFieldType(kind): + if mojom.IsStructKind(kind): + return ("mojo::internal::Pointer<%s>" % + GetNameForKind(kind, internal=True)) + if mojom.IsUnionKind(kind): + return "%s" % GetNameForKind(kind, internal=True) + if mojom.IsArrayKind(kind): + return ("mojo::internal::Pointer<mojo::internal::Array_Data<%s>>" % + GetCppFieldType(kind.kind)) + if mojom.IsMapKind(kind): + return ("mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" % + (GetCppFieldType(kind.key_kind), GetCppFieldType(kind.value_kind))) + if mojom.IsInterfaceKind(kind): + return "mojo::internal::Interface_Data" + if mojom.IsInterfaceRequestKind(kind): + return "mojo::internal::Handle_Data" + if mojom.IsAssociatedInterfaceKind(kind): + return "mojo::internal::AssociatedInterface_Data" + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "mojo::internal::AssociatedEndpointHandle_Data" + if mojom.IsEnumKind(kind): + return "int32_t" + if mojom.IsStringKind(kind): + return "mojo::internal::Pointer<mojo::internal::String_Data>" + if mojom.IsAnyHandleKind(kind): + return "mojo::internal::Handle_Data" + return _kind_to_cpp_type[kind] + +def GetCppUnionFieldType(kind): + if mojom.IsUnionKind(kind): + return ("mojo::internal::Pointer<%s>" % GetNameForKind(kind, internal=True)) + return GetCppFieldType(kind) + +def GetUnionGetterReturnType(kind): + if mojom.IsReferenceKind(kind): + return "%s&" % GetCppWrapperType(kind) + return GetCppWrapperType(kind) + +def GetUnionTraitGetterReturnType(kind): + """Get field type used in UnionTraits template specialization. + + The type may be qualified as UnionTraits specializations live outside the + namespace where e.g. structs are defined. + + Args: + kind: {Kind} The type of the field. + + Returns: + {str} The C++ type to use for the field. + """ + if mojom.IsReferenceKind(kind): + return "%s&" % GetCppWrapperType(kind, add_same_module_namespaces=True) + return GetCppWrapperType(kind, add_same_module_namespaces=True) + +def GetCppDataViewType(kind, qualified=False): + def _GetName(input_kind): + return _NameFormatter(input_kind, None).FormatForCpp( + add_same_module_namespaces=qualified, flatten_nested_kind=True) + + if mojom.IsEnumKind(kind): + return _GetName(kind) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return "%sDataView" % _GetName(kind) + if mojom.IsArrayKind(kind): + return "mojo::ArrayDataView<%s>" % GetCppDataViewType(kind.kind, qualified) + if mojom.IsMapKind(kind): + return ("mojo::MapDataView<%s, %s>" % ( + GetCppDataViewType(kind.key_kind, qualified), + GetCppDataViewType(kind.value_kind, qualified))) + if mojom.IsStringKind(kind): + return "mojo::StringDataView" + if mojom.IsInterfaceKind(kind): + return "%sPtrDataView" % _GetName(kind) + if mojom.IsInterfaceRequestKind(kind): + return "%sRequestDataView" % _GetName(kind.kind) + if mojom.IsAssociatedInterfaceKind(kind): + return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind) + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "%sAssociatedRequestDataView" % _GetName(kind.kind) + if mojom.IsGenericHandleKind(kind): + return "mojo::ScopedHandle" + if mojom.IsDataPipeConsumerKind(kind): + return "mojo::ScopedDataPipeConsumerHandle" + if mojom.IsDataPipeProducerKind(kind): + return "mojo::ScopedDataPipeProducerHandle" + if mojom.IsMessagePipeKind(kind): + return "mojo::ScopedMessagePipeHandle" + if mojom.IsSharedBufferKind(kind): + return "mojo::ScopedSharedBufferHandle" + return _kind_to_cpp_type[kind] + +def GetUnmappedTypeForSerializer(kind): + return GetCppDataViewType(kind, qualified=True) + +def TranslateConstants(token, kind): + if isinstance(token, mojom.NamedValue): + return GetNameForKind(token, flatten_nested_kind=True) + + if isinstance(token, mojom.BuiltinValue): + if token.value == "double.INFINITY": + return "std::numeric_limits<double>::infinity()" + if token.value == "float.INFINITY": + return "std::numeric_limits<float>::infinity()" + if token.value == "double.NEGATIVE_INFINITY": + return "-std::numeric_limits<double>::infinity()" + if token.value == "float.NEGATIVE_INFINITY": + return "-std::numeric_limits<float>::infinity()" + if token.value == "double.NAN": + return "std::numeric_limits<double>::quiet_NaN()" + if token.value == "float.NAN": + return "std::numeric_limits<float>::quiet_NaN()" + + if (kind is not None and mojom.IsFloatKind(kind)): + return token if token.isdigit() else token + "f"; + + # Per C++11, 2.14.2, the type of an integer literal is the first of the + # corresponding list in Table 6 in which its value can be represented. In this + # case, the list for decimal constants with no suffix is: + # int, long int, long long int + # The standard considers a program ill-formed if it contains an integer + # literal that cannot be represented by any of the allowed types. + # + # As it turns out, MSVC doesn't bother trying to fall back to long long int, + # so the integral constant -2147483648 causes it grief: it decides to + # represent 2147483648 as an unsigned integer, and then warns that the unary + # minus operator doesn't make sense on unsigned types. Doh! + if kind == mojom.INT32 and token == "-2147483648": + return "(-%d - 1) /* %s */" % ( + 2**31 - 1, "Workaround for MSVC bug; see https://crbug.com/445618") + + return "%s%s" % (token, _kind_to_cpp_literal_suffix.get(kind, "")) + +def ExpressionToText(value, kind=None): + return TranslateConstants(value, kind) + +def RequiresContextForDataView(kind): + for field in kind.fields: + if mojom.IsReferenceKind(field.kind): + return True + return False + +def ShouldInlineStruct(struct): + # TODO(darin): Base this on the size of the wrapper class. + if len(struct.fields) > 4: + return False + for field in struct.fields: + if mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind): + return False + return True + +def ContainsMoveOnlyMembers(struct): + for field in struct.fields: + if IsMoveOnlyKind(field.kind): + return True + return False + +def ShouldInlineUnion(union): + return not any( + mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind) + for field in union.fields) + + +class StructConstructor(object): + """Represents a constructor for a generated struct. + + Fields: + fields: {[Field]} All struct fields in order. + params: {[Field]} The fields that are passed as params. + """ + + def __init__(self, fields, params): + self._fields = fields + self._params = set(params) + + @property + def params(self): + return [field for field in self._fields if field in self._params] + + @property + def fields(self): + for field in self._fields: + yield (field, field in self._params) + + +def GetStructConstructors(struct): + """Returns a list of constructors for a struct. + + Params: + struct: {Struct} The struct to return constructors for. + + Returns: + {[StructConstructor]} A list of StructConstructors that should be generated + for |struct|. + """ + if not mojom.IsStructKind(struct): + raise TypeError + # Types that are neither copyable nor movable can't be passed to a struct + # constructor so only generate a default constructor. + if any(IsTypemappedKind(field.kind) and _current_typemap[ + GetFullMojomNameForKind(field.kind)]["non_copyable_non_movable"] + for field in struct.fields): + return [StructConstructor(struct.fields, [])] + + param_counts = [0] + for version in struct.versions: + if param_counts[-1] != version.num_fields: + param_counts.append(version.num_fields) + + ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal) + return (StructConstructor(struct.fields, ordinal_fields[:param_count]) + for param_count in param_counts) + + +def GetContainerValidateParamsCtorArgs(kind): + if mojom.IsStringKind(kind): + expected_num_elements = 0 + element_is_nullable = False + key_validate_params = "nullptr" + element_validate_params = "nullptr" + enum_validate_func = "nullptr" + elif mojom.IsMapKind(kind): + expected_num_elements = 0 + element_is_nullable = False + key_validate_params = GetNewContainerValidateParams(mojom.Array( + kind=kind.key_kind)) + element_validate_params = GetNewContainerValidateParams(mojom.Array( + kind=kind.value_kind)) + enum_validate_func = "nullptr" + else: # mojom.IsArrayKind(kind) + expected_num_elements = generator.ExpectedArraySize(kind) or 0 + element_is_nullable = mojom.IsNullableKind(kind.kind) + key_validate_params = "nullptr" + element_validate_params = GetNewContainerValidateParams(kind.kind) + if mojom.IsEnumKind(kind.kind): + enum_validate_func = ("%s::Validate" % + GetQualifiedNameForKind(kind.kind, internal=True, + flatten_nested_kind=True)) + else: + enum_validate_func = "nullptr" + + if enum_validate_func == "nullptr": + if key_validate_params == "nullptr": + return "%d, %s, %s" % (expected_num_elements, + "true" if element_is_nullable else "false", + element_validate_params) + else: + return "%s, %s" % (key_validate_params, element_validate_params) + else: + return "%d, %s" % (expected_num_elements, enum_validate_func) + +def GetNewContainerValidateParams(kind): + if (not mojom.IsArrayKind(kind) and not mojom.IsMapKind(kind) and + not mojom.IsStringKind(kind)): + return "nullptr" + + return "new mojo::internal::ContainerValidateParams(%s)" % ( + GetContainerValidateParamsCtorArgs(kind)) + +class Generator(generator.Generator): + + cpp_filters = { + "all_enum_values": AllEnumValues, + "constant_value": ConstantValue, + "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces, + "contains_move_only_members": ContainsMoveOnlyMembers, + "cpp_wrapper_param_type": GetCppWrapperParamType, + "cpp_data_view_type": GetCppDataViewType, + "cpp_field_type": GetCppFieldType, + "cpp_union_field_type": GetCppUnionFieldType, + "cpp_pod_type": GetCppPodType, + "cpp_union_getter_return_type": GetUnionGetterReturnType, + "cpp_union_trait_getter_return_type": GetUnionTraitGetterReturnType, + "cpp_wrapper_type": GetCppWrapperType, + "default_value": DefaultValue, + "expression_to_text": ExpressionToText, + "format_constant_declaration": FormatConstantDeclaration, + "get_container_validate_params_ctor_args": + GetContainerValidateParamsCtorArgs, + "get_name_for_kind": GetNameForKind, + "get_pad": pack.GetPad, + "get_qualified_name_for_kind": GetQualifiedNameForKind, + "has_callbacks": mojom.HasCallbacks, + "has_sync_methods": mojom.HasSyncMethods, + "requires_context_for_data_view": RequiresContextForDataView, + "should_inline": ShouldInlineStruct, + "should_inline_union": ShouldInlineUnion, + "is_array_kind": mojom.IsArrayKind, + "is_enum_kind": mojom.IsEnumKind, + "is_integral_kind": mojom.IsIntegralKind, + "is_native_only_kind": IsNativeOnlyKind, + "is_any_handle_kind": mojom.IsAnyHandleKind, + "is_any_interface_kind": mojom.IsAnyInterfaceKind, + "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind, + "is_associated_kind": mojom.IsAssociatedKind, + "is_hashable": IsHashableKind, + "is_map_kind": mojom.IsMapKind, + "is_nullable_kind": mojom.IsNullableKind, + "is_object_kind": mojom.IsObjectKind, + "is_reference_kind": mojom.IsReferenceKind, + "is_string_kind": mojom.IsStringKind, + "is_struct_kind": mojom.IsStructKind, + "is_typemapped_kind": IsTypemappedKind, + "is_union_kind": mojom.IsUnionKind, + "passes_associated_kinds": mojom.PassesAssociatedKinds, + "struct_constructors": GetStructConstructors, + "stylize_method": generator.StudlyCapsToCamel, + "under_to_camel": generator.UnderToCamel, + "unmapped_type_for_serializer": GetUnmappedTypeForSerializer, + "wtf_hash_fn_name_for_enum": GetWtfHashFnNameForEnum, + } + + def GetExtraTraitsHeaders(self): + extra_headers = set() + for typemap in self._GetAllUsedTypemaps(): + extra_headers.update(typemap.get("traits_headers", [])) + return sorted(extra_headers) + + def _GetAllUsedTypemaps(self): + """Returns the typemaps for types needed for serialization in this module. + + A type is needed for serialization if it is contained by a struct or union + defined in this module, is a parameter of a message in an interface in + this module or is contained within another type needed for serialization. + """ + used_typemaps = [] + seen_types = set() + def AddKind(kind): + if (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) or + mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind) or + mojom.IsAnyHandleKind(kind) or + mojom.IsInterfaceKind(kind) or + mojom.IsInterfaceRequestKind(kind) or + mojom.IsAssociatedKind(kind)): + pass + elif mojom.IsArrayKind(kind): + AddKind(kind.kind) + elif mojom.IsMapKind(kind): + AddKind(kind.key_kind) + AddKind(kind.value_kind) + else: + name = GetFullMojomNameForKind(kind) + if name in seen_types: + return + seen_types.add(name) + + typemap = _current_typemap.get(name, None) + if typemap: + used_typemaps.append(typemap) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + for field in kind.fields: + AddKind(field.kind) + + for kind in self.module.structs + self.module.unions: + for field in kind.fields: + AddKind(field.kind) + + for interface in self.module.interfaces: + for method in interface.methods: + for parameter in method.parameters + (method.response_parameters or []): + AddKind(parameter.kind) + + return used_typemaps + + def GetExtraPublicHeaders(self): + all_enums = list(self.module.enums) + for struct in self.module.structs: + all_enums.extend(struct.enums) + for interface in self.module.interfaces: + all_enums.extend(interface.enums) + + types = set(GetFullMojomNameForKind(typename) + for typename in + self.module.structs + all_enums + self.module.unions) + headers = set() + for typename, typemap in self.typemap.iteritems(): + if typename in types: + headers.update(typemap.get("public_headers", [])) + return sorted(headers) + + def _GetDirectlyUsedKinds(self): + for struct in self.module.structs + self.module.unions: + for field in struct.fields: + yield field.kind + + for interface in self.module.interfaces: + for method in interface.methods: + for param in method.parameters + (method.response_parameters or []): + yield param.kind + + def GetJinjaExports(self): + structs = self.GetStructs() + interfaces = self.GetInterfaces() + all_enums = list(self.module.enums) + for struct in structs: + all_enums.extend(struct.enums) + for interface in interfaces: + all_enums.extend(interface.enums) + + return { + "module": self.module, + "namespace": self.module.namespace, + "namespaces_as_array": NamespaceToArray(self.module.namespace), + "imports": self.module.imports, + "kinds": self.module.kinds, + "enums": self.module.enums, + "all_enums": all_enums, + "structs": structs, + "unions": self.GetUnions(), + "interfaces": interfaces, + "variant": self.variant, + "extra_traits_headers": self.GetExtraTraitsHeaders(), + "extra_public_headers": self.GetExtraPublicHeaders(), + "for_blink": self.for_blink, + "use_once_callback": self.use_once_callback, + "export_attribute": self.export_attribute, + "export_header": self.export_header, + } + + @staticmethod + def GetTemplatePrefix(): + return "cpp_templates" + + @classmethod + def GetFilters(cls): + return cls.cpp_filters + + @UseJinja("module.h.tmpl") + def GenerateModuleHeader(self): + return self.GetJinjaExports() + + @UseJinja("module.cc.tmpl") + def GenerateModuleSource(self): + return self.GetJinjaExports() + + @UseJinja("module-shared.h.tmpl") + def GenerateModuleSharedHeader(self): + return self.GetJinjaExports() + + @UseJinja("module-shared-internal.h.tmpl") + def GenerateModuleSharedInternalHeader(self): + return self.GetJinjaExports() + + @UseJinja("module-shared.cc.tmpl") + def GenerateModuleSharedSource(self): + return self.GetJinjaExports() + + def GenerateFiles(self, args): + if self.generate_non_variant_code: + self.Write(self.GenerateModuleSharedHeader(), + self.MatchMojomFilePath("%s-shared.h" % self.module.name)) + self.Write( + self.GenerateModuleSharedInternalHeader(), + self.MatchMojomFilePath("%s-shared-internal.h" % self.module.name)) + self.Write(self.GenerateModuleSharedSource(), + self.MatchMojomFilePath("%s-shared.cc" % self.module.name)) + else: + global _current_typemap + _current_typemap = self.typemap + global _for_blink + _for_blink = self.for_blink + global _use_once_callback + _use_once_callback = self.use_once_callback + global _variant + _variant = self.variant + global _export_attribute + _export_attribute = self.export_attribute + suffix = "-%s" % self.variant if self.variant else "" + self.Write(self.GenerateModuleHeader(), + self.MatchMojomFilePath("%s%s.h" % (self.module.name, suffix))) + self.Write( + self.GenerateModuleSource(), + self.MatchMojomFilePath("%s%s.cc" % (self.module.name, suffix))) diff --git a/mojo/public/tools/bindings/generators/mojom_java_generator.py b/mojo/public/tools/bindings/generators/mojom_java_generator.py new file mode 100644 index 0000000000..c7657ff99a --- /dev/null +++ b/mojo/public/tools/bindings/generators/mojom_java_generator.py @@ -0,0 +1,550 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generates java source files from a mojom.Module.""" + +import argparse +import ast +import contextlib +import os +import re +import shutil +import sys +import tempfile + +from jinja2 import contextfilter + +import mojom.fileutil as fileutil +import mojom.generate.generator as generator +import mojom.generate.module as mojom +from mojom.generate.template_expander import UseJinja + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, + os.pardir, os.pardir, os.pardir, os.pardir, + 'build', 'android', 'gyp')) +from util import build_utils + + +GENERATOR_PREFIX = 'java' + +_spec_to_java_type = { + mojom.BOOL.spec: 'boolean', + mojom.DCPIPE.spec: 'org.chromium.mojo.system.DataPipe.ConsumerHandle', + mojom.DOUBLE.spec: 'double', + mojom.DPPIPE.spec: 'org.chromium.mojo.system.DataPipe.ProducerHandle', + mojom.FLOAT.spec: 'float', + mojom.HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle', + mojom.INT16.spec: 'short', + mojom.INT32.spec: 'int', + mojom.INT64.spec: 'long', + mojom.INT8.spec: 'byte', + mojom.MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle', + mojom.NULLABLE_DCPIPE.spec: + 'org.chromium.mojo.system.DataPipe.ConsumerHandle', + mojom.NULLABLE_DPPIPE.spec: + 'org.chromium.mojo.system.DataPipe.ProducerHandle', + mojom.NULLABLE_HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle', + mojom.NULLABLE_MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle', + mojom.NULLABLE_SHAREDBUFFER.spec: + 'org.chromium.mojo.system.SharedBufferHandle', + mojom.NULLABLE_STRING.spec: 'String', + mojom.SHAREDBUFFER.spec: 'org.chromium.mojo.system.SharedBufferHandle', + mojom.STRING.spec: 'String', + mojom.UINT16.spec: 'short', + mojom.UINT32.spec: 'int', + mojom.UINT64.spec: 'long', + mojom.UINT8.spec: 'byte', +} + +_spec_to_decode_method = { + mojom.BOOL.spec: 'readBoolean', + mojom.DCPIPE.spec: 'readConsumerHandle', + mojom.DOUBLE.spec: 'readDouble', + mojom.DPPIPE.spec: 'readProducerHandle', + mojom.FLOAT.spec: 'readFloat', + mojom.HANDLE.spec: 'readUntypedHandle', + mojom.INT16.spec: 'readShort', + mojom.INT32.spec: 'readInt', + mojom.INT64.spec: 'readLong', + mojom.INT8.spec: 'readByte', + mojom.MSGPIPE.spec: 'readMessagePipeHandle', + mojom.NULLABLE_DCPIPE.spec: 'readConsumerHandle', + mojom.NULLABLE_DPPIPE.spec: 'readProducerHandle', + mojom.NULLABLE_HANDLE.spec: 'readUntypedHandle', + mojom.NULLABLE_MSGPIPE.spec: 'readMessagePipeHandle', + mojom.NULLABLE_SHAREDBUFFER.spec: 'readSharedBufferHandle', + mojom.NULLABLE_STRING.spec: 'readString', + mojom.SHAREDBUFFER.spec: 'readSharedBufferHandle', + mojom.STRING.spec: 'readString', + mojom.UINT16.spec: 'readShort', + mojom.UINT32.spec: 'readInt', + mojom.UINT64.spec: 'readLong', + mojom.UINT8.spec: 'readByte', +} + +_java_primitive_to_boxed_type = { + 'boolean': 'Boolean', + 'byte': 'Byte', + 'double': 'Double', + 'float': 'Float', + 'int': 'Integer', + 'long': 'Long', + 'short': 'Short', +} + + +def NameToComponent(name): + # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar -> + # HTTP_Entry2_FooBar) + name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name) + # insert '_' between non upper and start of upper blocks (e.g., + # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar) + name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name) + return [x.lower() for x in name.split('_')] + +def UpperCamelCase(name): + return ''.join([x.capitalize() for x in NameToComponent(name)]) + +def CamelCase(name): + uccc = UpperCamelCase(name) + return uccc[0].lower() + uccc[1:] + +def ConstantStyle(name): + components = NameToComponent(name) + if components[0] == 'k' and len(components) > 1: + components = components[1:] + # variable cannot starts with a digit. + if components[0][0].isdigit(): + components[0] = '_' + components[0] + return '_'.join([x.upper() for x in components]) + +def GetNameForElement(element): + if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or + mojom.IsStructKind(element) or mojom.IsUnionKind(element)): + return UpperCamelCase(element.name) + if mojom.IsInterfaceRequestKind(element) or mojom.IsAssociatedKind(element): + return GetNameForElement(element.kind) + if isinstance(element, (mojom.Method, + mojom.Parameter, + mojom.Field)): + return CamelCase(element.name) + if isinstance(element, mojom.EnumValue): + return (GetNameForElement(element.enum) + '.' + + ConstantStyle(element.name)) + if isinstance(element, (mojom.NamedValue, + mojom.Constant, + mojom.EnumField)): + return ConstantStyle(element.name) + raise Exception('Unexpected element: %s' % element) + +def GetInterfaceResponseName(method): + return UpperCamelCase(method.name + 'Response') + +def ParseStringAttribute(attribute): + assert isinstance(attribute, basestring) + return attribute + +def GetJavaTrueFalse(value): + return 'true' if value else 'false' + +def GetArrayNullabilityFlags(kind): + """Returns nullability flags for an array type, see Decoder.java. + + As we have dedicated decoding functions for arrays, we have to pass + nullability information about both the array itself, as well as the array + element type there. + """ + assert mojom.IsArrayKind(kind) + ARRAY_NULLABLE = \ + 'org.chromium.mojo.bindings.BindingsHelper.ARRAY_NULLABLE' + ELEMENT_NULLABLE = \ + 'org.chromium.mojo.bindings.BindingsHelper.ELEMENT_NULLABLE' + NOTHING_NULLABLE = \ + 'org.chromium.mojo.bindings.BindingsHelper.NOTHING_NULLABLE' + + flags_to_set = [] + if mojom.IsNullableKind(kind): + flags_to_set.append(ARRAY_NULLABLE) + if mojom.IsNullableKind(kind.kind): + flags_to_set.append(ELEMENT_NULLABLE) + + if not flags_to_set: + flags_to_set = [NOTHING_NULLABLE] + return ' | '.join(flags_to_set) + + +def AppendEncodeDecodeParams(initial_params, context, kind, bit): + """ Appends standard parameters shared between encode and decode calls. """ + params = list(initial_params) + if (kind == mojom.BOOL): + params.append(str(bit)) + if mojom.IsReferenceKind(kind): + if mojom.IsArrayKind(kind): + params.append(GetArrayNullabilityFlags(kind)) + else: + params.append(GetJavaTrueFalse(mojom.IsNullableKind(kind))) + if mojom.IsArrayKind(kind): + params.append(GetArrayExpectedLength(kind)) + if mojom.IsInterfaceKind(kind): + params.append('%s.MANAGER' % GetJavaType(context, kind)) + if mojom.IsArrayKind(kind) and mojom.IsInterfaceKind(kind.kind): + params.append('%s.MANAGER' % GetJavaType(context, kind.kind)) + return params + + +@contextfilter +def DecodeMethod(context, kind, offset, bit): + def _DecodeMethodName(kind): + if mojom.IsArrayKind(kind): + return _DecodeMethodName(kind.kind) + 's' + if mojom.IsEnumKind(kind): + return _DecodeMethodName(mojom.INT32) + if mojom.IsInterfaceRequestKind(kind): + return 'readInterfaceRequest' + if mojom.IsInterfaceKind(kind): + return 'readServiceInterface' + if mojom.IsAssociatedInterfaceRequestKind(kind): + return 'readAssociatedInterfaceRequestNotSupported' + if mojom.IsAssociatedInterfaceKind(kind): + return 'readAssociatedServiceInterfaceNotSupported' + return _spec_to_decode_method[kind.spec] + methodName = _DecodeMethodName(kind) + params = AppendEncodeDecodeParams([ str(offset) ], context, kind, bit) + return '%s(%s)' % (methodName, ', '.join(params)) + +@contextfilter +def EncodeMethod(context, kind, variable, offset, bit): + params = AppendEncodeDecodeParams( + [ variable, str(offset) ], context, kind, bit) + return 'encode(%s)' % ', '.join(params) + +def GetPackage(module): + if module.attributes and 'JavaPackage' in module.attributes: + return ParseStringAttribute(module.attributes['JavaPackage']) + # Default package. + if module.namespace: + return 'org.chromium.' + module.namespace + return 'org.chromium' + +def GetNameForKind(context, kind): + def _GetNameHierachy(kind): + hierachy = [] + if kind.parent_kind: + hierachy = _GetNameHierachy(kind.parent_kind) + hierachy.append(GetNameForElement(kind)) + return hierachy + + module = context.resolve('module') + elements = [] + if GetPackage(module) != GetPackage(kind.module): + elements += [GetPackage(kind.module)] + elements += _GetNameHierachy(kind) + return '.'.join(elements) + +@contextfilter +def GetJavaClassForEnum(context, kind): + return GetNameForKind(context, kind) + +def GetBoxedJavaType(context, kind, with_generics=True): + unboxed_type = GetJavaType(context, kind, False, with_generics) + if unboxed_type in _java_primitive_to_boxed_type: + return _java_primitive_to_boxed_type[unboxed_type] + return unboxed_type + +@contextfilter +def GetJavaType(context, kind, boxed=False, with_generics=True): + if boxed: + return GetBoxedJavaType(context, kind) + if (mojom.IsStructKind(kind) or + mojom.IsInterfaceKind(kind) or + mojom.IsUnionKind(kind)): + return GetNameForKind(context, kind) + if mojom.IsInterfaceRequestKind(kind): + return ('org.chromium.mojo.bindings.InterfaceRequest<%s>' % + GetNameForKind(context, kind.kind)) + if mojom.IsAssociatedInterfaceKind(kind): + return 'org.chromium.mojo.bindings.AssociatedInterfaceNotSupported' + if mojom.IsAssociatedInterfaceRequestKind(kind): + return 'org.chromium.mojo.bindings.AssociatedInterfaceRequestNotSupported' + if mojom.IsMapKind(kind): + if with_generics: + return 'java.util.Map<%s, %s>' % ( + GetBoxedJavaType(context, kind.key_kind), + GetBoxedJavaType(context, kind.value_kind)) + else: + return 'java.util.Map' + if mojom.IsArrayKind(kind): + return '%s[]' % GetJavaType(context, kind.kind, boxed, with_generics) + if mojom.IsEnumKind(kind): + return 'int' + return _spec_to_java_type[kind.spec] + +@contextfilter +def DefaultValue(context, field): + assert field.default + if isinstance(field.kind, mojom.Struct): + assert field.default == 'default' + return 'new %s()' % GetJavaType(context, field.kind) + return '(%s) %s' % ( + GetJavaType(context, field.kind), + ExpressionToText(context, field.default, kind_spec=field.kind.spec)) + +@contextfilter +def ConstantValue(context, constant): + return '(%s) %s' % ( + GetJavaType(context, constant.kind), + ExpressionToText(context, constant.value, kind_spec=constant.kind.spec)) + +@contextfilter +def NewArray(context, kind, size): + if mojom.IsArrayKind(kind.kind): + return NewArray(context, kind.kind, size) + '[]' + return 'new %s[%s]' % ( + GetJavaType(context, kind.kind, boxed=False, with_generics=False), size) + +@contextfilter +def ExpressionToText(context, token, kind_spec=''): + def _TranslateNamedValue(named_value): + entity_name = GetNameForElement(named_value) + if named_value.parent_kind: + return GetJavaType(context, named_value.parent_kind) + '.' + entity_name + # Handle the case where named_value is a module level constant: + if not isinstance(named_value, mojom.EnumValue): + entity_name = (GetConstantsMainEntityName(named_value.module) + '.' + + entity_name) + if GetPackage(named_value.module) == GetPackage(context.resolve('module')): + return entity_name + return GetPackage(named_value.module) + '.' + entity_name + + if isinstance(token, mojom.NamedValue): + return _TranslateNamedValue(token) + if kind_spec.startswith('i') or kind_spec.startswith('u'): + # Add Long suffix to all integer literals. + number = ast.literal_eval(token.lstrip('+ ')) + if not isinstance(number, (int, long)): + raise ValueError('got unexpected type %r for int literal %r' % ( + type(number), token)) + # If the literal is too large to fit a signed long, convert it to the + # equivalent signed long. + if number >= 2 ** 63: + number -= 2 ** 64 + return '%dL' % number + if isinstance(token, mojom.BuiltinValue): + if token.value == 'double.INFINITY': + return 'java.lang.Double.POSITIVE_INFINITY' + if token.value == 'double.NEGATIVE_INFINITY': + return 'java.lang.Double.NEGATIVE_INFINITY' + if token.value == 'double.NAN': + return 'java.lang.Double.NaN' + if token.value == 'float.INFINITY': + return 'java.lang.Float.POSITIVE_INFINITY' + if token.value == 'float.NEGATIVE_INFINITY': + return 'java.lang.Float.NEGATIVE_INFINITY' + if token.value == 'float.NAN': + return 'java.lang.Float.NaN' + return token + +def GetArrayKind(kind, size = None): + if size is None: + return mojom.Array(kind) + else: + array = mojom.Array(kind, 0) + array.java_map_size = size + return array + +def GetArrayExpectedLength(kind): + if mojom.IsArrayKind(kind) and kind.length is not None: + return getattr(kind, 'java_map_size', str(kind.length)) + else: + return 'org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH' + +def IsPointerArrayKind(kind): + if not mojom.IsArrayKind(kind): + return False + sub_kind = kind.kind + return mojom.IsObjectKind(sub_kind) and not mojom.IsUnionKind(sub_kind) + +def IsUnionArrayKind(kind): + if not mojom.IsArrayKind(kind): + return False + sub_kind = kind.kind + return mojom.IsUnionKind(sub_kind) + +def GetConstantsMainEntityName(module): + if module.attributes and 'JavaConstantsClassName' in module.attributes: + return ParseStringAttribute(module.attributes['JavaConstantsClassName']) + # This constructs the name of the embedding classes for module level constants + # by extracting the mojom's filename and prepending it to Constants. + return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) + + 'Constants') + +def GetMethodOrdinalName(method): + return ConstantStyle(method.name) + '_ORDINAL' + +def HasMethodWithResponse(interface): + for method in interface.methods: + if method.response_parameters is not None: + return True + return False + +def HasMethodWithoutResponse(interface): + for method in interface.methods: + if method.response_parameters is None: + return True + return False + +@contextlib.contextmanager +def TempDir(): + dirname = tempfile.mkdtemp() + try: + yield dirname + finally: + shutil.rmtree(dirname) + +class Generator(generator.Generator): + + java_filters = { + 'array_expected_length': GetArrayExpectedLength, + 'array': GetArrayKind, + 'constant_value': ConstantValue, + 'decode_method': DecodeMethod, + 'default_value': DefaultValue, + 'encode_method': EncodeMethod, + 'expression_to_text': ExpressionToText, + 'has_method_without_response': HasMethodWithoutResponse, + 'has_method_with_response': HasMethodWithResponse, + 'interface_response_name': GetInterfaceResponseName, + 'is_array_kind': mojom.IsArrayKind, + 'is_any_handle_kind': mojom.IsAnyHandleKind, + "is_enum_kind": mojom.IsEnumKind, + 'is_interface_request_kind': mojom.IsInterfaceRequestKind, + 'is_map_kind': mojom.IsMapKind, + 'is_nullable_kind': mojom.IsNullableKind, + 'is_pointer_array_kind': IsPointerArrayKind, + 'is_reference_kind': mojom.IsReferenceKind, + 'is_struct_kind': mojom.IsStructKind, + 'is_union_array_kind': IsUnionArrayKind, + 'is_union_kind': mojom.IsUnionKind, + 'java_class_for_enum': GetJavaClassForEnum, + 'java_true_false': GetJavaTrueFalse, + 'java_type': GetJavaType, + 'method_ordinal_name': GetMethodOrdinalName, + 'name': GetNameForElement, + 'new_array': NewArray, + 'ucc': lambda x: UpperCamelCase(x.name), + } + + def GetJinjaExports(self): + return { + 'package': GetPackage(self.module), + } + + @staticmethod + def GetTemplatePrefix(): + return "java_templates" + + @classmethod + def GetFilters(cls): + return cls.java_filters + + def GetJinjaExportsForInterface(self, interface): + exports = self.GetJinjaExports() + exports.update({'interface': interface}) + return exports + + @UseJinja('enum.java.tmpl') + def GenerateEnumSource(self, enum): + exports = self.GetJinjaExports() + exports.update({'enum': enum}) + return exports + + @UseJinja('struct.java.tmpl') + def GenerateStructSource(self, struct): + exports = self.GetJinjaExports() + exports.update({'struct': struct}) + return exports + + @UseJinja('union.java.tmpl') + def GenerateUnionSource(self, union): + exports = self.GetJinjaExports() + exports.update({'union': union}) + return exports + + @UseJinja('interface.java.tmpl') + def GenerateInterfaceSource(self, interface): + return self.GetJinjaExportsForInterface(interface) + + @UseJinja('interface_internal.java.tmpl') + def GenerateInterfaceInternalSource(self, interface): + return self.GetJinjaExportsForInterface(interface) + + @UseJinja('constants.java.tmpl') + def GenerateConstantsSource(self, module): + exports = self.GetJinjaExports() + exports.update({'main_entity': GetConstantsMainEntityName(module), + 'constants': module.constants}) + return exports + + def DoGenerateFiles(self): + fileutil.EnsureDirectoryExists(self.output_dir) + + # Keep this above the others as .GetStructs() changes the state of the + # module, annotating structs with required information. + for struct in self.GetStructs(): + self.Write(self.GenerateStructSource(struct), + '%s.java' % GetNameForElement(struct)) + + for union in self.module.unions: + self.Write(self.GenerateUnionSource(union), + '%s.java' % GetNameForElement(union)) + + for enum in self.module.enums: + self.Write(self.GenerateEnumSource(enum), + '%s.java' % GetNameForElement(enum)) + + for interface in self.GetInterfaces(): + self.Write(self.GenerateInterfaceSource(interface), + '%s.java' % GetNameForElement(interface)) + self.Write(self.GenerateInterfaceInternalSource(interface), + '%s_Internal.java' % GetNameForElement(interface)) + + if self.module.constants: + self.Write(self.GenerateConstantsSource(self.module), + '%s.java' % GetConstantsMainEntityName(self.module)) + + def GenerateFiles(self, unparsed_args): + # TODO(rockot): Support variant output for Java. + if self.variant: + raise Exception("Variants not supported in Java bindings.") + + parser = argparse.ArgumentParser() + parser.add_argument('--java_output_directory', dest='java_output_directory') + args = parser.parse_args(unparsed_args) + package_path = GetPackage(self.module).replace('.', '/') + + # Generate the java files in a temporary directory and place a single + # srcjar in the output directory. + basename = self.MatchMojomFilePath("%s.srcjar" % self.module.name) + zip_filename = os.path.join(self.output_dir, basename) + with TempDir() as temp_java_root: + self.output_dir = os.path.join(temp_java_root, package_path) + self.DoGenerateFiles(); + build_utils.ZipDir(zip_filename, temp_java_root) + + if args.java_output_directory: + # If requested, generate the java files directly into indicated directory. + self.output_dir = os.path.join(args.java_output_directory, package_path) + self.DoGenerateFiles(); + + def GetJinjaParameters(self): + return { + 'lstrip_blocks': True, + 'trim_blocks': True, + } + + def GetGlobals(self): + return { + 'namespace': self.module.namespace, + 'module': self.module, + } diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py new file mode 100644 index 0000000000..ab9635ee30 --- /dev/null +++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py @@ -0,0 +1,425 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generates JavaScript source files from a mojom.Module.""" + +import mojom.generate.generator as generator +import mojom.generate.module as mojom +import mojom.generate.pack as pack +import os +from mojom.generate.template_expander import UseJinja + +_kind_to_javascript_default_value = { + mojom.BOOL: "false", + mojom.INT8: "0", + mojom.UINT8: "0", + mojom.INT16: "0", + mojom.UINT16: "0", + mojom.INT32: "0", + mojom.UINT32: "0", + mojom.FLOAT: "0", + mojom.HANDLE: "null", + mojom.DCPIPE: "null", + mojom.DPPIPE: "null", + mojom.MSGPIPE: "null", + mojom.SHAREDBUFFER: "null", + mojom.NULLABLE_HANDLE: "null", + mojom.NULLABLE_DCPIPE: "null", + mojom.NULLABLE_DPPIPE: "null", + mojom.NULLABLE_MSGPIPE: "null", + mojom.NULLABLE_SHAREDBUFFER: "null", + mojom.INT64: "0", + mojom.UINT64: "0", + mojom.DOUBLE: "0", + mojom.STRING: "null", + mojom.NULLABLE_STRING: "null" +} + + +def JavaScriptType(kind): + name = [] + if kind.imported_from: + name.append(kind.imported_from["unique_name"]) + if kind.parent_kind: + name.append(kind.parent_kind.name) + name.append(kind.name) + return ".".join(name) + + +def JavaScriptDefaultValue(field): + if field.default: + if mojom.IsStructKind(field.kind): + assert field.default == "default" + return "new %s()" % JavaScriptType(field.kind) + return ExpressionToText(field.default) + if field.kind in mojom.PRIMITIVES: + return _kind_to_javascript_default_value[field.kind] + if mojom.IsStructKind(field.kind): + return "null" + if mojom.IsUnionKind(field.kind): + return "null" + if mojom.IsArrayKind(field.kind): + return "null" + if mojom.IsMapKind(field.kind): + return "null" + if mojom.IsInterfaceKind(field.kind): + return "new %sPtr()" % JavaScriptType(field.kind) + if mojom.IsInterfaceRequestKind(field.kind): + return "new bindings.InterfaceRequest()" + if mojom.IsAssociatedKind(field.kind): + return "null" + if mojom.IsEnumKind(field.kind): + return "0" + raise Exception("No valid default: %s" % field) + + +def JavaScriptPayloadSize(packed): + packed_fields = packed.packed_fields + if not packed_fields: + return 0 + last_field = packed_fields[-1] + offset = last_field.offset + last_field.size + pad = pack.GetPad(offset, 8) + return offset + pad + + +_kind_to_codec_type = { + mojom.BOOL: "codec.Uint8", + mojom.INT8: "codec.Int8", + mojom.UINT8: "codec.Uint8", + mojom.INT16: "codec.Int16", + mojom.UINT16: "codec.Uint16", + mojom.INT32: "codec.Int32", + mojom.UINT32: "codec.Uint32", + mojom.FLOAT: "codec.Float", + mojom.HANDLE: "codec.Handle", + mojom.DCPIPE: "codec.Handle", + mojom.DPPIPE: "codec.Handle", + mojom.MSGPIPE: "codec.Handle", + mojom.SHAREDBUFFER: "codec.Handle", + mojom.NULLABLE_HANDLE: "codec.NullableHandle", + mojom.NULLABLE_DCPIPE: "codec.NullableHandle", + mojom.NULLABLE_DPPIPE: "codec.NullableHandle", + mojom.NULLABLE_MSGPIPE: "codec.NullableHandle", + mojom.NULLABLE_SHAREDBUFFER: "codec.NullableHandle", + mojom.INT64: "codec.Int64", + mojom.UINT64: "codec.Uint64", + mojom.DOUBLE: "codec.Double", + mojom.STRING: "codec.String", + mojom.NULLABLE_STRING: "codec.NullableString", +} + + +def CodecType(kind): + if kind in mojom.PRIMITIVES: + return _kind_to_codec_type[kind] + if mojom.IsStructKind(kind): + pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \ + else "PointerTo" + return "new codec.%s(%s)" % (pointer_type, JavaScriptType(kind)) + if mojom.IsUnionKind(kind): + return JavaScriptType(kind) + if mojom.IsArrayKind(kind): + array_type = "NullableArrayOf" if mojom.IsNullableKind(kind) else "ArrayOf" + array_length = "" if kind.length is None else ", %d" % kind.length + element_type = ElementCodecType(kind.kind) + return "new codec.%s(%s%s)" % (array_type, element_type, array_length) + if mojom.IsInterfaceKind(kind): + return "new codec.%s(%sPtr)" % ( + "NullableInterface" if mojom.IsNullableKind(kind) else "Interface", + JavaScriptType(kind)) + if mojom.IsInterfaceRequestKind(kind): + return "codec.%s" % ( + "NullableInterfaceRequest" if mojom.IsNullableKind(kind) + else "InterfaceRequest") + if mojom.IsAssociatedInterfaceKind(kind): + return "codec.AssociatedInterfaceNotSupported" + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "codec.AssociatedInterfaceRequestNotSupported" + if mojom.IsEnumKind(kind): + return "new codec.Enum(%s)" % JavaScriptType(kind) + if mojom.IsMapKind(kind): + map_type = "NullableMapOf" if mojom.IsNullableKind(kind) else "MapOf" + key_type = ElementCodecType(kind.key_kind) + value_type = ElementCodecType(kind.value_kind) + return "new codec.%s(%s, %s)" % (map_type, key_type, value_type) + raise Exception("No codec type for %s" % kind) + + +def ElementCodecType(kind): + return "codec.PackedBool" if mojom.IsBoolKind(kind) else CodecType(kind) + + +def JavaScriptDecodeSnippet(kind): + if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or + mojom.IsAnyInterfaceKind(kind)): + return "decodeStruct(%s)" % CodecType(kind) + if mojom.IsStructKind(kind): + return "decodeStructPointer(%s)" % JavaScriptType(kind) + if mojom.IsMapKind(kind): + return "decodeMapPointer(%s, %s)" % \ + (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind)) + if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind): + return "decodeArrayPointer(codec.PackedBool)" + if mojom.IsArrayKind(kind): + return "decodeArrayPointer(%s)" % CodecType(kind.kind) + if mojom.IsUnionKind(kind): + return "decodeUnion(%s)" % CodecType(kind) + if mojom.IsEnumKind(kind): + return JavaScriptDecodeSnippet(mojom.INT32) + raise Exception("No decode snippet for %s" % kind) + + +def JavaScriptEncodeSnippet(kind): + if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or + mojom.IsAnyInterfaceKind(kind)): + return "encodeStruct(%s, " % CodecType(kind) + if mojom.IsUnionKind(kind): + return "encodeStruct(%s, " % JavaScriptType(kind) + if mojom.IsStructKind(kind): + return "encodeStructPointer(%s, " % JavaScriptType(kind) + if mojom.IsMapKind(kind): + return "encodeMapPointer(%s, %s, " % \ + (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind)) + if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind): + return "encodeArrayPointer(codec.PackedBool, "; + if mojom.IsArrayKind(kind): + return "encodeArrayPointer(%s, " % CodecType(kind.kind) + if mojom.IsEnumKind(kind): + return JavaScriptEncodeSnippet(mojom.INT32) + raise Exception("No encode snippet for %s" % kind) + + +def JavaScriptUnionDecodeSnippet(kind): + if mojom.IsUnionKind(kind): + return "decodeStructPointer(%s)" % JavaScriptType(kind) + return JavaScriptDecodeSnippet(kind) + + +def JavaScriptUnionEncodeSnippet(kind): + if mojom.IsUnionKind(kind): + return "encodeStructPointer(%s, " % JavaScriptType(kind) + return JavaScriptEncodeSnippet(kind) + + +def JavaScriptFieldOffset(packed_field): + return "offset + codec.kStructHeaderSize + %s" % packed_field.offset + + +def JavaScriptNullableParam(field): + return "true" if mojom.IsNullableKind(field.kind) else "false" + + +def GetArrayExpectedDimensionSizes(kind): + expected_dimension_sizes = [] + while mojom.IsArrayKind(kind): + expected_dimension_sizes.append(generator.ExpectedArraySize(kind) or 0) + kind = kind.kind + # Strings are serialized as variable-length arrays. + if (mojom.IsStringKind(kind)): + expected_dimension_sizes.append(0) + return expected_dimension_sizes + + +def JavaScriptValidateArrayParams(field): + nullable = JavaScriptNullableParam(field) + element_kind = field.kind.kind + element_size = pack.PackedField.GetSizeForKind(element_kind) + expected_dimension_sizes = GetArrayExpectedDimensionSizes( + field.kind) + element_type = ElementCodecType(element_kind) + return "%s, %s, %s, %s, 0" % \ + (element_size, element_type, nullable, + expected_dimension_sizes) + + +def JavaScriptValidateEnumParams(field): + return JavaScriptType(field.kind) + +def JavaScriptValidateStructParams(field): + nullable = JavaScriptNullableParam(field) + struct_type = JavaScriptType(field.kind) + return "%s, %s" % (struct_type, nullable) + +def JavaScriptValidateUnionParams(field): + nullable = JavaScriptNullableParam(field) + union_type = JavaScriptType(field.kind) + return "%s, %s" % (union_type, nullable) + +def JavaScriptValidateMapParams(field): + nullable = JavaScriptNullableParam(field) + keys_type = ElementCodecType(field.kind.key_kind) + values_kind = field.kind.value_kind; + values_type = ElementCodecType(values_kind) + values_nullable = "true" if mojom.IsNullableKind(values_kind) else "false" + return "%s, %s, %s, %s" % \ + (nullable, keys_type, values_type, values_nullable) + + +def TranslateConstants(token): + if isinstance(token, (mojom.EnumValue, mojom.NamedValue)): + # Both variable and enum constants are constructed like: + # NamespaceUid.Struct[.Enum].CONSTANT_NAME + name = [] + if token.imported_from: + name.append(token.imported_from["unique_name"]) + if token.parent_kind: + name.append(token.parent_kind.name) + if isinstance(token, mojom.EnumValue): + name.append(token.enum.name) + name.append(token.name) + return ".".join(name) + + if isinstance(token, mojom.BuiltinValue): + if token.value == "double.INFINITY" or token.value == "float.INFINITY": + return "Infinity"; + if token.value == "double.NEGATIVE_INFINITY" or \ + token.value == "float.NEGATIVE_INFINITY": + return "-Infinity"; + if token.value == "double.NAN" or token.value == "float.NAN": + return "NaN"; + + return token + + +def ExpressionToText(value): + return TranslateConstants(value) + +def IsArrayPointerField(field): + return mojom.IsArrayKind(field.kind) + +def IsEnumField(field): + return mojom.IsEnumKind(field.kind) + +def IsStringPointerField(field): + return mojom.IsStringKind(field.kind) + +def IsStructPointerField(field): + return mojom.IsStructKind(field.kind) + +def IsMapPointerField(field): + return mojom.IsMapKind(field.kind) + +def IsHandleField(field): + return mojom.IsAnyHandleKind(field.kind) + +def IsInterfaceField(field): + return mojom.IsInterfaceKind(field.kind) + +def IsInterfaceRequestField(field): + return mojom.IsInterfaceRequestKind(field.kind) + +def IsUnionField(field): + return mojom.IsUnionKind(field.kind) + +def IsBoolField(field): + return mojom.IsBoolKind(field.kind) + +def IsObjectField(field): + return mojom.IsObjectKind(field.kind) + +def IsAnyHandleOrInterfaceField(field): + return mojom.IsAnyHandleOrInterfaceKind(field.kind) + +def IsEnumField(field): + return mojom.IsEnumKind(field.kind) + +def GetRelativePath(module, base_module): + return os.path.relpath(module.path, os.path.dirname(base_module.path)) + + +class Generator(generator.Generator): + + js_filters = { + "decode_snippet": JavaScriptDecodeSnippet, + "default_value": JavaScriptDefaultValue, + "encode_snippet": JavaScriptEncodeSnippet, + "expression_to_text": ExpressionToText, + "field_offset": JavaScriptFieldOffset, + "has_callbacks": mojom.HasCallbacks, + "is_any_handle_or_interface_field": IsAnyHandleOrInterfaceField, + "is_array_pointer_field": IsArrayPointerField, + "is_bool_field": IsBoolField, + "is_enum_field": IsEnumField, + "is_handle_field": IsHandleField, + "is_interface_field": IsInterfaceField, + "is_interface_request_field": IsInterfaceRequestField, + "is_map_pointer_field": IsMapPointerField, + "is_object_field": IsObjectField, + "is_string_pointer_field": IsStringPointerField, + "is_struct_pointer_field": IsStructPointerField, + "is_union_field": IsUnionField, + "js_type": JavaScriptType, + "payload_size": JavaScriptPayloadSize, + "get_relative_path": GetRelativePath, + "stylize_method": generator.StudlyCapsToCamel, + "union_decode_snippet": JavaScriptUnionDecodeSnippet, + "union_encode_snippet": JavaScriptUnionEncodeSnippet, + "validate_array_params": JavaScriptValidateArrayParams, + "validate_enum_params": JavaScriptValidateEnumParams, + "validate_map_params": JavaScriptValidateMapParams, + "validate_nullable_params": JavaScriptNullableParam, + "validate_struct_params": JavaScriptValidateStructParams, + "validate_union_params": JavaScriptValidateUnionParams, + } + + def GetParameters(self): + return { + "namespace": self.module.namespace, + "imports": self.GetImports(), + "kinds": self.module.kinds, + "enums": self.module.enums, + "module": self.module, + "structs": self.GetStructs() + self.GetStructsFromMethods(), + "unions": self.GetUnions(), + "use_new_js_bindings": self.use_new_js_bindings, + "interfaces": self.GetInterfaces(), + "imported_interfaces": self.GetImportedInterfaces(), + } + + @staticmethod + def GetTemplatePrefix(): + return "js_templates" + + @classmethod + def GetFilters(cls): + return cls.js_filters + + @UseJinja("module.amd.tmpl") + def GenerateAMDModule(self): + return self.GetParameters() + + def GenerateFiles(self, args): + if self.variant: + raise Exception("Variants not supported in JavaScript bindings.") + + self.Write(self.GenerateAMDModule(), + self.MatchMojomFilePath("%s.js" % self.module.name)) + + def GetImports(self): + used_names = set() + for each_import in self.module.imports: + simple_name = each_import["module_name"].split(".")[0] + + # Since each import is assigned a variable in JS, they need to have unique + # names. + unique_name = simple_name + counter = 0 + while unique_name in used_names: + counter += 1 + unique_name = simple_name + str(counter) + + used_names.add(unique_name) + each_import["unique_name"] = unique_name + "$" + counter += 1 + return self.module.imports + + def GetImportedInterfaces(self): + interface_to_import = {}; + for each_import in self.module.imports: + for each_interface in each_import["module"].interfaces: + name = each_interface.name + interface_to_import[name] = each_import["unique_name"] + "." + name + return interface_to_import; + diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni new file mode 100644 index 0000000000..4a244fb5b1 --- /dev/null +++ b/mojo/public/tools/bindings/mojom.gni @@ -0,0 +1,661 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +declare_args() { + # Indicates whether typemapping should be supported in this build + # configuration. This may be disabled when building external projects which + # depend on //mojo but which do not need/want all of the Chromium tree + # dependencies that come with typemapping. + # + # Note that (perhaps obviously) a huge amount of Chromium code will not build + # with typemapping disabled, so it is never valid to set this to |false| in + # any Chromium build configuration. + enable_mojom_typemapping = true +} + +mojom_generator_root = "//mojo/public/tools/bindings" +mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py" +mojom_generator_sources = [ + "$mojom_generator_root/generators/mojom_cpp_generator.py", + "$mojom_generator_root/generators/mojom_js_generator.py", + "$mojom_generator_root/generators/mojom_java_generator.py", + "$mojom_generator_root/pylib/mojom/__init__.py", + "$mojom_generator_root/pylib/mojom/error.py", + "$mojom_generator_root/pylib/mojom/generate/__init__.py", + "$mojom_generator_root/pylib/mojom/generate/constant_resolver.py", + "$mojom_generator_root/pylib/mojom/generate/generator.py", + "$mojom_generator_root/pylib/mojom/generate/module.py", + "$mojom_generator_root/pylib/mojom/generate/pack.py", + "$mojom_generator_root/pylib/mojom/generate/template_expander.py", + "$mojom_generator_root/pylib/mojom/generate/translate.py", + "$mojom_generator_root/pylib/mojom/parse/__init__.py", + "$mojom_generator_root/pylib/mojom/parse/ast.py", + "$mojom_generator_root/pylib/mojom/parse/lexer.py", + "$mojom_generator_root/pylib/mojom/parse/parser.py", + "$mojom_generator_script", +] + +if (enable_mojom_typemapping) { + if (!is_ios) { + _bindings_configuration_files = [ + "//mojo/public/tools/bindings/chromium_bindings_configuration.gni", + "//mojo/public/tools/bindings/blink_bindings_configuration.gni", + ] + } else { + _bindings_configuration_files = + [ "//mojo/public/tools/bindings/chromium_bindings_configuration.gni" ] + } + _bindings_configurations = [] + foreach(config_file, _bindings_configuration_files) { + _bindings_configurations += [ read_file(config_file, "scope") ] + } + foreach(configuration, _bindings_configurations) { + # Check that the mojom field of each typemap refers to a mojom that exists. + foreach(typemap, configuration.typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + read_file(_typemap_config.mojom, "") + } + if (is_mac && defined(configuration.typemaps_mac)) { + foreach(typemap, configuration.typemaps_mac) { + _typemap_config = { + } + _typemap_config = typemap.config + read_file(_typemap_config.mojom, "") + } + } + } +} else { + _bindings_configuration_files = [] + _bindings_configurations = [ + { + typemaps = [] + }, + { + variant = "blink" + for_blink = true + typemaps = [] + }, + ] +} + +# Generates targets for building C++, JavaScript and Java bindings from mojom +# files. The output files will go under the generated file directory tree with +# the same path as each input file. +# +# Other targets should depend on one of these generated targets (where "foo" +# is the target name): +# +# foo +# C++ and Javascript bindings. Other mojom targets should also depend on +# this target. +# +# foo_blink +# C++ bindings using Blink standard types. +# +# foo_java +# Java bindings. +# +# Parameters: +# +# sources (optional if one of the deps sets listed below is present) +# List of source .mojom files to compile. +# +# deps (optional) +# Note: this can contain only other mojom targets. +# +# DEPRECATED: This is synonymous with public_deps because all mojom +# dependencies must be public by design. Please use public_deps. +# +# public_deps (optional) +# Note: this can contain only other mojom targets. +# +# import_dirs (optional) +# List of import directories that will get added when processing sources. +# +# testonly (optional) +# +# visibility (optional) +# +# visibility_blink (optional) +# The value to use for visibility for the blink variant. If unset, +# |visibility| is used. +# +# use_once_callback (optional) +# If set to true, generated classes will use base::OnceCallback instead of +# base::RepeatingCallback. +# Default value is false. +# TODO(dcheng): +# - Convert everything to use OnceCallback. +# - Remove support for the old mode. +# +# cpp_only (optional) +# If set to true, only the C++ bindings targets will be generated. +# +# use_new_js_bindings (optional) +# If set to true, the generated JS code will use the new module loading +# approach and the core API exposed by Web IDL. +# +# TODO(yzshen): Switch all existing users to use_new_js_bindings=true and +# remove the old mode. +# +# The following parameters are used to support the component build. They are +# needed so that bindings which are linked with a component can use the same +# export settings for classes. The first three are for the chromium variant, and +# the last three are for the blink variant. +# export_class_attribute (optional) +# The attribute to add to the class declaration. e.g. "CONTENT_EXPORT" +# export_define (optional) +# A define to be added to the source_set which is needed by the export +# header. e.g. "CONTENT_IMPLEMENTATION=1" +# export_header (optional) +# A header to be added to the generated bindings to support the component +# build. e.g. "content/common/content_export.h" +# export_class_attribute_blink (optional) +# export_define_blink (optional) +# export_header_blink (optional) +# These three parameters are the blink variants of the previous 3. +# +# The following parameters are used to correct component build dependencies. +# They are needed so mojom-mojom dependencies follow the rule that dependencies +# on a source set in another component are replaced by a dependency on the +# containing component. The first two are for the chromium variant; the other +# two are for the blink variant. +# overridden_deps (optional) +# The list of mojom deps to be overridden. +# component_deps (optional) +# The list of component deps to add to replace overridden_deps. +# overridden_deps_blink (optional) +# component_deps_blink (optional) +# These two parameters are the blink variants of the previous two. +template("mojom") { + assert( + defined(invoker.sources) || defined(invoker.deps) || + defined(invoker.public_deps), + "\"sources\" or \"deps\" must be defined for the $target_name template.") + if (defined(invoker.export_class_attribute) || + defined(invoker.export_define) || defined(invoker.export_header)) { + assert(defined(invoker.export_class_attribute)) + assert(defined(invoker.export_define)) + assert(defined(invoker.export_header)) + } + if (defined(invoker.export_class_attribute_blink) || + defined(invoker.export_define_blink) || + defined(invoker.export_header_blink)) { + assert(defined(invoker.export_class_attribute_blink)) + assert(defined(invoker.export_define_blink)) + assert(defined(invoker.export_header_blink)) + } + if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) { + assert(defined(invoker.overridden_deps)) + assert(defined(invoker.component_deps)) + } + + if (defined(invoker.overridden_deps_blink) || + defined(invoker.component_deps_blink)) { + assert(defined(invoker.overridden_deps_blink)) + assert(defined(invoker.component_deps_blink)) + } + + all_deps = [] + if (defined(invoker.deps)) { + all_deps += invoker.deps + } + if (defined(invoker.public_deps)) { + all_deps += invoker.public_deps + } + + group("${target_name}__is_mojom") { + } + + # Explicitly ensure that all dependencies (invoker.deps and + # invoker.public_deps) are mojom targets. + group("${target_name}__check_deps_are_all_mojom") { + deps = [] + foreach(d, all_deps) { + name = get_label_info(d, "label_no_toolchain") + toolchain = get_label_info(d, "toolchain") + deps += [ "${name}__is_mojom(${toolchain})" ] + } + } + + # Generate code that is shared by different variants. + if (defined(invoker.sources)) { + common_generator_args = [ + "--use_bundled_pylibs", + "generate", + "{{source}}", + "-d", + rebase_path("//", root_build_dir), + "-I", + rebase_path("//", root_build_dir), + "-o", + rebase_path(root_gen_dir), + "--bytecode_path", + rebase_path("$root_gen_dir/mojo/public/tools/bindings"), + ] + + if (defined(invoker.import_dirs)) { + foreach(import_dir, invoker.import_dirs) { + common_generator_args += [ + "-I", + rebase_path(import_dir, root_build_dir), + ] + } + } + + generator_shared_cpp_outputs = [ + "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h", + "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.cc", + "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.h", + ] + generator_shared_target_name = "${target_name}_shared__generator" + action_foreach(generator_shared_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + sources = invoker.sources + deps = [ + "//mojo/public/tools/bindings:precompile_templates", + ] + outputs = generator_shared_cpp_outputs + args = common_generator_args + args += [ + "--generate_non_variant_code", + "-g", + "c++", + ] + depfile = "{{source_gen_dir}}/${generator_shared_target_name}_{{source_name_part}}.d" + args += [ + "--depfile", + depfile, + "--depfile_target", + "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h", + ] + } + } + + shared_cpp_sources_suffix = "shared_cpp_sources" + shared_cpp_sources_target_name = "${target_name}_${shared_cpp_sources_suffix}" + source_set(shared_cpp_sources_target_name) { + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + deps = [] + if (defined(invoker.sources)) { + sources = + process_file_template(invoker.sources, generator_shared_cpp_outputs) + deps += [ ":$generator_shared_target_name" ] + } + public_deps = [] + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append shared_cpp_sources_suffix + # to get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}_${shared_cpp_sources_suffix}" ] + } + } + + # Generate code for variants. + foreach(bindings_configuration, _bindings_configurations) { + cpp_only = false + if (defined(invoker.cpp_only)) { + cpp_only = invoker.cpp_only + } + variant_suffix = "" + if (defined(bindings_configuration.variant)) { + variant = bindings_configuration.variant + variant_suffix = "_${variant}" + cpp_only = true + } + type_mappings_target_name = "${target_name}${variant_suffix}__type_mappings" + type_mappings_path = + "$target_gen_dir/${target_name}${variant_suffix}__type_mappings" + active_typemaps = [] + enabled_sources = [] + if (defined(invoker.sources)) { + generator_cpp_outputs = [] + generator_js_outputs = [] + generator_java_outputs = [] + variant_dash_suffix = "" + if (defined(variant)) { + variant_dash_suffix = "-${variant}" + } + generator_cpp_outputs += [ + "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc", + "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.h", + ] + enabled_sources = [] + if (defined(bindings_configuration.blacklist)) { + foreach(source, invoker.sources) { + blacklisted = false + foreach(blacklisted_source, bindings_configuration.blacklist) { + if (get_path_info(source, "abspath") == blacklisted_source) { + blacklisted = true + } + } + if (!blacklisted) { + enabled_sources += [ source ] + } + } + } else { + enabled_sources = invoker.sources + } + foreach(source, enabled_sources) { + # TODO(sammc): Use a map instead of a linear scan when GN supports maps. + foreach(typemap, bindings_configuration.typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + if (get_path_info(source, "abspath") == _typemap_config.mojom) { + active_typemaps += [ typemap ] + } + } + if (is_mac && defined(bindings_configuration.typemaps_mac)) { + foreach(typemap, bindings_configuration.typemaps_mac) { + _typemap_config = { + } + _typemap_config = typemap.config + if (get_path_info(source, "abspath") == _typemap_config.mojom) { + active_typemaps += [ typemap ] + } + } + } + } + + if (!cpp_only) { + generator_js_outputs = + [ "{{source_gen_dir}}/{{source_name_part}}.mojom.js" ] + generator_java_outputs = + [ "{{source_gen_dir}}/{{source_name_part}}.mojom.srcjar" ] + } + generator_target_name = "${target_name}${variant_suffix}__generator" + action_foreach(generator_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + sources = invoker.sources + deps = [ + ":$type_mappings_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + outputs = generator_cpp_outputs + generator_java_outputs + + generator_js_outputs + args = common_generator_args + + if (cpp_only) { + args += [ + "-g", + "c++", + ] + } else { + args += [ + "-g", + "c++,javascript,java", + ] + } + + if (defined(bindings_configuration.variant)) { + args += [ + "--variant", + bindings_configuration.variant, + ] + } + depfile = + "{{source_gen_dir}}/${generator_target_name}_{{source_name_part}}.d" + args += [ + "--depfile", + depfile, + "--depfile_target", + "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc", + ] + + args += [ + "--typemap", + rebase_path(type_mappings_path, root_build_dir), + ] + + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + args += [ "--for_blink" ] + if (defined(invoker.export_class_attribute_blink)) { + args += [ + "--export_attribute", + invoker.export_class_attribute_blink, + "--export_header", + invoker.export_header_blink, + ] + } + } else { + if (defined(invoker.export_class_attribute)) { + args += [ + "--export_attribute", + invoker.export_class_attribute, + "--export_header", + invoker.export_header, + ] + } + } + + if (defined(invoker.use_once_callback) && invoker.use_once_callback) { + args += [ "--use_once_callback" ] + } + + if (defined(invoker.use_new_js_bindings) && + invoker.use_new_js_bindings) { + args += [ "--use_new_js_bindings" ] + } + } + } + + action(type_mappings_target_name) { + inputs = _bindings_configuration_files + outputs = [ + type_mappings_path, + ] + script = "$mojom_generator_root/generate_type_mappings.py" + deps = [] + args = [ + "--output", + rebase_path(type_mappings_path, root_build_dir), + ] + + foreach(d, all_deps) { + name = get_label_info(d, "label_no_toolchain") + toolchain = get_label_info(d, "toolchain") + dependency_output = "${name}${variant_suffix}__type_mappings" + dependency_target = "${dependency_output}(${toolchain})" + deps += [ dependency_target ] + dependency_output_dir = + get_label_info(dependency_output, "target_gen_dir") + dependency_name = get_label_info(dependency_output, "name") + dependency_path = + rebase_path("$dependency_output_dir/${dependency_name}", + root_build_dir) + args += [ + "--dependency", + dependency_path, + ] + } + + if (enabled_sources != []) { + # TODO(sammc): Pass the typemap description in a file to avoid command + # line length limitations. + typemap_description = [] + foreach(typemap, active_typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + typemap_description += [ "--start-typemap" ] + if (defined(_typemap_config.public_headers)) { + foreach(value, _typemap_config.public_headers) { + typemap_description += [ "public_headers=$value" ] + } + } + if (defined(_typemap_config.traits_headers)) { + foreach(value, _typemap_config.traits_headers) { + typemap_description += [ "traits_headers=$value" ] + } + } + foreach(value, _typemap_config.type_mappings) { + typemap_description += [ "type_mappings=$value" ] + } + + # The typemap configuration files are not actually used as inputs here + # but this establishes a necessary build dependency to ensure that + # typemap changes force a rebuild of affected targets. + inputs += [ typemap.filename ] + } + args += typemap_description + } + } + + source_set("${target_name}${variant_suffix}") { + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink && + defined(invoker.visibility_blink)) { + visibility = invoker.visibility_blink + } else if (defined(invoker.visibility)) { + visibility = invoker.visibility + } + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + if (defined(invoker.sources) && !defined(bindings_configuration.variant)) { + data = process_file_template(enabled_sources, generator_js_outputs) + } + defines = [] + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + if (defined(invoker.export_define)) { + defines += [ invoker.export_define ] + } + if (defined(invoker.export_define_blink)) { + defines += [ invoker.export_define_blink ] + } + if (enabled_sources != []) { + sources = process_file_template(enabled_sources, generator_cpp_outputs) + } + deps = [ + "//mojo/public/cpp/bindings:struct_traits", + "//mojo/public/interfaces/bindings:bindings__generator", + "//mojo/public/interfaces/bindings:bindings_shared__generator", + ] + public_deps = [ + ":$shared_cpp_sources_target_name", + "//base", + "//mojo/public/cpp/bindings", + ] + if (enabled_sources != []) { + public_deps += [ ":$generator_target_name" ] + } + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix to + # get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}${variant_suffix}" ] + } + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + if (defined(invoker.overridden_deps_blink)) { + foreach(d, invoker.overridden_deps_blink) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix + # to get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps -= [ "${full_name}${variant_suffix}" ] + } + public_deps += invoker.component_deps_blink + } + } else { + if (defined(invoker.overridden_deps)) { + foreach(d, invoker.overridden_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix + # to get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps -= [ "${full_name}${variant_suffix}" ] + } + public_deps += invoker.component_deps + } + } + foreach(typemap, active_typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + if (defined(_typemap_config.public_headers)) { + sources += _typemap_config.public_headers + } + if (defined(_typemap_config.traits_headers)) { + sources += _typemap_config.traits_headers + } + if (defined(_typemap_config.sources)) { + sources += _typemap_config.sources + } + if (defined(_typemap_config.public_deps)) { + public_deps += _typemap_config.public_deps + } + if (defined(_typemap_config.deps)) { + deps += _typemap_config.deps + } + } + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ] + } + } + + if (!cpp_only && is_android) { + import("//build/config/android/rules.gni") + + java_srcjar_target_name = target_name + "_java_sources" + action(java_srcjar_target_name) { + script = "//mojo/public/tools/gn/zip.py" + inputs = [] + if (enabled_sources != []) { + inputs = + process_file_template(enabled_sources, generator_java_outputs) + } + output = "$target_gen_dir/$target_name.srcjar" + outputs = [ + output, + ] + rebase_inputs = rebase_path(inputs, root_build_dir) + rebase_output = rebase_path(output, root_build_dir) + args = [ + "--zip-inputs=$rebase_inputs", + "--output=$rebase_output", + ] + deps = [] + if (enabled_sources != []) { + deps = [ + ":$generator_target_name", + ] + } + } + + java_target_name = target_name + "_java" + android_library(java_target_name) { + deps = [ + "//base:base_java", + "//mojo/public/java:bindings_java", + "//mojo/public/java:system_java", + ] + + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append "_java" to get the java + # dependency name. + full_name = get_label_info(d, "label_no_toolchain") + deps += [ "${full_name}_java" ] + } + + srcjar_deps = [ ":$java_srcjar_target_name" ] + run_findbugs_override = false + } + } + } +} diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py new file mode 100755 index 0000000000..a9650d7764 --- /dev/null +++ b/mojo/public/tools/bindings/mojom_bindings_generator.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""The frontend for the Mojo bindings system.""" + + +import argparse +import imp +import json +import os +import pprint +import re +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +# Manually check for the command-line flag. (This isn't quite right, since it +# ignores, e.g., "--", but it's close enough.) +if "--use_bundled_pylibs" in sys.argv[1:]: + sys.path.insert(0, os.path.join(_GetDirAbove("mojo"), "third_party")) + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + "pylib")) + +from mojom.error import Error +import mojom.fileutil as fileutil +from mojom.generate import translate +from mojom.generate import template_expander +from mojom.parse.parser import Parse + + +_BUILTIN_GENERATORS = { + "c++": "mojom_cpp_generator.py", + "javascript": "mojom_js_generator.py", + "java": "mojom_java_generator.py", +} + + +def LoadGenerators(generators_string): + if not generators_string: + return [] # No generators. + + script_dir = os.path.dirname(os.path.abspath(__file__)) + generators = {} + for generator_name in [s.strip() for s in generators_string.split(",")]: + language = generator_name.lower() + if language in _BUILTIN_GENERATORS: + generator_name = os.path.join(script_dir, "generators", + _BUILTIN_GENERATORS[language]) + else: + print "Unknown generator name %s" % generator_name + sys.exit(1) + generator_module = imp.load_source(os.path.basename(generator_name)[:-3], + generator_name) + generators[language] = generator_module + return generators + + +def MakeImportStackMessage(imported_filename_stack): + """Make a (human-readable) message listing a chain of imports. (Returned + string begins with a newline (if nonempty) and does not end with one.)""" + return ''.join( + reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \ + zip(imported_filename_stack[1:], imported_filename_stack)])) + + +class RelativePath(object): + """Represents a path relative to the source tree.""" + def __init__(self, path, source_root): + self.path = path + self.source_root = source_root + + def relative_path(self): + return os.path.relpath(os.path.abspath(self.path), + os.path.abspath(self.source_root)) + + +def FindImportFile(rel_dir, file_name, search_rel_dirs): + """Finds |file_name| in either |rel_dir| or |search_rel_dirs|. Returns a + RelativePath with first file found, or an arbitrary non-existent file + otherwise.""" + for rel_search_dir in [rel_dir] + search_rel_dirs: + path = os.path.join(rel_search_dir.path, file_name) + if os.path.isfile(path): + return RelativePath(path, rel_search_dir.source_root) + return RelativePath(os.path.join(rel_dir.path, file_name), + rel_dir.source_root) + + +class MojomProcessor(object): + """Parses mojom files and creates ASTs for them. + + Attributes: + _processed_files: {Dict[str, mojom.generate.module.Module]} Mapping from + relative mojom filename paths to the module AST for that mojom file. + """ + def __init__(self, should_generate): + self._should_generate = should_generate + self._processed_files = {} + self._parsed_files = {} + self._typemap = {} + + def LoadTypemaps(self, typemaps): + # Support some very simple single-line comments in typemap JSON. + comment_expr = r"^\s*//.*$" + def no_comments(line): + return not re.match(comment_expr, line) + for filename in typemaps: + with open(filename) as f: + typemaps = json.loads("".join(filter(no_comments, f.readlines()))) + for language, typemap in typemaps.iteritems(): + language_map = self._typemap.get(language, {}) + language_map.update(typemap) + self._typemap[language] = language_map + + def ProcessFile(self, args, remaining_args, generator_modules, filename): + self._ParseFileAndImports(RelativePath(filename, args.depth), + args.import_directories, []) + + return self._GenerateModule(args, remaining_args, generator_modules, + RelativePath(filename, args.depth)) + + def _GenerateModule(self, args, remaining_args, generator_modules, + rel_filename): + # Return the already-generated module. + if rel_filename.path in self._processed_files: + return self._processed_files[rel_filename.path] + tree = self._parsed_files[rel_filename.path] + + dirname, name = os.path.split(rel_filename.path) + + # Process all our imports first and collect the module object for each. + # We use these to generate proper type info. + imports = {} + for parsed_imp in tree.import_list: + rel_import_file = FindImportFile( + RelativePath(dirname, rel_filename.source_root), + parsed_imp.import_filename, args.import_directories) + imports[parsed_imp.import_filename] = self._GenerateModule( + args, remaining_args, generator_modules, rel_import_file) + + module = translate.OrderedModule(tree, name, imports) + + # Set the path as relative to the source root. + module.path = rel_filename.relative_path() + + # Normalize to unix-style path here to keep the generators simpler. + module.path = module.path.replace('\\', '/') + + if self._should_generate(rel_filename.path): + for language, generator_module in generator_modules.iteritems(): + generator = generator_module.Generator( + module, args.output_dir, typemap=self._typemap.get(language, {}), + variant=args.variant, bytecode_path=args.bytecode_path, + for_blink=args.for_blink, + use_once_callback=args.use_once_callback, + use_new_js_bindings=args.use_new_js_bindings, + export_attribute=args.export_attribute, + export_header=args.export_header, + generate_non_variant_code=args.generate_non_variant_code) + filtered_args = [] + if hasattr(generator_module, 'GENERATOR_PREFIX'): + prefix = '--' + generator_module.GENERATOR_PREFIX + '_' + filtered_args = [arg for arg in remaining_args + if arg.startswith(prefix)] + generator.GenerateFiles(filtered_args) + + # Save result. + self._processed_files[rel_filename.path] = module + return module + + def _ParseFileAndImports(self, rel_filename, import_directories, + imported_filename_stack): + # Ignore already-parsed files. + if rel_filename.path in self._parsed_files: + return + + if rel_filename.path in imported_filename_stack: + print "%s: Error: Circular dependency" % rel_filename.path + \ + MakeImportStackMessage(imported_filename_stack + [rel_filename.path]) + sys.exit(1) + + try: + with open(rel_filename.path) as f: + source = f.read() + except IOError as e: + print "%s: Error: %s" % (rel_filename.path, e.strerror) + \ + MakeImportStackMessage(imported_filename_stack + [rel_filename.path]) + sys.exit(1) + + try: + tree = Parse(source, rel_filename.path) + except Error as e: + full_stack = imported_filename_stack + [rel_filename.path] + print str(e) + MakeImportStackMessage(full_stack) + sys.exit(1) + + dirname = os.path.split(rel_filename.path)[0] + for imp_entry in tree.import_list: + import_file_entry = FindImportFile( + RelativePath(dirname, rel_filename.source_root), + imp_entry.import_filename, import_directories) + self._ParseFileAndImports(import_file_entry, import_directories, + imported_filename_stack + [rel_filename.path]) + + self._parsed_files[rel_filename.path] = tree + + +def _Generate(args, remaining_args): + if args.variant == "none": + args.variant = None + + for idx, import_dir in enumerate(args.import_directories): + tokens = import_dir.split(":") + if len(tokens) >= 2: + args.import_directories[idx] = RelativePath(tokens[0], tokens[1]) + else: + args.import_directories[idx] = RelativePath(tokens[0], args.depth) + generator_modules = LoadGenerators(args.generators_string) + + fileutil.EnsureDirectoryExists(args.output_dir) + + processor = MojomProcessor(lambda filename: filename in args.filename) + processor.LoadTypemaps(set(args.typemaps)) + for filename in args.filename: + processor.ProcessFile(args, remaining_args, generator_modules, filename) + if args.depfile: + assert args.depfile_target + with open(args.depfile, 'w') as f: + f.write('%s: %s' % ( + args.depfile_target, + ' '.join(processor._parsed_files.keys()))) + + return 0 + + +def _Precompile(args, _): + generator_modules = LoadGenerators(",".join(_BUILTIN_GENERATORS.keys())) + + template_expander.PrecompileTemplates(generator_modules, args.output_dir) + return 0 + + + +def main(): + parser = argparse.ArgumentParser( + description="Generate bindings from mojom files.") + parser.add_argument("--use_bundled_pylibs", action="store_true", + help="use Python modules bundled in the SDK") + + subparsers = parser.add_subparsers() + generate_parser = subparsers.add_parser( + "generate", description="Generate bindings from mojom files.") + generate_parser.add_argument("filename", nargs="+", + help="mojom input file") + generate_parser.add_argument("-d", "--depth", dest="depth", default=".", + help="depth from source root") + generate_parser.add_argument("-o", "--output_dir", dest="output_dir", + default=".", + help="output directory for generated files") + generate_parser.add_argument("-g", "--generators", + dest="generators_string", + metavar="GENERATORS", + default="c++,javascript,java", + help="comma-separated list of generators") + generate_parser.add_argument( + "-I", dest="import_directories", action="append", metavar="directory", + default=[], + help="add a directory to be searched for import files. The depth from " + "source root can be specified for each import by appending it after " + "a colon") + generate_parser.add_argument("--typemap", action="append", metavar="TYPEMAP", + default=[], dest="typemaps", + help="apply TYPEMAP to generated output") + generate_parser.add_argument("--variant", dest="variant", default=None, + help="output a named variant of the bindings") + generate_parser.add_argument( + "--bytecode_path", type=str, required=True, help=( + "the path from which to load template bytecode; to generate template " + "bytecode, run %s precompile BYTECODE_PATH" % os.path.basename( + sys.argv[0]))) + generate_parser.add_argument("--for_blink", action="store_true", + help="Use WTF types as generated types for mojo " + "string/array/map.") + generate_parser.add_argument( + "--use_once_callback", action="store_true", + help="Use base::OnceCallback instead of base::RepeatingCallback.") + generate_parser.add_argument( + "--use_new_js_bindings", action="store_true", + help="Use the new module loading approach and the core API exposed by " + "Web IDL. This option only affects the JavaScript bindings.") + generate_parser.add_argument( + "--export_attribute", type=str, default="", + help="Optional attribute to specify on class declaration to export it " + "for the component build.") + generate_parser.add_argument( + "--export_header", type=str, default="", + help="Optional header to include in the generated headers to support the " + "component build.") + generate_parser.add_argument( + "--generate_non_variant_code", action="store_true", + help="Generate code that is shared by different variants.") + generate_parser.add_argument( + "--depfile", type=str, + help="A file into which the list of input files will be written.") + generate_parser.add_argument( + "--depfile_target", type=str, + help="The target name to use in the depfile.") + generate_parser.set_defaults(func=_Generate) + + precompile_parser = subparsers.add_parser("precompile", + description="Precompile templates for the mojom bindings generator.") + precompile_parser.add_argument( + "-o", "--output_dir", dest="output_dir", default=".", + help="output directory for precompiled templates") + precompile_parser.set_defaults(func=_Precompile) + + args, remaining_args = parser.parse_known_args() + return args.func(args, remaining_args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py new file mode 100644 index 0000000000..de388561cb --- /dev/null +++ b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py @@ -0,0 +1,23 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +from mojom_bindings_generator import MakeImportStackMessage + + +class MojoBindingsGeneratorTest(unittest.TestCase): + """Tests mojo_bindings_generator.""" + + def testMakeImportStackMessage(self): + """Tests MakeImportStackMessage().""" + self.assertEquals(MakeImportStackMessage(["x"]), "") + self.assertEquals(MakeImportStackMessage(["x", "y"]), + "\n y was imported by x") + self.assertEquals(MakeImportStackMessage(["x", "y", "z"]), + "\n z was imported by y\n y was imported by x") + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/public/tools/bindings/pylib/mojom/__init__.py b/mojo/public/tools/bindings/pylib/mojom/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom/error.py b/mojo/public/tools/bindings/pylib/mojom/error.py new file mode 100644 index 0000000000..99522b9507 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/error.py @@ -0,0 +1,27 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +class Error(Exception): + """Base class for Mojo IDL bindings parser/generator errors.""" + + def __init__(self, filename, message, lineno=None, addenda=None, **kwargs): + """|filename| is the (primary) file which caused the error, |message| is the + error message, |lineno| is the 1-based line number (or |None| if not + applicable/available), and |addenda| is a list of additional lines to append + to the final error message.""" + Exception.__init__(self, **kwargs) + self.filename = filename + self.message = message + self.lineno = lineno + self.addenda = addenda + + def __str__(self): + if self.lineno: + s = "%s:%d: Error: %s" % (self.filename, self.lineno, self.message) + else: + s = "%s: Error: %s" % (self.filename, self.message) + return "\n".join([s] + self.addenda) if self.addenda else s + + def __repr__(self): + return str(self) diff --git a/mojo/public/tools/bindings/pylib/mojom/fileutil.py b/mojo/public/tools/bindings/pylib/mojom/fileutil.py new file mode 100644 index 0000000000..b321e9f543 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/fileutil.py @@ -0,0 +1,18 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import errno +import os.path + +def EnsureDirectoryExists(path, always_try_to_create=False): + """A wrapper for os.makedirs that does not error if the directory already + exists. A different process could be racing to create this directory.""" + + if not os.path.exists(path) or always_try_to_create: + try: + os.makedirs(path) + except OSError as e: + # There may have been a race to create this directory. + if e.errno != errno.EEXIST: + raise diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py b/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py b/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py new file mode 100644 index 0000000000..c8b21f2629 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py @@ -0,0 +1,91 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Resolves the values used for constants and enums.""" + +from itertools import ifilter +import mojom.generate.module as mojom + +def ResolveConstants(module, expression_to_text): + in_progress = set() + computed = set() + + def GetResolvedValue(named_value): + assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue)) + if isinstance(named_value, mojom.EnumValue): + field = next(ifilter(lambda field: field.name == named_value.name, + named_value.enum.fields), None) + if not field: + raise RuntimeError( + 'Unable to get computed value for field %s of enum %s' % + (named_value.name, named_value.enum.name)) + if field not in computed: + ResolveEnum(named_value.enum) + return field.resolved_value + else: + ResolveConstant(named_value.constant) + named_value.resolved_value = named_value.constant.resolved_value + return named_value.resolved_value + + def ResolveConstant(constant): + if constant in computed: + return + if constant in in_progress: + raise RuntimeError('Circular dependency for constant: %s' % constant.name) + in_progress.add(constant) + if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)): + resolved_value = GetResolvedValue(constant.value) + else: + resolved_value = expression_to_text(constant.value) + constant.resolved_value = resolved_value + in_progress.remove(constant) + computed.add(constant) + + def ResolveEnum(enum): + def ResolveEnumField(enum, field, default_value): + if field in computed: + return + if field in in_progress: + raise RuntimeError('Circular dependency for enum: %s' % enum.name) + in_progress.add(field) + if field.value: + if isinstance(field.value, mojom.EnumValue): + resolved_value = GetResolvedValue(field.value) + elif isinstance(field.value, str): + resolved_value = int(field.value, 0) + else: + raise RuntimeError('Unexpected value: %s' % field.value) + else: + resolved_value = default_value + field.resolved_value = resolved_value + in_progress.remove(field) + computed.add(field) + + current_value = 0 + for field in enum.fields: + ResolveEnumField(enum, field, current_value) + current_value = field.resolved_value + 1 + + for constant in module.constants: + ResolveConstant(constant) + + for enum in module.enums: + ResolveEnum(enum) + + for struct in module.structs: + for constant in struct.constants: + ResolveConstant(constant) + for enum in struct.enums: + ResolveEnum(enum) + for field in struct.fields: + if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)): + field.default.resolved_value = GetResolvedValue(field.default) + + for interface in module.interfaces: + for constant in interface.constants: + ResolveConstant(constant) + for enum in interface.enums: + ResolveEnum(enum) + + return module diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py new file mode 100644 index 0000000000..0e64af78a1 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py @@ -0,0 +1,153 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Code shared by the various language-specific code generators.""" + +from functools import partial +import os.path +import re + +import module as mojom +import mojom.fileutil as fileutil +import pack + +def ExpectedArraySize(kind): + if mojom.IsArrayKind(kind): + return kind.length + return None + +def StudlyCapsToCamel(studly): + return studly[0].lower() + studly[1:] + +def UnderToCamel(under): + """Converts underscore_separated strings to CamelCase strings.""" + return ''.join(word.capitalize() for word in under.split('_')) + +def WriteFile(contents, full_path): + # Make sure the containing directory exists. + full_dir = os.path.dirname(full_path) + fileutil.EnsureDirectoryExists(full_dir) + + # Dump the data to disk. + with open(full_path, "w+") as f: + f.write(contents) + +class Generator(object): + # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all + # files to stdout. + def __init__(self, module, output_dir=None, typemap=None, variant=None, + bytecode_path=None, for_blink=False, use_once_callback=False, + use_new_js_bindings=False, export_attribute=None, + export_header=None, generate_non_variant_code=False): + self.module = module + self.output_dir = output_dir + self.typemap = typemap or {} + self.variant = variant + self.bytecode_path = bytecode_path + self.for_blink = for_blink + self.use_once_callback = use_once_callback + self.use_new_js_bindings = use_new_js_bindings + self.export_attribute = export_attribute + self.export_header = export_header + self.generate_non_variant_code = generate_non_variant_code + + def GetStructsFromMethods(self): + result = [] + for interface in self.module.interfaces: + for method in interface.methods: + result.append(self._GetStructFromMethod(method)) + if method.response_parameters != None: + result.append(self._GetResponseStructFromMethod(method)) + return result + + def GetStructs(self): + return map(partial(self._AddStructComputedData, True), self.module.structs) + + def GetUnions(self): + return map(self._AddUnionComputedData, self.module.unions) + + def GetInterfaces(self): + return map(self._AddInterfaceComputedData, self.module.interfaces) + + # Prepend the filename with a directory that matches the directory of the + # original .mojom file, relative to the import root. + def MatchMojomFilePath(self, filename): + return os.path.join(os.path.dirname(self.module.path), filename) + + def Write(self, contents, filename): + if self.output_dir is None: + print contents + return + full_path = os.path.join(self.output_dir, filename) + WriteFile(contents, full_path) + + def GenerateFiles(self, args): + raise NotImplementedError("Subclasses must override/implement this method") + + def GetJinjaParameters(self): + """Returns default constructor parameters for the jinja environment.""" + return {} + + def GetGlobals(self): + """Returns global mappings for the template generation.""" + return {} + + def _AddStructComputedData(self, exported, struct): + """Adds computed data to the given struct. The data is computed once and + used repeatedly in the generation process.""" + struct.packed = pack.PackedStruct(struct) + struct.bytes = pack.GetByteLayout(struct.packed) + struct.versions = pack.GetVersionInfo(struct.packed) + struct.exported = exported + return struct + + def _AddUnionComputedData(self, union): + """Adds computed data to the given union. The data is computed once and + used repeatedly in the generation process.""" + ordinal = 0 + for field in union.fields: + if field.ordinal is not None: + ordinal = field.ordinal + field.ordinal = ordinal + ordinal += 1 + return union + + def _AddInterfaceComputedData(self, interface): + """Adds computed data to the given interface. The data is computed once and + used repeatedly in the generation process.""" + interface.version = 0 + for method in interface.methods: + if method.min_version is not None: + interface.version = max(interface.version, method.min_version) + + method.param_struct = self._GetStructFromMethod(method) + interface.version = max(interface.version, + method.param_struct.versions[-1].version) + + if method.response_parameters is not None: + method.response_param_struct = self._GetResponseStructFromMethod(method) + interface.version = max( + interface.version, + method.response_param_struct.versions[-1].version) + else: + method.response_param_struct = None + return interface + + def _GetStructFromMethod(self, method): + """Converts a method's parameters into the fields of a struct.""" + params_class = "%s_%s_Params" % (method.interface.name, method.name) + struct = mojom.Struct(params_class, module=method.interface.module) + for param in method.parameters: + struct.AddField(param.name, param.kind, param.ordinal, + attributes=param.attributes) + return self._AddStructComputedData(False, struct) + + def _GetResponseStructFromMethod(self, method): + """Converts a method's response_parameters into the fields of a struct.""" + params_class = "%s_%s_ResponseParams" % (method.interface.name, method.name) + struct = mojom.Struct(params_class, module=method.interface.module) + for param in method.response_parameters: + struct.AddField(param.name, param.kind, param.ordinal, + attributes=param.attributes) + return self._AddStructComputedData(False, struct) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py new file mode 100644 index 0000000000..9966b0b7f8 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py @@ -0,0 +1,24 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +import module as mojom +import generator + +class TestGenerator(unittest.TestCase): + + def testGetUnionsAddsOrdinals(self): + module = mojom.Module() + union = module.AddUnion('a') + union.AddField('a', mojom.BOOL) + union.AddField('b', mojom.BOOL) + union.AddField('c', mojom.BOOL, ordinal=10) + union.AddField('d', mojom.BOOL) + + gen = generator.Generator(module) + union = gen.GetUnions()[0] + ordinals = [field.ordinal for field in union.fields] + + self.assertEquals([0, 1, 10, 11], ordinals) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module.py b/mojo/public/tools/bindings/pylib/mojom/generate/module.py new file mode 100644 index 0000000000..3a5f188e75 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/module.py @@ -0,0 +1,891 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This module's classes provide an interface to mojo modules. Modules are +# collections of interfaces and structs to be used by mojo ipc clients and +# servers. +# +# A simple interface would be created this way: +# module = mojom.generate.module.Module('Foo') +# interface = module.AddInterface('Bar') +# method = interface.AddMethod('Tat', 0) +# method.AddParameter('baz', 0, mojom.INT32) + + +# We use our own version of __repr__ when displaying the AST, as the +# AST currently doesn't capture which nodes are reference (e.g. to +# types) and which nodes are definitions. This allows us to e.g. print +# the definition of a struct when it's defined inside a module, but +# only print its name when it's referenced in e.g. a method parameter. +def Repr(obj, as_ref=True): + """A version of __repr__ that can distinguish references. + + Sometimes we like to print an object's full representation + (e.g. with its fields) and sometimes we just want to reference an + object that was printed in full elsewhere. This function allows us + to make that distinction. + + Args: + obj: The object whose string representation we compute. + as_ref: If True, use the short reference representation. + + Returns: + A str representation of |obj|. + """ + if hasattr(obj, 'Repr'): + return obj.Repr(as_ref=as_ref) + # Since we cannot implement Repr for existing container types, we + # handle them here. + elif isinstance(obj, list): + if not obj: + return '[]' + else: + return ('[\n%s\n]' % (',\n'.join(' %s' % Repr(elem, as_ref).replace( + '\n', '\n ') for elem in obj))) + elif isinstance(obj, dict): + if not obj: + return '{}' + else: + return ('{\n%s\n}' % (',\n'.join(' %s: %s' % ( + Repr(key, as_ref).replace('\n', '\n '), + Repr(val, as_ref).replace('\n', '\n ')) + for key, val in obj.iteritems()))) + else: + return repr(obj) + + +def GenericRepr(obj, names): + """Compute generic Repr for |obj| based on the attributes in |names|. + + Args: + obj: The object to compute a Repr for. + names: A dict from attribute names to include, to booleans + specifying whether those attributes should be shown as + references or not. + + Returns: + A str representation of |obj|. + """ + def ReprIndent(name, as_ref): + return ' %s=%s' % (name, Repr(getattr(obj, name), as_ref).replace( + '\n', '\n ')) + + return '%s(\n%s\n)' % ( + obj.__class__.__name__, + ',\n'.join(ReprIndent(name, as_ref) + for (name, as_ref) in names.iteritems())) + + +class Kind(object): + """Kind represents a type (e.g. int8, string). + + Attributes: + spec: A string uniquely identifying the type. May be None. + parent_kind: The enclosing type. For example, a struct defined + inside an interface has that interface as its parent. May be None. + """ + def __init__(self, spec=None): + self.spec = spec + self.parent_kind = None + + def Repr(self, as_ref=True): + return '<%s spec=%r>' % (self.__class__.__name__, self.spec) + + def __repr__(self): + # Gives us a decent __repr__ for all kinds. + return self.Repr() + + +class ReferenceKind(Kind): + """ReferenceKind represents pointer and handle types. + + A type is nullable if null (for pointer types) or invalid handle (for handle + types) is a legal value for the type. + + Attributes: + is_nullable: True if the type is nullable. + """ + + def __init__(self, spec=None, is_nullable=False): + assert spec is None or is_nullable == spec.startswith('?') + Kind.__init__(self, spec) + self.is_nullable = is_nullable + self.shared_definition = {} + + def Repr(self, as_ref=True): + return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec, + self.is_nullable) + + def MakeNullableKind(self): + assert not self.is_nullable + + if self == STRING: + return NULLABLE_STRING + if self == HANDLE: + return NULLABLE_HANDLE + if self == DCPIPE: + return NULLABLE_DCPIPE + if self == DPPIPE: + return NULLABLE_DPPIPE + if self == MSGPIPE: + return NULLABLE_MSGPIPE + if self == SHAREDBUFFER: + return NULLABLE_SHAREDBUFFER + + nullable_kind = type(self)() + nullable_kind.shared_definition = self.shared_definition + if self.spec is not None: + nullable_kind.spec = '?' + self.spec + nullable_kind.is_nullable = True + + return nullable_kind + + @classmethod + def AddSharedProperty(cls, name): + """Adds a property |name| to |cls|, which accesses the corresponding item in + |shared_definition|. + + The reason of adding such indirection is to enable sharing definition + between a reference kind and its nullable variation. For example: + a = Struct('test_struct_1') + b = a.MakeNullableKind() + a.name = 'test_struct_2' + print b.name # Outputs 'test_struct_2'. + """ + def Get(self): + return self.shared_definition[name] + + def Set(self, value): + self.shared_definition[name] = value + + setattr(cls, name, property(Get, Set)) + + +# Initialize the set of primitive types. These can be accessed by clients. +BOOL = Kind('b') +INT8 = Kind('i8') +INT16 = Kind('i16') +INT32 = Kind('i32') +INT64 = Kind('i64') +UINT8 = Kind('u8') +UINT16 = Kind('u16') +UINT32 = Kind('u32') +UINT64 = Kind('u64') +FLOAT = Kind('f') +DOUBLE = Kind('d') +STRING = ReferenceKind('s') +HANDLE = ReferenceKind('h') +DCPIPE = ReferenceKind('h:d:c') +DPPIPE = ReferenceKind('h:d:p') +MSGPIPE = ReferenceKind('h:m') +SHAREDBUFFER = ReferenceKind('h:s') +NULLABLE_STRING = ReferenceKind('?s', True) +NULLABLE_HANDLE = ReferenceKind('?h', True) +NULLABLE_DCPIPE = ReferenceKind('?h:d:c', True) +NULLABLE_DPPIPE = ReferenceKind('?h:d:p', True) +NULLABLE_MSGPIPE = ReferenceKind('?h:m', True) +NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True) + + +# Collection of all Primitive types +PRIMITIVES = ( + BOOL, + INT8, + INT16, + INT32, + INT64, + UINT8, + UINT16, + UINT32, + UINT64, + FLOAT, + DOUBLE, + STRING, + HANDLE, + DCPIPE, + DPPIPE, + MSGPIPE, + SHAREDBUFFER, + NULLABLE_STRING, + NULLABLE_HANDLE, + NULLABLE_DCPIPE, + NULLABLE_DPPIPE, + NULLABLE_MSGPIPE, + NULLABLE_SHAREDBUFFER +) + + +ATTRIBUTE_MIN_VERSION = 'MinVersion' +ATTRIBUTE_EXTENSIBLE = 'Extensible' +ATTRIBUTE_SYNC = 'Sync' + + +class NamedValue(object): + def __init__(self, module, parent_kind, name): + self.module = module + self.namespace = module.namespace + self.parent_kind = parent_kind + self.name = name + self.imported_from = None + + def GetSpec(self): + return (self.namespace + '.' + + (self.parent_kind and (self.parent_kind.name + '.') or "") + + self.name) + + +class BuiltinValue(object): + def __init__(self, value): + self.value = value + + +class ConstantValue(NamedValue): + def __init__(self, module, parent_kind, constant): + NamedValue.__init__(self, module, parent_kind, constant.name) + self.constant = constant + + +class EnumValue(NamedValue): + def __init__(self, module, enum, field): + NamedValue.__init__(self, module, enum.parent_kind, field.name) + self.enum = enum + + def GetSpec(self): + return (self.namespace + '.' + + (self.parent_kind and (self.parent_kind.name + '.') or "") + + self.enum.name + '.' + self.name) + + +class Constant(object): + def __init__(self, name=None, kind=None, value=None, parent_kind=None): + self.name = name + self.kind = kind + self.value = value + self.parent_kind = parent_kind + + +class Field(object): + def __init__(self, name=None, kind=None, ordinal=None, default=None, + attributes=None): + if self.__class__.__name__ == 'Field': + raise Exception() + self.name = name + self.kind = kind + self.ordinal = ordinal + self.default = default + self.attributes = attributes + + def Repr(self, as_ref=True): + # Fields are only referenced by objects which define them and thus + # they are always displayed as non-references. + return GenericRepr(self, {'name': False, 'kind': True}) + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + +class StructField(Field): pass + + +class UnionField(Field): pass + + +class Struct(ReferenceKind): + """A struct with typed fields. + + Attributes: + name: {str} The name of the struct type. + native_only: {bool} Does the struct have a body (i.e. any fields) or is it + purely a native struct. + module: {Module} The defining module. + imported_from: {dict} Information about where this union was + imported from. + fields: {List[StructField]} The members of the struct. + attributes: {dict} Additional information about the struct, such as + if it's a native struct. + """ + + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('native_only') + ReferenceKind.AddSharedProperty('module') + ReferenceKind.AddSharedProperty('imported_from') + ReferenceKind.AddSharedProperty('fields') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, name=None, module=None, attributes=None): + if name is not None: + spec = 'x:' + name + else: + spec = None + ReferenceKind.__init__(self, spec) + self.name = name + self.native_only = False + self.module = module + self.imported_from = None + self.fields = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r imported_from=%s>' % ( + self.__class__.__name__, self.name, + Repr(self.imported_from, as_ref=True)) + else: + return GenericRepr(self, {'name': False, 'fields': False, + 'imported_from': True}) + + def AddField(self, name, kind, ordinal=None, default=None, attributes=None): + field = StructField(name, kind, ordinal, default, attributes) + self.fields.append(field) + return field + + +class Union(ReferenceKind): + """A union of several kinds. + + Attributes: + name: {str} The name of the union type. + module: {Module} The defining module. + imported_from: {dict} Information about where this union was + imported from. + fields: {List[UnionField]} The members of the union. + attributes: {dict} Additional information about the union, such as + which Java class name to use to represent it in the generated + bindings. + """ + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('module') + ReferenceKind.AddSharedProperty('imported_from') + ReferenceKind.AddSharedProperty('fields') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, name=None, module=None, attributes=None): + if name is not None: + spec = 'x:' + name + else: + spec = None + ReferenceKind.__init__(self, spec) + self.name = name + self.module = module + self.imported_from = None + self.fields = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r fields=%s>' % ( + self.__class__.__name__, self.spec, self.is_nullable, + Repr(self.fields)) + else: + return GenericRepr(self, {'fields': True, 'is_nullable': False}) + + def AddField(self, name, kind, ordinal=None, attributes=None): + field = UnionField(name, kind, ordinal, None, attributes) + self.fields.append(field) + return field + + +class Array(ReferenceKind): + """An array. + + Attributes: + kind: {Kind} The type of the elements. May be None. + length: The number of elements. None if unknown. + """ + + ReferenceKind.AddSharedProperty('kind') + ReferenceKind.AddSharedProperty('length') + + def __init__(self, kind=None, length=None): + if kind is not None: + if length is not None: + spec = 'a%d:%s' % (length, kind.spec) + else: + spec = 'a:%s' % kind.spec + + ReferenceKind.__init__(self, spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + self.length = length + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % ( + self.__class__.__name__, self.spec, self.is_nullable, Repr(self.kind), + self.length) + else: + return GenericRepr(self, {'kind': True, 'length': False, + 'is_nullable': False}) + + +class Map(ReferenceKind): + """A map. + + Attributes: + key_kind: {Kind} The type of the keys. May be None. + value_kind: {Kind} The type of the elements. May be None. + """ + ReferenceKind.AddSharedProperty('key_kind') + ReferenceKind.AddSharedProperty('value_kind') + + def __init__(self, key_kind=None, value_kind=None): + if (key_kind is not None and value_kind is not None): + ReferenceKind.__init__(self, + 'm[' + key_kind.spec + '][' + value_kind.spec + + ']') + if IsNullableKind(key_kind): + raise Exception("Nullable kinds cannot be keys in maps.") + if IsAnyHandleKind(key_kind): + raise Exception("Handles cannot be keys in maps.") + if IsAnyInterfaceKind(key_kind): + raise Exception("Interfaces cannot be keys in maps.") + if IsArrayKind(key_kind): + raise Exception("Arrays cannot be keys in maps.") + else: + ReferenceKind.__init__(self) + + self.key_kind = key_kind + self.value_kind = value_kind + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % ( + self.__class__.__name__, self.spec, self.is_nullable, + Repr(self.key_kind), Repr(self.value_kind)) + else: + return GenericRepr(self, {'key_kind': True, 'value_kind': True}) + + +class InterfaceRequest(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + "Interface request requires %r to be an interface." % kind.spec) + ReferenceKind.__init__(self, 'r:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + +class AssociatedInterfaceRequest(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, InterfaceRequest): + raise Exception( + "Associated interface request requires %r to be an interface " + "request." % kind.spec) + assert not kind.is_nullable + ReferenceKind.__init__(self, 'asso:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind.kind if kind is not None else None + + +class Parameter(object): + def __init__(self, name=None, kind=None, ordinal=None, default=None, + attributes=None): + self.name = name + self.ordinal = ordinal + self.kind = kind + self.default = default + self.attributes = attributes + + def Repr(self, as_ref=True): + return '<%s name=%r kind=%s>' % (self.__class__.__name__, self.name, + self.kind.Repr(as_ref=True)) + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + +class Method(object): + def __init__(self, interface, name, ordinal=None, attributes=None): + self.interface = interface + self.name = name + self.ordinal = ordinal + self.parameters = [] + self.response_parameters = None + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r>' % (self.__class__.__name__, self.name) + else: + return GenericRepr(self, {'name': False, 'parameters': True, + 'response_parameters': True}) + + def AddParameter(self, name, kind, ordinal=None, default=None, + attributes=None): + parameter = Parameter(name, kind, ordinal, default, attributes) + self.parameters.append(parameter) + return parameter + + def AddResponseParameter(self, name, kind, ordinal=None, default=None, + attributes=None): + if self.response_parameters == None: + self.response_parameters = [] + parameter = Parameter(name, kind, ordinal, default, attributes) + self.response_parameters.append(parameter) + return parameter + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + @property + def sync(self): + return self.attributes.get(ATTRIBUTE_SYNC) \ + if self.attributes else None + + +class Interface(ReferenceKind): + ReferenceKind.AddSharedProperty('module') + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('imported_from') + ReferenceKind.AddSharedProperty('methods') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, name=None, module=None, attributes=None): + if name is not None: + spec = 'x:' + name + else: + spec = None + ReferenceKind.__init__(self, spec) + self.module = module + self.name = name + self.imported_from = None + self.methods = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r>' % (self.__class__.__name__, self.name) + else: + return GenericRepr(self, {'name': False, 'attributes': False, + 'methods': False}) + + def AddMethod(self, name, ordinal=None, attributes=None): + method = Method(self, name, ordinal, attributes) + self.methods.append(method) + return method + + # TODO(451323): Remove when the language backends no longer rely on this. + @property + def client(self): + return None + + +class AssociatedInterface(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + "Associated interface requires %r to be an interface." % kind.spec) + assert not kind.is_nullable + ReferenceKind.__init__(self, 'asso:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + +class EnumField(object): + def __init__(self, name=None, value=None, attributes=None, + numeric_value=None): + self.name = name + self.value = value + self.attributes = attributes + self.numeric_value = numeric_value + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + +class Enum(Kind): + def __init__(self, name=None, module=None, attributes=None): + self.module = module + self.name = name + self.native_only = False + self.imported_from = None + if name is not None: + spec = 'x:' + name + else: + spec = None + Kind.__init__(self, spec) + self.fields = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r>' % (self.__class__.__name__, self.name) + else: + return GenericRepr(self, {'name': False, 'fields': False}) + + @property + def extensible(self): + return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \ + if self.attributes else False + + +class Module(object): + def __init__(self, name=None, namespace=None, attributes=None): + self.name = name + self.path = name + self.namespace = namespace + self.structs = [] + self.unions = [] + self.interfaces = [] + self.kinds = {} + self.attributes = attributes + + def __repr__(self): + # Gives us a decent __repr__ for modules. + return self.Repr() + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r namespace=%r>' % ( + self.__class__.__name__, self.name, self.namespace) + else: + return GenericRepr(self, {'name': False, 'namespace': False, + 'attributes': False, 'structs': False, + 'interfaces': False, 'unions': False}) + + def AddInterface(self, name, attributes=None): + interface = Interface(name, self, attributes) + self.interfaces.append(interface) + return interface + + def AddStruct(self, name, attributes=None): + struct = Struct(name, self, attributes) + self.structs.append(struct) + return struct + + def AddUnion(self, name, attributes=None): + union = Union(name, self, attributes) + self.unions.append(union) + return union + + +def IsBoolKind(kind): + return kind.spec == BOOL.spec + + +def IsFloatKind(kind): + return kind.spec == FLOAT.spec + + +def IsDoubleKind(kind): + return kind.spec == DOUBLE.spec + + +def IsIntegralKind(kind): + return (kind.spec == BOOL.spec or + kind.spec == INT8.spec or + kind.spec == INT16.spec or + kind.spec == INT32.spec or + kind.spec == INT64.spec or + kind.spec == UINT8.spec or + kind.spec == UINT16.spec or + kind.spec == UINT32.spec or + kind.spec == UINT64.spec) + + +def IsStringKind(kind): + return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec + + +def IsGenericHandleKind(kind): + return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec + + +def IsDataPipeConsumerKind(kind): + return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec + + +def IsDataPipeProducerKind(kind): + return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec + + +def IsMessagePipeKind(kind): + return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec + + +def IsSharedBufferKind(kind): + return (kind.spec == SHAREDBUFFER.spec or + kind.spec == NULLABLE_SHAREDBUFFER.spec) + + +def IsStructKind(kind): + return isinstance(kind, Struct) + + +def IsUnionKind(kind): + return isinstance(kind, Union) + + +def IsArrayKind(kind): + return isinstance(kind, Array) + + +def IsInterfaceKind(kind): + return isinstance(kind, Interface) + + +def IsAssociatedInterfaceKind(kind): + return isinstance(kind, AssociatedInterface) + + +def IsInterfaceRequestKind(kind): + return isinstance(kind, InterfaceRequest) + + +def IsAssociatedInterfaceRequestKind(kind): + return isinstance(kind, AssociatedInterfaceRequest) + + +def IsEnumKind(kind): + return isinstance(kind, Enum) + + +def IsReferenceKind(kind): + return isinstance(kind, ReferenceKind) + + +def IsNullableKind(kind): + return IsReferenceKind(kind) and kind.is_nullable + + +def IsMapKind(kind): + return isinstance(kind, Map) + + +def IsObjectKind(kind): + return IsPointerKind(kind) or IsUnionKind(kind) + + +def IsPointerKind(kind): + return (IsStructKind(kind) or IsArrayKind(kind) or IsStringKind(kind) or + IsMapKind(kind)) + + +# Please note that it doesn't include any interface kind. +def IsAnyHandleKind(kind): + return (IsGenericHandleKind(kind) or + IsDataPipeConsumerKind(kind) or + IsDataPipeProducerKind(kind) or + IsMessagePipeKind(kind) or + IsSharedBufferKind(kind)) + + +def IsAnyInterfaceKind(kind): + return (IsInterfaceKind(kind) or IsInterfaceRequestKind(kind) or + IsAssociatedKind(kind)) + + +def IsAnyHandleOrInterfaceKind(kind): + return IsAnyHandleKind(kind) or IsAnyInterfaceKind(kind) + + +def IsAssociatedKind(kind): + return (IsAssociatedInterfaceKind(kind) or + IsAssociatedInterfaceRequestKind(kind)) + + +def HasCallbacks(interface): + for method in interface.methods: + if method.response_parameters != None: + return True + return False + + +# Finds out whether an interface passes associated interfaces and associated +# interface requests. +def PassesAssociatedKinds(interface): + def _ContainsAssociatedKinds(kind, visited_kinds): + if kind in visited_kinds: + # No need to examine the kind again. + return False + visited_kinds.add(kind) + if IsAssociatedKind(kind): + return True + if IsArrayKind(kind): + return _ContainsAssociatedKinds(kind.kind, visited_kinds) + if IsStructKind(kind) or IsUnionKind(kind): + for field in kind.fields: + if _ContainsAssociatedKinds(field.kind, visited_kinds): + return True + if IsMapKind(kind): + # No need to examine the key kind, only primitive kinds and non-nullable + # string are allowed to be key kinds. + return _ContainsAssociatedKinds(kind.value_kind, visited_kinds) + return False + + visited_kinds = set() + for method in interface.methods: + for param in method.parameters: + if _ContainsAssociatedKinds(param.kind, visited_kinds): + return True + if method.response_parameters != None: + for param in method.response_parameters: + if _ContainsAssociatedKinds(param.kind, visited_kinds): + return True + return False + + +def HasSyncMethods(interface): + for method in interface.methods: + if method.sync: + return True + return False + + +def ContainsHandlesOrInterfaces(kind): + """Check if the kind contains any handles. + + This check is recursive so it checks all struct fields, containers elements, + etc. + + Args: + struct: {Kind} The kind to check. + + Returns: + {bool}: True if the kind contains handles. + """ + # We remember the types we already checked to avoid infinite recursion when + # checking recursive (or mutually recursive) types: + checked = set() + def Check(kind): + if kind.spec in checked: + return False + checked.add(kind.spec) + if IsStructKind(kind): + return any(Check(field.kind) for field in kind.fields) + elif IsUnionKind(kind): + return any(Check(field.kind) for field in kind.fields) + elif IsAnyHandleKind(kind): + return True + elif IsAnyInterfaceKind(kind): + return True + elif IsArrayKind(kind): + return Check(kind.kind) + elif IsMapKind(kind): + return Check(kind.key_kind) or Check(kind.value_kind) + else: + return False + return Check(kind) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py new file mode 100644 index 0000000000..a887686e1b --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py @@ -0,0 +1,34 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys + +import test_support + +EXPECT_EQ = test_support.EXPECT_EQ +EXPECT_TRUE = test_support.EXPECT_TRUE +RunTest = test_support.RunTest +ModulesAreEqual = test_support.ModulesAreEqual +BuildTestModule = test_support.BuildTestModule +TestTestModule = test_support.TestTestModule + + +def BuildAndTestModule(): + return TestTestModule(BuildTestModule()) + + +def TestModulesEqual(): + return EXPECT_TRUE(ModulesAreEqual(BuildTestModule(), BuildTestModule())) + + +def Main(args): + errors = 0 + errors += RunTest(BuildAndTestModule) + errors += RunTest(TestModulesEqual) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/pack.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py new file mode 100644 index 0000000000..37dc8f396b --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py @@ -0,0 +1,250 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import module as mojom + +# This module provides a mechanism for determining the packed order and offsets +# of a mojom.Struct. +# +# ps = pack.PackedStruct(struct) +# ps.packed_fields will access a list of PackedField objects, each of which +# will have an offset, a size and a bit (for mojom.BOOLs). + +# Size of struct header in bytes: num_bytes [4B] + version [4B]. +HEADER_SIZE = 8 + +class PackedField(object): + kind_to_size = { + mojom.BOOL: 1, + mojom.INT8: 1, + mojom.UINT8: 1, + mojom.INT16: 2, + mojom.UINT16: 2, + mojom.INT32: 4, + mojom.UINT32: 4, + mojom.FLOAT: 4, + mojom.HANDLE: 4, + mojom.MSGPIPE: 4, + mojom.SHAREDBUFFER: 4, + mojom.DCPIPE: 4, + mojom.DPPIPE: 4, + mojom.NULLABLE_HANDLE: 4, + mojom.NULLABLE_MSGPIPE: 4, + mojom.NULLABLE_SHAREDBUFFER: 4, + mojom.NULLABLE_DCPIPE: 4, + mojom.NULLABLE_DPPIPE: 4, + mojom.INT64: 8, + mojom.UINT64: 8, + mojom.DOUBLE: 8, + mojom.STRING: 8, + mojom.NULLABLE_STRING: 8 + } + + @classmethod + def GetSizeForKind(cls, kind): + if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct, + mojom.Interface, mojom.AssociatedInterface)): + return 8 + if isinstance(kind, mojom.Union): + return 16 + if isinstance(kind, mojom.InterfaceRequest): + kind = mojom.MSGPIPE + if isinstance(kind, mojom.AssociatedInterfaceRequest): + return 4 + if isinstance(kind, mojom.Enum): + # TODO(mpcomplete): what about big enums? + return cls.kind_to_size[mojom.INT32] + if not kind in cls.kind_to_size: + raise Exception("Invalid kind: %s" % kind.spec) + return cls.kind_to_size[kind] + + @classmethod + def GetAlignmentForKind(cls, kind): + if isinstance(kind, (mojom.Interface, mojom.AssociatedInterface)): + return 4 + if isinstance(kind, mojom.Union): + return 8 + return cls.GetSizeForKind(kind) + + def __init__(self, field, index, ordinal): + """ + Args: + field: the original field. + index: the position of the original field in the struct. + ordinal: the ordinal of the field for serialization. + """ + self.field = field + self.index = index + self.ordinal = ordinal + self.size = self.GetSizeForKind(field.kind) + self.alignment = self.GetAlignmentForKind(field.kind) + self.offset = None + self.bit = None + self.min_version = None + + +def GetPad(offset, alignment): + """Returns the pad necessary to reserve space so that |offset + pad| equals to + some multiple of |alignment|.""" + return (alignment - (offset % alignment)) % alignment + + +def GetFieldOffset(field, last_field): + """Returns a 2-tuple of the field offset and bit (for BOOLs).""" + if (field.field.kind == mojom.BOOL and + last_field.field.kind == mojom.BOOL and + last_field.bit < 7): + return (last_field.offset, last_field.bit + 1) + + offset = last_field.offset + last_field.size + pad = GetPad(offset, field.alignment) + return (offset + pad, 0) + + +def GetPayloadSizeUpToField(field): + """Returns the payload size (not including struct header) if |field| is the + last field. + """ + if not field: + return 0 + offset = field.offset + field.size + pad = GetPad(offset, 8) + return offset + pad + + +class PackedStruct(object): + def __init__(self, struct): + self.struct = struct + # |packed_fields| contains all the fields, in increasing offset order. + self.packed_fields = [] + # |packed_fields_in_ordinal_order| refers to the same fields as + # |packed_fields|, but in ordinal order. + self.packed_fields_in_ordinal_order = [] + + # No fields. + if (len(struct.fields) == 0): + return + + # Start by sorting by ordinal. + src_fields = self.packed_fields_in_ordinal_order + ordinal = 0 + for index, field in enumerate(struct.fields): + if field.ordinal is not None: + ordinal = field.ordinal + src_fields.append(PackedField(field, index, ordinal)) + ordinal += 1 + src_fields.sort(key=lambda field: field.ordinal) + + # Set |min_version| for each field. + next_min_version = 0 + for packed_field in src_fields: + if packed_field.field.min_version is None: + assert next_min_version == 0 + else: + assert packed_field.field.min_version >= next_min_version + next_min_version = packed_field.field.min_version + packed_field.min_version = next_min_version + + if (packed_field.min_version != 0 and + mojom.IsReferenceKind(packed_field.field.kind) and + not packed_field.field.kind.is_nullable): + raise Exception("Non-nullable fields are only allowed in version 0 of " + "a struct. %s.%s is defined with [MinVersion=%d]." + % (self.struct.name, packed_field.field.name, + packed_field.min_version)) + + src_field = src_fields[0] + src_field.offset = 0 + src_field.bit = 0 + dst_fields = self.packed_fields + dst_fields.append(src_field) + + # Then find first slot that each field will fit. + for src_field in src_fields[1:]: + last_field = dst_fields[0] + for i in xrange(1, len(dst_fields)): + next_field = dst_fields[i] + offset, bit = GetFieldOffset(src_field, last_field) + if offset + src_field.size <= next_field.offset: + # Found hole. + src_field.offset = offset + src_field.bit = bit + dst_fields.insert(i, src_field) + break + last_field = next_field + if src_field.offset is None: + # Add to end + src_field.offset, src_field.bit = GetFieldOffset(src_field, last_field) + dst_fields.append(src_field) + + +class ByteInfo(object): + def __init__(self): + self.is_padding = False + self.packed_fields = [] + + +def GetByteLayout(packed_struct): + total_payload_size = GetPayloadSizeUpToField( + packed_struct.packed_fields[-1] if packed_struct.packed_fields else None) + bytes = [ByteInfo() for i in xrange(total_payload_size)] + + limit_of_previous_field = 0 + for packed_field in packed_struct.packed_fields: + for i in xrange(limit_of_previous_field, packed_field.offset): + bytes[i].is_padding = True + bytes[packed_field.offset].packed_fields.append(packed_field) + limit_of_previous_field = packed_field.offset + packed_field.size + + for i in xrange(limit_of_previous_field, len(bytes)): + bytes[i].is_padding = True + + for byte in bytes: + # A given byte cannot both be padding and have a fields packed into it. + assert not (byte.is_padding and byte.packed_fields) + + return bytes + + +class VersionInfo(object): + def __init__(self, version, num_fields, num_bytes): + self.version = version + self.num_fields = num_fields + self.num_bytes = num_bytes + + +def GetVersionInfo(packed_struct): + """Get version information for a struct. + + Args: + packed_struct: A PackedStruct instance. + + Returns: + A non-empty list of VersionInfo instances, sorted by version in increasing + order. + Note: The version numbers may not be consecutive. + """ + versions = [] + last_version = 0 + last_num_fields = 0 + last_payload_size = 0 + + for packed_field in packed_struct.packed_fields_in_ordinal_order: + if packed_field.min_version != last_version: + versions.append( + VersionInfo(last_version, last_num_fields, + last_payload_size + HEADER_SIZE)) + last_version = packed_field.min_version + + last_num_fields += 1 + # The fields are iterated in ordinal order here. However, the size of a + # version is determined by the last field of that version in pack order, + # instead of ordinal order. Therefore, we need to calculate the max value. + last_payload_size = max(GetPayloadSizeUpToField(packed_field), + last_payload_size) + + assert len(versions) == 0 or last_num_fields != versions[-1].num_fields + versions.append(VersionInfo(last_version, last_num_fields, + last_payload_size + HEADER_SIZE)) + return versions diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py new file mode 100644 index 0000000000..14f699da34 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py @@ -0,0 +1,193 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys + +import module as mojom +import pack +import test_support + + +EXPECT_EQ = test_support.EXPECT_EQ +EXPECT_TRUE = test_support.EXPECT_TRUE +RunTest = test_support.RunTest + + +def TestOrdinalOrder(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT32, 2) + struct.AddField('testfield2', mojom.INT32, 1) + ps = pack.PackedStruct(struct) + + errors += EXPECT_EQ(2, len(ps.packed_fields)) + errors += EXPECT_EQ('testfield2', ps.packed_fields[0].field.name) + errors += EXPECT_EQ('testfield1', ps.packed_fields[1].field.name) + + return errors + +def TestZeroFields(): + errors = 0 + struct = mojom.Struct('test') + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(0, len(ps.packed_fields)) + return errors + + +def TestOneField(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT8) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(1, len(ps.packed_fields)) + return errors + +# Pass three tuples. +# |kinds| is a sequence of mojom.Kinds that specify the fields that are to +# be created. +# |fields| is the expected order of the resulting fields, with the integer +# "1" first. +# |offsets| is the expected order of offsets, with the integer "0" first. +def TestSequence(kinds, fields, offsets): + errors = 0 + struct = mojom.Struct('test') + index = 1 + for kind in kinds: + struct.AddField("%d" % index, kind) + index += 1 + ps = pack.PackedStruct(struct) + num_fields = len(ps.packed_fields) + errors += EXPECT_EQ(len(kinds), num_fields) + for i in xrange(num_fields): + EXPECT_EQ("%d" % fields[i], ps.packed_fields[i].field.name) + EXPECT_EQ(offsets[i], ps.packed_fields[i].offset) + + return errors + + +def TestPaddingPackedInOrder(): + return TestSequence( + (mojom.INT8, mojom.UINT8, mojom.INT32), + (1, 2, 3), + (0, 1, 4)) + + +def TestPaddingPackedOutOfOrder(): + return TestSequence( + (mojom.INT8, mojom.INT32, mojom.UINT8), + (1, 3, 2), + (0, 1, 4)) + + +def TestPaddingPackedOverflow(): + kinds = (mojom.INT8, mojom.INT32, mojom.INT16, mojom.INT8, mojom.INT8) + # 2 bytes should be packed together first, followed by short, then by int. + fields = (1, 4, 3, 2, 5) + offsets = (0, 1, 2, 4, 8) + return TestSequence(kinds, fields, offsets) + + +def TestNullableTypes(): + kinds = (mojom.STRING.MakeNullableKind(), + mojom.HANDLE.MakeNullableKind(), + mojom.Struct('test_struct').MakeNullableKind(), + mojom.DCPIPE.MakeNullableKind(), + mojom.Array().MakeNullableKind(), + mojom.DPPIPE.MakeNullableKind(), + mojom.Array(length=5).MakeNullableKind(), + mojom.MSGPIPE.MakeNullableKind(), + mojom.Interface('test_inteface').MakeNullableKind(), + mojom.SHAREDBUFFER.MakeNullableKind(), + mojom.InterfaceRequest().MakeNullableKind()) + fields = (1, 2, 4, 3, 5, 6, 8, 7, 9, 10, 11) + offsets = (0, 8, 12, 16, 24, 32, 36, 40, 48, 52, 56) + return TestSequence(kinds, fields, offsets) + + +def TestAllTypes(): + return TestSequence( + (mojom.BOOL, mojom.INT8, mojom.STRING, mojom.UINT8, + mojom.INT16, mojom.DOUBLE, mojom.UINT16, + mojom.INT32, mojom.UINT32, mojom.INT64, + mojom.FLOAT, mojom.STRING, mojom.HANDLE, + mojom.UINT64, mojom.Struct('test'), mojom.Array(), + mojom.STRING.MakeNullableKind()), + (1, 2, 4, 5, 7, 3, 6, 8, 9, 10, 11, 13, 12, 14, 15, 16, 17, 18), + (0, 1, 2, 4, 6, 8, 16, 24, 28, 32, 40, 44, 48, 56, 64, 72, 80, 88)) + + +def TestPaddingPackedOutOfOrderByOrdinal(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT8) + struct.AddField('testfield3', mojom.UINT8, 3) + struct.AddField('testfield2', mojom.INT32, 2) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(3, len(ps.packed_fields)) + + # Second byte should be packed in behind first, altering order. + errors += EXPECT_EQ('testfield1', ps.packed_fields[0].field.name) + errors += EXPECT_EQ('testfield3', ps.packed_fields[1].field.name) + errors += EXPECT_EQ('testfield2', ps.packed_fields[2].field.name) + + # Second byte should be packed with first. + errors += EXPECT_EQ(0, ps.packed_fields[0].offset) + errors += EXPECT_EQ(1, ps.packed_fields[1].offset) + errors += EXPECT_EQ(4, ps.packed_fields[2].offset) + + return errors + + +def TestBools(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('bit0', mojom.BOOL) + struct.AddField('bit1', mojom.BOOL) + struct.AddField('int', mojom.INT32) + struct.AddField('bit2', mojom.BOOL) + struct.AddField('bit3', mojom.BOOL) + struct.AddField('bit4', mojom.BOOL) + struct.AddField('bit5', mojom.BOOL) + struct.AddField('bit6', mojom.BOOL) + struct.AddField('bit7', mojom.BOOL) + struct.AddField('bit8', mojom.BOOL) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(10, len(ps.packed_fields)) + + # First 8 bits packed together. + for i in xrange(8): + pf = ps.packed_fields[i] + errors += EXPECT_EQ(0, pf.offset) + errors += EXPECT_EQ("bit%d" % i, pf.field.name) + errors += EXPECT_EQ(i, pf.bit) + + # Ninth bit goes into second byte. + errors += EXPECT_EQ("bit8", ps.packed_fields[8].field.name) + errors += EXPECT_EQ(1, ps.packed_fields[8].offset) + errors += EXPECT_EQ(0, ps.packed_fields[8].bit) + + # int comes last. + errors += EXPECT_EQ("int", ps.packed_fields[9].field.name) + errors += EXPECT_EQ(4, ps.packed_fields[9].offset) + + return errors + + +def Main(args): + errors = 0 + errors += RunTest(TestZeroFields) + errors += RunTest(TestOneField) + errors += RunTest(TestPaddingPackedInOrder) + errors += RunTest(TestPaddingPackedOutOfOrder) + errors += RunTest(TestPaddingPackedOverflow) + errors += RunTest(TestNullableTypes) + errors += RunTest(TestAllTypes) + errors += RunTest(TestPaddingPackedOutOfOrderByOrdinal) + errors += RunTest(TestBools) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py new file mode 100755 index 0000000000..41f11a2b71 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" Test runner for Mojom """ + +import subprocess +import sys + +def TestMojom(testname, args): + print '\nRunning unit tests for %s.' % testname + try: + args = [sys.executable, testname] + args + subprocess.check_call(args, stdout=sys.stdout) + print 'Succeeded' + return 0 + except subprocess.CalledProcessError as err: + print 'Failed with %s.' % str(err) + return 1 + + +def main(args): + errors = 0 + errors += TestMojom('data_tests.py', ['--test']) + errors += TestMojom('module_tests.py', ['--test']) + errors += TestMojom('pack_tests.py', ['--test']) + + if errors: + print '\nFailed tests.' + return min(errors, 127) # Make sure the return value doesn't "wrap". + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py new file mode 100644 index 0000000000..66f8954012 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py @@ -0,0 +1,67 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Based on third_party/WebKit/Source/build/scripts/template_expander.py. + +import imp +import os.path +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("jinja2") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +import jinja2 + + +def ApplyTemplate(mojo_generator, path_to_template, params, **kwargs): + loader = jinja2.ModuleLoader(os.path.join( + mojo_generator.bytecode_path, "%s.zip" % mojo_generator.GetTemplatePrefix( + ))) + final_kwargs = dict(mojo_generator.GetJinjaParameters()) + final_kwargs.update(kwargs) + jinja_env = jinja2.Environment(loader=loader, + keep_trailing_newline=True, + **final_kwargs) + jinja_env.globals.update(mojo_generator.GetGlobals()) + jinja_env.filters.update(mojo_generator.GetFilters()) + template = jinja_env.get_template(path_to_template) + return template.render(params) + + +def UseJinja(path_to_template, **kwargs): + def RealDecorator(generator): + def GeneratorInternal(*args, **kwargs2): + parameters = generator(*args, **kwargs2) + return ApplyTemplate(args[0], path_to_template, parameters, **kwargs) + GeneratorInternal.func_name = generator.func_name + return GeneratorInternal + return RealDecorator + + +def PrecompileTemplates(generator_modules, output_dir): + for module in generator_modules.values(): + generator = module.Generator(None) + jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader([os.path.join( + os.path.dirname(module.__file__), generator.GetTemplatePrefix())])) + jinja_env.filters.update(generator.GetFilters()) + jinja_env.compile_templates( + os.path.join(output_dir, "%s.zip" % generator.GetTemplatePrefix()), + extensions=["tmpl"], + zip="stored", + py_compile=True, + ignore_errors=False) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py b/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py new file mode 100644 index 0000000000..eb394619d2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py @@ -0,0 +1,193 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys +import traceback + +import module as mojom + +# Support for writing mojom test cases. +# RunTest(fn) will execute fn, catching any exceptions. fn should return +# the number of errors that are encountered. +# +# EXPECT_EQ(a, b) and EXPECT_TRUE(b) will print error information if the +# expectations are not true and return a non zero value. This allows test cases +# to be written like this +# +# def Foo(): +# errors = 0 +# errors += EXPECT_EQ('test', test()) +# ... +# return errors +# +# RunTest(foo) + +def FieldsAreEqual(field1, field2): + if field1 == field2: + return True + return field1.name == field2.name and \ + KindsAreEqual(field1.kind, field2.kind) and \ + field1.ordinal == field2.ordinal and \ + field1.default == field2.default + + +def KindsAreEqual(kind1, kind2): + if kind1 == kind2: + return True + if kind1.__class__ != kind2.__class__ or kind1.spec != kind2.spec: + return False + if kind1.__class__ == mojom.Kind: + return kind1.spec == kind2.spec + if kind1.__class__ == mojom.Struct: + if kind1.name != kind2.name or \ + kind1.spec != kind2.spec or \ + len(kind1.fields) != len(kind2.fields): + return False + for i in range(len(kind1.fields)): + if not FieldsAreEqual(kind1.fields[i], kind2.fields[i]): + return False + return True + if kind1.__class__ == mojom.Array: + return KindsAreEqual(kind1.kind, kind2.kind) + print 'Unknown Kind class: ', kind1.__class__.__name__ + return False + + +def ParametersAreEqual(parameter1, parameter2): + if parameter1 == parameter2: + return True + return parameter1.name == parameter2.name and \ + parameter1.ordinal == parameter2.ordinal and \ + parameter1.default == parameter2.default and \ + KindsAreEqual(parameter1.kind, parameter2.kind) + + +def MethodsAreEqual(method1, method2): + if method1 == method2: + return True + if method1.name != method2.name or \ + method1.ordinal != method2.ordinal or \ + len(method1.parameters) != len(method2.parameters): + return False + for i in range(len(method1.parameters)): + if not ParametersAreEqual(method1.parameters[i], method2.parameters[i]): + return False + return True + + +def InterfacesAreEqual(interface1, interface2): + if interface1 == interface2: + return True + if interface1.name != interface2.name or \ + len(interface1.methods) != len(interface2.methods): + return False + for i in range(len(interface1.methods)): + if not MethodsAreEqual(interface1.methods[i], interface2.methods[i]): + return False + return True + + +def ModulesAreEqual(module1, module2): + if module1 == module2: + return True + if module1.name != module2.name or \ + module1.namespace != module2.namespace or \ + len(module1.structs) != len(module2.structs) or \ + len(module1.interfaces) != len(module2.interfaces): + return False + for i in range(len(module1.structs)): + if not KindsAreEqual(module1.structs[i], module2.structs[i]): + return False + for i in range(len(module1.interfaces)): + if not InterfacesAreEqual(module1.interfaces[i], module2.interfaces[i]): + return False + return True + + +# Builds and returns a Module suitable for testing/ +def BuildTestModule(): + module = mojom.Module('test', 'testspace') + struct = module.AddStruct('teststruct') + struct.AddField('testfield1', mojom.INT32) + struct.AddField('testfield2', mojom.Array(mojom.INT32), 42) + + interface = module.AddInterface('Server') + method = interface.AddMethod('Foo', 42) + method.AddParameter('foo', mojom.INT32) + method.AddParameter('bar', mojom.Array(struct)) + + return module + + +# Tests if |module| is as built by BuildTestModule(). Returns the number of +# errors +def TestTestModule(module): + errors = 0 + + errors += EXPECT_EQ('test', module.name) + errors += EXPECT_EQ('testspace', module.namespace) + errors += EXPECT_EQ(1, len(module.structs)) + errors += EXPECT_EQ('teststruct', module.structs[0].name) + errors += EXPECT_EQ(2, len(module.structs[0].fields)) + errors += EXPECT_EQ('testfield1', module.structs[0].fields[0].name) + errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[0].kind) + errors += EXPECT_EQ('testfield2', module.structs[0].fields[1].name) + errors += EXPECT_EQ(mojom.Array, module.structs[0].fields[1].kind.__class__) + errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[1].kind.kind) + + errors += EXPECT_EQ(1, len(module.interfaces)) + errors += EXPECT_EQ('Server', module.interfaces[0].name) + errors += EXPECT_EQ(1, len(module.interfaces[0].methods)) + errors += EXPECT_EQ('Foo', module.interfaces[0].methods[0].name) + errors += EXPECT_EQ(2, len(module.interfaces[0].methods[0].parameters)) + errors += EXPECT_EQ('foo', module.interfaces[0].methods[0].parameters[0].name) + errors += EXPECT_EQ(mojom.INT32, + module.interfaces[0].methods[0].parameters[0].kind) + errors += EXPECT_EQ('bar', module.interfaces[0].methods[0].parameters[1].name) + errors += EXPECT_EQ( + mojom.Array, + module.interfaces[0].methods[0].parameters[1].kind.__class__) + errors += EXPECT_EQ( + module.structs[0], + module.interfaces[0].methods[0].parameters[1].kind.kind) + return errors + + +def PrintFailure(string): + stack = traceback.extract_stack() + frame = stack[len(stack)-3] + sys.stderr.write("ERROR at %s:%d, %s\n" % (frame[0], frame[1], string)) + print "Traceback:" + for line in traceback.format_list(stack[:len(stack)-2]): + sys.stderr.write(line) + + +def EXPECT_EQ(a, b): + if a != b: + PrintFailure("%s != %s" % (a, b)) + return 1 + return 0 + + +def EXPECT_TRUE(a): + if not a: + PrintFailure('Expecting True') + return 1 + return 0 + + +def RunTest(fn): + sys.stdout.write('Running %s...' % fn.__name__) + try: + errors = fn() + except: + traceback.print_exc(sys.stderr) + errors = 1 + if errors == 0: + sys.stdout.write('OK\n') + elif errors == 1: + sys.stdout.write('1 ERROR\n') + else: + sys.stdout.write('%d ERRORS\n' % errors) + return errors diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/translate.py b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py new file mode 100644 index 0000000000..ffad7447a9 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py @@ -0,0 +1,639 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Convert parse tree to AST. + +This module converts the parse tree to the AST we use for code generation. The +main entry point is OrderedModule, which gets passed the parser +representation of a mojom file. When called it's assumed that all imports have +already been parsed and converted to ASTs before. +""" + +import copy +import re + +import module as mojom +from mojom.parse import ast + +def _DuplicateName(values): + """Returns the 'name' of the first entry in |values| whose 'name' has already + been encountered. If there are no duplicates, returns None.""" + names = set() + for value in values: + if value.name in names: + return value.name + names.add(value.name) + return None + +def _ElemsOfType(elems, elem_type, scope): + """Find all elements of the given type. + + Args: + elems: {Sequence[Any]} Sequence of elems. + elem_type: {Type[C]} Extract all elems of this type. + scope: {str} The name of the surrounding scope (e.g. struct + definition). Used in error messages. + + Returns: + {List[C]} All elems of matching type. + """ + assert isinstance(elem_type, type) + result = [elem for elem in elems if isinstance(elem, elem_type)] + duplicate_name = _DuplicateName(result) + if duplicate_name: + raise Exception('Names in mojom must be unique within a scope. The name ' + '"%s" is used more than once within the scope "%s".' % + (duplicate_name, scope)) + return result + +def _MapKind(kind): + map_to_kind = {'bool': 'b', + 'int8': 'i8', + 'int16': 'i16', + 'int32': 'i32', + 'int64': 'i64', + 'uint8': 'u8', + 'uint16': 'u16', + 'uint32': 'u32', + 'uint64': 'u64', + 'float': 'f', + 'double': 'd', + 'string': 's', + 'handle': 'h', + 'handle<data_pipe_consumer>': 'h:d:c', + 'handle<data_pipe_producer>': 'h:d:p', + 'handle<message_pipe>': 'h:m', + 'handle<shared_buffer>': 'h:s'} + if kind.endswith('?'): + base_kind = _MapKind(kind[0:-1]) + # NOTE: This doesn't rule out enum types. Those will be detected later, when + # cross-reference is established. + reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso') + if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds: + raise Exception( + 'A type (spec "%s") cannot be made nullable' % base_kind) + return '?' + base_kind + if kind.endswith('}'): + lbracket = kind.rfind('{') + value = kind[0:lbracket] + return 'm[' + _MapKind(kind[lbracket+1:-1]) + '][' + _MapKind(value) + ']' + if kind.endswith(']'): + lbracket = kind.rfind('[') + typename = kind[0:lbracket] + return 'a' + kind[lbracket+1:-1] + ':' + _MapKind(typename) + if kind.endswith('&'): + return 'r:' + _MapKind(kind[0:-1]) + if kind.startswith('asso<'): + assert kind.endswith('>') + return 'asso:' + _MapKind(kind[5:-1]) + if kind in map_to_kind: + return map_to_kind[kind] + return 'x:' + kind + +def _AttributeListToDict(attribute_list): + if attribute_list is None: + return None + assert isinstance(attribute_list, ast.AttributeList) + # TODO(vtl): Check for duplicate keys here. + return dict([(attribute.key, attribute.value) + for attribute in attribute_list]) + +builtin_values = frozenset([ + "double.INFINITY", + "double.NEGATIVE_INFINITY", + "double.NAN", + "float.INFINITY", + "float.NEGATIVE_INFINITY", + "float.NAN"]) + +def _IsBuiltinValue(value): + return value in builtin_values + +def _LookupKind(kinds, spec, scope): + """Tries to find which Kind a spec refers to, given the scope in which its + referenced. Starts checking from the narrowest scope to most general. For + example, given a struct field like + Foo.Bar x; + Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner + type 'Bar' in the struct 'Foo' in the current namespace. + + |scope| is a tuple that looks like (namespace, struct/interface), referring + to the location where the type is referenced.""" + if spec.startswith('x:'): + name = spec[2:] + for i in xrange(len(scope), -1, -1): + test_spec = 'x:' + if i > 0: + test_spec += '.'.join(scope[:i]) + '.' + test_spec += name + kind = kinds.get(test_spec) + if kind: + return kind + + return kinds.get(spec) + +def _LookupValue(values, name, scope, kind): + """Like LookupKind, but for constant values.""" + # If the type is an enum, the value can be specified as a qualified name, in + # which case the form EnumName.ENUM_VALUE must be used. We use the presence + # of a '.' in the requested name to identify this. Otherwise, we prepend the + # enum name. + if isinstance(kind, mojom.Enum) and '.' not in name: + name = '%s.%s' % (kind.spec.split(':', 1)[1], name) + for i in reversed(xrange(len(scope) + 1)): + test_spec = '.'.join(scope[:i]) + if test_spec: + test_spec += '.' + test_spec += name + value = values.get(test_spec) + if value: + return value + + return values.get(name) + +def _FixupExpression(module, value, scope, kind): + """Translates an IDENTIFIER into a built-in value or structured NamedValue + object.""" + if isinstance(value, tuple) and value[0] == 'IDENTIFIER': + # Allow user defined values to shadow builtins. + result = _LookupValue(module.values, value[1], scope, kind) + if result: + if isinstance(result, tuple): + raise Exception('Unable to resolve expression: %r' % value[1]) + return result + if _IsBuiltinValue(value[1]): + return mojom.BuiltinValue(value[1]) + return value + +def _Kind(kinds, spec, scope): + """Convert a type name into a mojom.Kind object. + + As a side-effect this function adds the result to 'kinds'. + + Args: + kinds: {Dict[str, mojom.Kind]} All known kinds up to this point, indexed by + their names. + spec: {str} A name uniquely identifying a type. + scope: {Tuple[str, str]} A tuple that looks like (namespace, + struct/interface), referring to the location where the type is + referenced. + + Returns: + {mojom.Kind} The type corresponding to 'spec'. + """ + kind = _LookupKind(kinds, spec, scope) + if kind: + return kind + + if spec.startswith('?'): + kind = _Kind(kinds, spec[1:], scope).MakeNullableKind() + elif spec.startswith('a:'): + kind = mojom.Array(_Kind(kinds, spec[2:], scope)) + elif spec.startswith('asso:'): + inner_kind = _Kind(kinds, spec[5:], scope) + if isinstance(inner_kind, mojom.InterfaceRequest): + kind = mojom.AssociatedInterfaceRequest(inner_kind) + else: + kind = mojom.AssociatedInterface(inner_kind) + elif spec.startswith('a'): + colon = spec.find(':') + length = int(spec[1:colon]) + kind = mojom.Array(_Kind(kinds, spec[colon+1:], scope), length) + elif spec.startswith('r:'): + kind = mojom.InterfaceRequest(_Kind(kinds, spec[2:], scope)) + elif spec.startswith('m['): + # Isolate the two types from their brackets. + + # It is not allowed to use map as key, so there shouldn't be nested ']'s + # inside the key type spec. + key_end = spec.find(']') + assert key_end != -1 and key_end < len(spec) - 1 + assert spec[key_end+1] == '[' and spec[-1] == ']' + + first_kind = spec[2:key_end] + second_kind = spec[key_end+2:-1] + + kind = mojom.Map(_Kind(kinds, first_kind, scope), + _Kind(kinds, second_kind, scope)) + else: + kind = mojom.Kind(spec) + + kinds[spec] = kind + return kind + +def _KindFromImport(original_kind, imported_from): + """Used with 'import module' - clones the kind imported from the given + module's namespace. Only used with Structs, Unions, Interfaces and Enums.""" + kind = copy.copy(original_kind) + # |shared_definition| is used to store various properties (see + # |AddSharedProperty()| in module.py), including |imported_from|. We don't + # want the copy to share these with the original, so copy it if necessary. + if hasattr(original_kind, 'shared_definition'): + kind.shared_definition = copy.copy(original_kind.shared_definition) + kind.imported_from = imported_from + return kind + +def _Import(module, import_module): + import_item = {} + import_item['module_name'] = import_module.name + import_item['namespace'] = import_module.namespace + import_item['module'] = import_module + + # Copy the struct kinds from our imports into the current module. + importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface) + for kind in import_module.kinds.itervalues(): + if (isinstance(kind, importable_kinds) and + kind.imported_from is None): + kind = _KindFromImport(kind, import_item) + module.kinds[kind.spec] = kind + # Ditto for values. + for value in import_module.values.itervalues(): + if value.imported_from is None: + # Values don't have shared definitions (since they're not nullable), so no + # need to do anything special. + value = copy.copy(value) + value.imported_from = import_item + module.values[value.GetSpec()] = value + + return import_item + +def _Struct(module, parsed_struct): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_struct: {ast.Struct} Parsed struct. + + Returns: + {mojom.Struct} AST struct. + """ + struct = mojom.Struct(module=module) + struct.name = parsed_struct.name + struct.native_only = parsed_struct.body is None + struct.spec = 'x:' + module.namespace + '.' + struct.name + module.kinds[struct.spec] = struct + if struct.native_only: + struct.enums = [] + struct.constants = [] + struct.fields_data = [] + else: + struct.enums = map( + lambda enum: _Enum(module, enum, struct), + _ElemsOfType(parsed_struct.body, ast.Enum, parsed_struct.name)) + struct.constants = map( + lambda constant: _Constant(module, constant, struct), + _ElemsOfType(parsed_struct.body, ast.Const, parsed_struct.name)) + # Stash fields parsed_struct here temporarily. + struct.fields_data = _ElemsOfType( + parsed_struct.body, ast.StructField, parsed_struct.name) + struct.attributes = _AttributeListToDict(parsed_struct.attribute_list) + + # Enforce that a [Native] attribute is set to make native-only struct + # declarations more explicit. + if struct.native_only: + if not struct.attributes or not struct.attributes.get('Native', False): + raise Exception("Native-only struct declarations must include a " + + "Native attribute.") + + return struct + +def _Union(module, parsed_union): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_union: {ast.Union} Parsed union. + + Returns: + {mojom.Union} AST union. + """ + union = mojom.Union(module=module) + union.name = parsed_union.name + union.spec = 'x:' + module.namespace + '.' + union.name + module.kinds[union.spec] = union + # Stash fields parsed_union here temporarily. + union.fields_data = _ElemsOfType( + parsed_union.body, ast.UnionField, parsed_union.name) + union.attributes = _AttributeListToDict(parsed_union.attribute_list) + return union + +def _StructField(module, parsed_field, struct): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_field: {ast.StructField} Parsed struct field. + struct: {mojom.Struct} Struct this field belongs to. + + Returns: + {mojom.StructField} AST struct field. + """ + field = mojom.StructField() + field.name = parsed_field.name + field.kind = _Kind( + module.kinds, _MapKind(parsed_field.typename), + (module.namespace, struct.name)) + field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None + field.default = _FixupExpression( + module, parsed_field.default_value, (module.namespace, struct.name), + field.kind) + field.attributes = _AttributeListToDict(parsed_field.attribute_list) + return field + +def _UnionField(module, parsed_field, union): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_field: {ast.UnionField} Parsed union field. + union: {mojom.Union} Union this fields belong to. + + Returns: + {mojom.UnionField} AST union. + """ + field = mojom.UnionField() + field.name = parsed_field.name + field.kind = _Kind( + module.kinds, _MapKind(parsed_field.typename), + (module.namespace, union.name)) + field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None + field.default = _FixupExpression( + module, None, (module.namespace, union.name), field.kind) + field.attributes = _AttributeListToDict(parsed_field.attribute_list) + return field + +def _Parameter(module, parsed_param, interface): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_param: {ast.Parameter} Parsed parameter. + union: {mojom.Interface} Interface this parameter belongs to. + + Returns: + {mojom.Parameter} AST parameter. + """ + parameter = mojom.Parameter() + parameter.name = parsed_param.name + parameter.kind = _Kind( + module.kinds, _MapKind(parsed_param.typename), + (module.namespace, interface.name)) + parameter.ordinal = ( + parsed_param.ordinal.value if parsed_param.ordinal else None) + parameter.default = None # TODO(tibell): We never have these. Remove field? + parameter.attributes = _AttributeListToDict(parsed_param.attribute_list) + return parameter + +def _Method(module, parsed_method, interface): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_method: {ast.Method} Parsed method. + interface: {mojom.Interface} Interface this method belongs to. + + Returns: + {mojom.Method} AST method. + """ + method = mojom.Method( + interface, parsed_method.name, + ordinal=parsed_method.ordinal.value if parsed_method.ordinal else None) + method.parameters = map( + lambda parameter: _Parameter(module, parameter, interface), + parsed_method.parameter_list) + if parsed_method.response_parameter_list is not None: + method.response_parameters = map( + lambda parameter: _Parameter(module, parameter, interface), + parsed_method.response_parameter_list) + method.attributes = _AttributeListToDict(parsed_method.attribute_list) + + # Enforce that only methods with response can have a [Sync] attribute. + if method.sync and method.response_parameters is None: + raise Exception("Only methods with response can include a [Sync] " + "attribute. If no response parameters are needed, you " + "could use an empty response parameter list, i.e., " + "\"=> ()\".") + + return method + +def _Interface(module, parsed_iface): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_iface: {ast.Interface} Parsed interface. + + Returns: + {mojom.Interface} AST interface. + """ + interface = mojom.Interface(module=module) + interface.name = parsed_iface.name + interface.spec = 'x:' + module.namespace + '.' + interface.name + module.kinds[interface.spec] = interface + interface.enums = map( + lambda enum: _Enum(module, enum, interface), + _ElemsOfType(parsed_iface.body, ast.Enum, parsed_iface.name)) + interface.constants = map( + lambda constant: _Constant(module, constant, interface), + _ElemsOfType(parsed_iface.body, ast.Const, parsed_iface.name)) + # Stash methods parsed_iface here temporarily. + interface.methods_data = _ElemsOfType( + parsed_iface.body, ast.Method, parsed_iface.name) + interface.attributes = _AttributeListToDict(parsed_iface.attribute_list) + return interface + +def _EnumField(module, enum, parsed_field, parent_kind): + """ + Args: + module: {mojom.Module} Module currently being constructed. + enum: {mojom.Enum} Enum this field belongs to. + parsed_field: {ast.EnumValue} Parsed enum value. + parent_kind: {mojom.Kind} The enclosing type. + + Returns: + {mojom.EnumField} AST enum field. + """ + field = mojom.EnumField() + field.name = parsed_field.name + # TODO(mpcomplete): FixupExpression should be done in the second pass, + # so constants and enums can refer to each other. + # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or + # vice versa? + if parent_kind: + field.value = _FixupExpression( + module, parsed_field.value, (module.namespace, parent_kind.name), enum) + else: + field.value = _FixupExpression( + module, parsed_field.value, (module.namespace, ), enum) + field.attributes = _AttributeListToDict(parsed_field.attribute_list) + value = mojom.EnumValue(module, enum, field) + module.values[value.GetSpec()] = value + return field + +def _ResolveNumericEnumValues(enum_fields): + """ + Given a reference to a list of mojom.EnumField, resolves and assigns their + values to EnumField.numeric_value. + """ + + # map of <name> -> integral value + resolved_enum_values = {} + prev_value = -1 + for field in enum_fields: + # This enum value is +1 the previous enum value (e.g: BEGIN). + if field.value is None: + prev_value += 1 + + # Integral value (e.g: BEGIN = -0x1). + elif type(field.value) is str: + prev_value = int(field.value, 0) + + # Reference to a previous enum value (e.g: INIT = BEGIN). + elif type(field.value) is mojom.EnumValue: + prev_value = resolved_enum_values[field.value.name] + else: + raise Exception("Unresolved enum value.") + + resolved_enum_values[field.name] = prev_value + field.numeric_value = prev_value + +def _Enum(module, parsed_enum, parent_kind): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_enum: {ast.Enum} Parsed enum. + + Returns: + {mojom.Enum} AST enum. + """ + enum = mojom.Enum(module=module) + enum.name = parsed_enum.name + enum.native_only = parsed_enum.enum_value_list is None + name = enum.name + if parent_kind: + name = parent_kind.name + '.' + name + enum.spec = 'x:%s.%s' % (module.namespace, name) + enum.parent_kind = parent_kind + enum.attributes = _AttributeListToDict(parsed_enum.attribute_list) + if enum.native_only: + enum.fields = [] + else: + enum.fields = map( + lambda field: _EnumField(module, enum, field, parent_kind), + parsed_enum.enum_value_list) + _ResolveNumericEnumValues(enum.fields) + + module.kinds[enum.spec] = enum + + # Enforce that a [Native] attribute is set to make native-only enum + # declarations more explicit. + if enum.native_only: + if not enum.attributes or not enum.attributes.get('Native', False): + raise Exception("Native-only enum declarations must include a " + + "Native attribute.") + + return enum + +def _Constant(module, parsed_const, parent_kind): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_const: {ast.Const} Parsed constant. + + Returns: + {mojom.Constant} AST constant. + """ + constant = mojom.Constant() + constant.name = parsed_const.name + if parent_kind: + scope = (module.namespace, parent_kind.name) + else: + scope = (module.namespace, ) + # TODO(mpcomplete): maybe we should only support POD kinds. + constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope) + constant.parent_kind = parent_kind + constant.value = _FixupExpression(module, parsed_const.value, scope, None) + + value = mojom.ConstantValue(module, parent_kind, constant) + module.values[value.GetSpec()] = value + return constant + +def _Module(tree, name, imports): + """ + Args: + tree: {ast.Mojom} The parse tree. + name: {str} The mojom filename, excluding the path. + imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in + the import list, to already processed modules. Used to process imports. + + Returns: + {mojom.Module} An AST for the mojom. + """ + module = mojom.Module() + module.kinds = {} + for kind in mojom.PRIMITIVES: + module.kinds[kind.spec] = kind + + module.values = {} + + module.name = name + module.namespace = tree.module.name[1] if tree.module else '' + # Imports must come first, because they add to module.kinds which is used + # by by the others. + module.imports = [ + _Import(module, imports[imp.import_filename]) + for imp in tree.import_list] + if tree.module and tree.module.attribute_list: + assert isinstance(tree.module.attribute_list, ast.AttributeList) + # TODO(vtl): Check for duplicate keys here. + module.attributes = dict((attribute.key, attribute.value) + for attribute in tree.module.attribute_list) + + # First pass collects kinds. + module.enums = map( + lambda enum: _Enum(module, enum, None), + _ElemsOfType(tree.definition_list, ast.Enum, name)) + module.structs = map( + lambda struct: _Struct(module, struct), + _ElemsOfType(tree.definition_list, ast.Struct, name)) + module.unions = map( + lambda union: _Union(module, union), + _ElemsOfType(tree.definition_list, ast.Union, name)) + module.interfaces = map( + lambda interface: _Interface(module, interface), + _ElemsOfType(tree.definition_list, ast.Interface, name)) + module.constants = map( + lambda constant: _Constant(module, constant, None), + _ElemsOfType(tree.definition_list, ast.Const, name)) + + # Second pass expands fields and methods. This allows fields and parameters + # to refer to kinds defined anywhere in the mojom. + for struct in module.structs: + struct.fields = map(lambda field: + _StructField(module, field, struct), struct.fields_data) + del struct.fields_data + for union in module.unions: + union.fields = map(lambda field: + _UnionField(module, field, union), union.fields_data) + del union.fields_data + for interface in module.interfaces: + interface.methods = map(lambda method: + _Method(module, method, interface), interface.methods_data) + del interface.methods_data + + return module + +def OrderedModule(tree, name, imports): + """Convert parse tree to AST module. + + Args: + tree: {ast.Mojom} The parse tree. + name: {str} The mojom filename, excluding the path. + imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in + the import list, to already processed modules. Used to process imports. + + Returns: + {mojom.Module} An AST for the mojom. + """ + module = _Module(tree, name, imports) + for interface in module.interfaces: + next_ordinal = 0 + for method in interface.methods: + if method.ordinal is None: + method.ordinal = next_ordinal + next_ordinal = method.ordinal + 1 + return module diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py b/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/ast.py b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py new file mode 100644 index 0000000000..2c6b5fcdf8 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py @@ -0,0 +1,410 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Node classes for the AST for a Mojo IDL file.""" + +# Note: For convenience of testing, you probably want to define __eq__() methods +# for all node types; it's okay to be slightly lax (e.g., not compare filename +# and lineno). You may also define __repr__() to help with analyzing test +# failures, especially for more complex types. + + +class NodeBase(object): + """Base class for nodes in the AST.""" + + def __init__(self, filename=None, lineno=None): + self.filename = filename + self.lineno = lineno + + def __eq__(self, other): + return type(self) == type(other) + + # Make != the inverse of ==. (Subclasses shouldn't have to override this.) + def __ne__(self, other): + return not self == other + + +# TODO(vtl): Some of this is complicated enough that it should be tested. +class NodeListBase(NodeBase): + """Represents a list of other nodes, all having the same type. (This is meant + to be subclassed, with subclasses defining _list_item_type to be the class (or + classes, in a tuple) of the members of the list.)""" + + def __init__(self, item_or_items=None, **kwargs): + super(NodeListBase, self).__init__(**kwargs) + self.items = [] + if item_or_items is None: + pass + elif isinstance(item_or_items, list): + for item in item_or_items: + assert isinstance(item, self._list_item_type) + self.Append(item) + else: + assert isinstance(item_or_items, self._list_item_type) + self.Append(item_or_items) + + # Support iteration. For everything else, users should just access |items| + # directly. (We intentionally do NOT supply |__len__()| or |__nonzero__()|, so + # |bool(NodeListBase())| is true.) + def __iter__(self): + return self.items.__iter__() + + def __eq__(self, other): + return super(NodeListBase, self).__eq__(other) and \ + self.items == other.items + + # Implement this so that on failure, we get slightly more sensible output. + def __repr__(self): + return self.__class__.__name__ + "([" + \ + ", ".join([repr(elem) for elem in self.items]) + "])" + + def Insert(self, item): + """Inserts item at the front of the list.""" + + assert isinstance(item, self._list_item_type) + self.items.insert(0, item) + self._UpdateFilenameAndLineno() + + def Append(self, item): + """Appends item to the end of the list.""" + + assert isinstance(item, self._list_item_type) + self.items.append(item) + self._UpdateFilenameAndLineno() + + def _UpdateFilenameAndLineno(self): + if self.items: + self.filename = self.items[0].filename + self.lineno = self.items[0].lineno + + +class Definition(NodeBase): + """Represents a definition of anything that has a global name (e.g., enums, + enum values, consts, structs, struct fields, interfaces). (This does not + include parameter definitions.) This class is meant to be subclassed.""" + + def __init__(self, name, **kwargs): + assert isinstance(name, str) + NodeBase.__init__(self, **kwargs) + self.name = name + + +################################################################################ + + +class Attribute(NodeBase): + """Represents an attribute.""" + + def __init__(self, key, value, **kwargs): + assert isinstance(key, str) + super(Attribute, self).__init__(**kwargs) + self.key = key + self.value = value + + def __eq__(self, other): + return super(Attribute, self).__eq__(other) and \ + self.key == other.key and \ + self.value == other.value + + +class AttributeList(NodeListBase): + """Represents a list attributes.""" + + _list_item_type = Attribute + + +class Const(Definition): + """Represents a const definition.""" + + def __init__(self, name, typename, value, **kwargs): + # The typename is currently passed through as a string. + assert isinstance(typename, str) + # The value is either a literal (currently passed through as a string) or a + # "wrapped identifier". + assert isinstance(value, str) or isinstance(value, tuple) + super(Const, self).__init__(name, **kwargs) + self.typename = typename + self.value = value + + def __eq__(self, other): + return super(Const, self).__eq__(other) and \ + self.typename == other.typename and \ + self.value == other.value + + +class Enum(Definition): + """Represents an enum definition.""" + + def __init__(self, name, attribute_list, enum_value_list, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert enum_value_list is None or isinstance(enum_value_list, EnumValueList) + super(Enum, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.enum_value_list = enum_value_list + + def __eq__(self, other): + return super(Enum, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.enum_value_list == other.enum_value_list + + +class EnumValue(Definition): + """Represents a definition of an enum value.""" + + def __init__(self, name, attribute_list, value, **kwargs): + # The optional value is either an int (which is current a string) or a + # "wrapped identifier". + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert value is None or isinstance(value, (str, tuple)) + super(EnumValue, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.value = value + + def __eq__(self, other): + return super(EnumValue, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.value == other.value + + +class EnumValueList(NodeListBase): + """Represents a list of enum value definitions (i.e., the "body" of an enum + definition).""" + + _list_item_type = EnumValue + + +class Import(NodeBase): + """Represents an import statement.""" + + def __init__(self, import_filename, **kwargs): + assert isinstance(import_filename, str) + super(Import, self).__init__(**kwargs) + self.import_filename = import_filename + + def __eq__(self, other): + return super(Import, self).__eq__(other) and \ + self.import_filename == other.import_filename + + +class ImportList(NodeListBase): + """Represents a list (i.e., sequence) of import statements.""" + + _list_item_type = Import + + +class Interface(Definition): + """Represents an interface definition.""" + + def __init__(self, name, attribute_list, body, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert isinstance(body, InterfaceBody) + super(Interface, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.body = body + + def __eq__(self, other): + return super(Interface, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.body == other.body + + +class Method(Definition): + """Represents a method definition.""" + + def __init__(self, name, attribute_list, ordinal, parameter_list, + response_parameter_list, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert ordinal is None or isinstance(ordinal, Ordinal) + assert isinstance(parameter_list, ParameterList) + assert response_parameter_list is None or \ + isinstance(response_parameter_list, ParameterList) + super(Method, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.ordinal = ordinal + self.parameter_list = parameter_list + self.response_parameter_list = response_parameter_list + + def __eq__(self, other): + return super(Method, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.ordinal == other.ordinal and \ + self.parameter_list == other.parameter_list and \ + self.response_parameter_list == other.response_parameter_list + + +# This needs to be declared after |Method|. +class InterfaceBody(NodeListBase): + """Represents the body of (i.e., list of definitions inside) an interface.""" + + _list_item_type = (Const, Enum, Method) + + +class Module(NodeBase): + """Represents a module statement.""" + + def __init__(self, name, attribute_list, **kwargs): + # |name| is either none or a "wrapped identifier". + assert name is None or isinstance(name, tuple) + assert attribute_list is None or isinstance(attribute_list, AttributeList) + super(Module, self).__init__(**kwargs) + self.name = name + self.attribute_list = attribute_list + + def __eq__(self, other): + return super(Module, self).__eq__(other) and \ + self.name == other.name and \ + self.attribute_list == other.attribute_list + + +class Mojom(NodeBase): + """Represents an entire .mojom file. (This is the root node.)""" + + def __init__(self, module, import_list, definition_list, **kwargs): + assert module is None or isinstance(module, Module) + assert isinstance(import_list, ImportList) + assert isinstance(definition_list, list) + super(Mojom, self).__init__(**kwargs) + self.module = module + self.import_list = import_list + self.definition_list = definition_list + + def __eq__(self, other): + return super(Mojom, self).__eq__(other) and \ + self.module == other.module and \ + self.import_list == other.import_list and \ + self.definition_list == other.definition_list + + def __repr__(self): + return "%s(%r, %r, %r)" % (self.__class__.__name__, self.module, + self.import_list, self.definition_list) + + +class Ordinal(NodeBase): + """Represents an ordinal value labeling, e.g., a struct field.""" + + def __init__(self, value, **kwargs): + assert isinstance(value, int) + super(Ordinal, self).__init__(**kwargs) + self.value = value + + def __eq__(self, other): + return super(Ordinal, self).__eq__(other) and \ + self.value == other.value + + +class Parameter(NodeBase): + """Represents a method request or response parameter.""" + + def __init__(self, name, attribute_list, ordinal, typename, **kwargs): + assert isinstance(name, str) + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert ordinal is None or isinstance(ordinal, Ordinal) + assert isinstance(typename, str) + super(Parameter, self).__init__(**kwargs) + self.name = name + self.attribute_list = attribute_list + self.ordinal = ordinal + self.typename = typename + + def __eq__(self, other): + return super(Parameter, self).__eq__(other) and \ + self.name == other.name and \ + self.attribute_list == other.attribute_list and \ + self.ordinal == other.ordinal and \ + self.typename == other.typename + + +class ParameterList(NodeListBase): + """Represents a list of (method request or response) parameters.""" + + _list_item_type = Parameter + + +class Struct(Definition): + """Represents a struct definition.""" + + def __init__(self, name, attribute_list, body, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert isinstance(body, StructBody) or body is None + super(Struct, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.body = body + + def __eq__(self, other): + return super(Struct, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.body == other.body + + +class StructField(Definition): + """Represents a struct field definition.""" + + def __init__(self, name, attribute_list, ordinal, typename, default_value, + **kwargs): + assert isinstance(name, str) + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert ordinal is None or isinstance(ordinal, Ordinal) + assert isinstance(typename, str) + # The optional default value is currently either a value as a string or a + # "wrapped identifier". + assert default_value is None or isinstance(default_value, (str, tuple)) + super(StructField, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.ordinal = ordinal + self.typename = typename + self.default_value = default_value + + def __eq__(self, other): + return super(StructField, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.ordinal == other.ordinal and \ + self.typename == other.typename and \ + self.default_value == other.default_value + + +# This needs to be declared after |StructField|. +class StructBody(NodeListBase): + """Represents the body of (i.e., list of definitions inside) a struct.""" + + _list_item_type = (Const, Enum, StructField) + + +class Union(Definition): + """Represents a union definition.""" + + def __init__(self, name, attribute_list, body, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert isinstance(body, UnionBody) + super(Union, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.body = body + + def __eq__(self, other): + return super(Union, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.body == other.body + + +class UnionField(Definition): + + def __init__(self, name, attribute_list, ordinal, typename, **kwargs): + assert isinstance(name, str) + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert ordinal is None or isinstance(ordinal, Ordinal) + assert isinstance(typename, str) + super(UnionField, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.ordinal = ordinal + self.typename = typename + + def __eq__(self, other): + return super(UnionField, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.ordinal == other.ordinal and \ + self.typename == other.typename + + +class UnionBody(NodeListBase): + + _list_item_type = UnionField diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py new file mode 100644 index 0000000000..06354b1d85 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py @@ -0,0 +1,254 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply.lex import TOKEN + +from ..error import Error + + +class LexError(Error): + """Class for errors from the lexer.""" + + def __init__(self, filename, message, lineno): + Error.__init__(self, filename, message, lineno=lineno) + + +# We have methods which look like they could be functions: +# pylint: disable=R0201 +class Lexer(object): + + def __init__(self, filename): + self.filename = filename + + ######################-- PRIVATE --###################### + + ## + ## Internal auxiliary methods + ## + def _error(self, msg, token): + raise LexError(self.filename, msg, token.lineno) + + ## + ## Reserved keywords + ## + keywords = ( + 'HANDLE', + + 'IMPORT', + 'MODULE', + 'STRUCT', + 'UNION', + 'INTERFACE', + 'ENUM', + 'CONST', + 'TRUE', + 'FALSE', + 'DEFAULT', + 'ARRAY', + 'MAP', + 'ASSOCIATED' + ) + + keyword_map = {} + for keyword in keywords: + keyword_map[keyword.lower()] = keyword + + ## + ## All the tokens recognized by the lexer + ## + tokens = keywords + ( + # Identifiers + 'NAME', + + # Constants + 'ORDINAL', + 'INT_CONST_DEC', 'INT_CONST_HEX', + 'FLOAT_CONST', + + # String literals + 'STRING_LITERAL', + + # Operators + 'MINUS', + 'PLUS', + 'AMP', + 'QSTN', + + # Assignment + 'EQUALS', + + # Request / response + 'RESPONSE', + + # Delimiters + 'LPAREN', 'RPAREN', # ( ) + 'LBRACKET', 'RBRACKET', # [ ] + 'LBRACE', 'RBRACE', # { } + 'LANGLE', 'RANGLE', # < > + 'SEMI', # ; + 'COMMA', 'DOT' # , . + ) + + ## + ## Regexes for use in tokens + ## + + # valid C identifiers (K&R2: A.2.3) + identifier = r'[a-zA-Z_][0-9a-zA-Z_]*' + + hex_prefix = '0[xX]' + hex_digits = '[0-9a-fA-F]+' + + # integer constants (K&R2: A.2.5.1) + decimal_constant = '0|([1-9][0-9]*)' + hex_constant = hex_prefix+hex_digits + # Don't allow octal constants (even invalid octal). + octal_constant_disallowed = '0[0-9]+' + + # character constants (K&R2: A.2.5.2) + # Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line + # directives with Windows paths as filenames (..\..\dir\file) + # For the same reason, decimal_escape allows all digit sequences. We want to + # parse all correct code, even if it means to sometimes parse incorrect + # code. + # + simple_escape = r"""([a-zA-Z._~!=&\^\-\\?'"])""" + decimal_escape = r"""(\d+)""" + hex_escape = r"""(x[0-9a-fA-F]+)""" + bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-7])""" + + escape_sequence = \ + r"""(\\("""+simple_escape+'|'+decimal_escape+'|'+hex_escape+'))' + + # string literals (K&R2: A.2.6) + string_char = r"""([^"\\\n]|"""+escape_sequence+')' + string_literal = '"'+string_char+'*"' + bad_string_literal = '"'+string_char+'*'+bad_escape+string_char+'*"' + + # floating constants (K&R2: A.2.5.3) + exponent_part = r"""([eE][-+]?[0-9]+)""" + fractional_constant = r"""([0-9]*\.[0-9]+)|([0-9]+\.)""" + floating_constant = \ + '(((('+fractional_constant+')'+ \ + exponent_part+'?)|([0-9]+'+exponent_part+')))' + + # Ordinals + ordinal = r'@[0-9]+' + missing_ordinal_value = r'@' + # Don't allow ordinal values in octal (even invalid octal, like 09) or + # hexadecimal. + octal_or_hex_ordinal_disallowed = r'@((0[0-9]+)|('+hex_prefix+hex_digits+'))' + + ## + ## Rules for the normal state + ## + t_ignore = ' \t\r' + + # Newlines + def t_NEWLINE(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + + # Operators + t_MINUS = r'-' + t_PLUS = r'\+' + t_AMP = r'&' + t_QSTN = r'\?' + + # = + t_EQUALS = r'=' + + # => + t_RESPONSE = r'=>' + + # Delimiters + t_LPAREN = r'\(' + t_RPAREN = r'\)' + t_LBRACKET = r'\[' + t_RBRACKET = r'\]' + t_LBRACE = r'\{' + t_RBRACE = r'\}' + t_LANGLE = r'<' + t_RANGLE = r'>' + t_COMMA = r',' + t_DOT = r'\.' + t_SEMI = r';' + + t_STRING_LITERAL = string_literal + + # The following floating and integer constants are defined as + # functions to impose a strict order (otherwise, decimal + # is placed before the others because its regex is longer, + # and this is bad) + # + @TOKEN(floating_constant) + def t_FLOAT_CONST(self, t): + return t + + @TOKEN(hex_constant) + def t_INT_CONST_HEX(self, t): + return t + + @TOKEN(octal_constant_disallowed) + def t_OCTAL_CONSTANT_DISALLOWED(self, t): + msg = "Octal values not allowed" + self._error(msg, t) + + @TOKEN(decimal_constant) + def t_INT_CONST_DEC(self, t): + return t + + # unmatched string literals are caught by the preprocessor + + @TOKEN(bad_string_literal) + def t_BAD_STRING_LITERAL(self, t): + msg = "String contains invalid escape code" + self._error(msg, t) + + # Handle ordinal-related tokens in the right order: + @TOKEN(octal_or_hex_ordinal_disallowed) + def t_OCTAL_OR_HEX_ORDINAL_DISALLOWED(self, t): + msg = "Octal and hexadecimal ordinal values not allowed" + self._error(msg, t) + + @TOKEN(ordinal) + def t_ORDINAL(self, t): + return t + + @TOKEN(missing_ordinal_value) + def t_BAD_ORDINAL(self, t): + msg = "Missing ordinal value" + self._error(msg, t) + + @TOKEN(identifier) + def t_NAME(self, t): + t.type = self.keyword_map.get(t.value, "NAME") + return t + + # Ignore C and C++ style comments + def t_COMMENT(self, t): + r'(/\*(.|\n)*?\*/)|(//.*(\n[ \t]*//.*)*)' + t.lexer.lineno += t.value.count("\n") + + def t_error(self, t): + msg = "Illegal character %s" % repr(t.value[0]) + self._error(msg, t) diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/parser.py b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py new file mode 100644 index 0000000000..868fb45f33 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py @@ -0,0 +1,461 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generates a syntax tree from a Mojo IDL file.""" + +import imp +import os.path +import sys + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply import lex +from ply import yacc + +from ..error import Error +from . import ast +from .lexer import Lexer + + +_MAX_ORDINAL_VALUE = 0xffffffff +_MAX_ARRAY_SIZE = 0xffffffff + + +class ParseError(Error): + """Class for errors from the parser.""" + + def __init__(self, filename, message, lineno=None, snippet=None): + Error.__init__(self, filename, message, lineno=lineno, + addenda=([snippet] if snippet else None)) + + +# We have methods which look like they could be functions: +# pylint: disable=R0201 +class Parser(object): + + def __init__(self, lexer, source, filename): + self.tokens = lexer.tokens + self.source = source + self.filename = filename + + # Names of functions + # + # In general, we name functions after the left-hand-side of the rule(s) that + # they handle. E.g., |p_foo_bar| for a rule |foo_bar : ...|. + # + # There may be multiple functions handling rules for the same left-hand-side; + # then we name the functions |p_foo_bar_N| (for left-hand-side |foo_bar|), + # where N is a number (numbered starting from 1). Note that using multiple + # functions is actually more efficient than having single functions handle + # multiple rules (and, e.g., distinguishing them by examining |len(p)|). + # + # It's also possible to have a function handling multiple rules with different + # left-hand-sides. We do not do this. + # + # See http://www.dabeaz.com/ply/ply.html#ply_nn25 for more details. + + # TODO(vtl): Get rid of the braces in the module "statement". (Consider + # renaming "module" -> "package".) Then we'll be able to have a single rule + # for root (by making module "optional"). + def p_root_1(self, p): + """root : """ + p[0] = ast.Mojom(None, ast.ImportList(), []) + + def p_root_2(self, p): + """root : root module""" + if p[1].module is not None: + raise ParseError(self.filename, + "Multiple \"module\" statements not allowed:", + p[2].lineno, snippet=self._GetSnippet(p[2].lineno)) + if p[1].import_list.items or p[1].definition_list: + raise ParseError( + self.filename, + "\"module\" statements must precede imports and definitions:", + p[2].lineno, snippet=self._GetSnippet(p[2].lineno)) + p[0] = p[1] + p[0].module = p[2] + + def p_root_3(self, p): + """root : root import""" + if p[1].definition_list: + raise ParseError(self.filename, + "\"import\" statements must precede definitions:", + p[2].lineno, snippet=self._GetSnippet(p[2].lineno)) + p[0] = p[1] + p[0].import_list.Append(p[2]) + + def p_root_4(self, p): + """root : root definition""" + p[0] = p[1] + p[0].definition_list.append(p[2]) + + def p_import(self, p): + """import : IMPORT STRING_LITERAL SEMI""" + # 'eval' the literal to strip the quotes. + # TODO(vtl): This eval is dubious. We should unquote/unescape ourselves. + p[0] = ast.Import(eval(p[2]), filename=self.filename, lineno=p.lineno(2)) + + def p_module(self, p): + """module : attribute_section MODULE identifier_wrapped SEMI""" + p[0] = ast.Module(p[3], p[1], filename=self.filename, lineno=p.lineno(2)) + + def p_definition(self, p): + """definition : struct + | union + | interface + | enum + | const""" + p[0] = p[1] + + def p_attribute_section_1(self, p): + """attribute_section : """ + p[0] = None + + def p_attribute_section_2(self, p): + """attribute_section : LBRACKET attribute_list RBRACKET""" + p[0] = p[2] + + def p_attribute_list_1(self, p): + """attribute_list : """ + p[0] = ast.AttributeList() + + def p_attribute_list_2(self, p): + """attribute_list : nonempty_attribute_list""" + p[0] = p[1] + + def p_nonempty_attribute_list_1(self, p): + """nonempty_attribute_list : attribute""" + p[0] = ast.AttributeList(p[1]) + + def p_nonempty_attribute_list_2(self, p): + """nonempty_attribute_list : nonempty_attribute_list COMMA attribute""" + p[0] = p[1] + p[0].Append(p[3]) + + def p_attribute_1(self, p): + """attribute : NAME EQUALS evaled_literal + | NAME EQUALS NAME""" + p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1)) + + def p_attribute_2(self, p): + """attribute : NAME""" + p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1)) + + def p_evaled_literal(self, p): + """evaled_literal : literal""" + # 'eval' the literal to strip the quotes. Handle keywords "true" and "false" + # specially since they cannot directly be evaluated to python boolean + # values. + if p[1] == "true": + p[0] = True + elif p[1] == "false": + p[0] = False + else: + p[0] = eval(p[1]) + + def p_struct_1(self, p): + """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI""" + p[0] = ast.Struct(p[3], p[1], p[5]) + + def p_struct_2(self, p): + """struct : attribute_section STRUCT NAME SEMI""" + p[0] = ast.Struct(p[3], p[1], None) + + def p_struct_body_1(self, p): + """struct_body : """ + p[0] = ast.StructBody() + + def p_struct_body_2(self, p): + """struct_body : struct_body const + | struct_body enum + | struct_body struct_field""" + p[0] = p[1] + p[0].Append(p[2]) + + def p_struct_field(self, p): + """struct_field : attribute_section typename NAME ordinal default SEMI""" + p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5]) + + def p_union(self, p): + """union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI""" + p[0] = ast.Union(p[3], p[1], p[5]) + + def p_union_body_1(self, p): + """union_body : """ + p[0] = ast.UnionBody() + + def p_union_body_2(self, p): + """union_body : union_body union_field""" + p[0] = p[1] + p[1].Append(p[2]) + + def p_union_field(self, p): + """union_field : attribute_section typename NAME ordinal SEMI""" + p[0] = ast.UnionField(p[3], p[1], p[4], p[2]) + + def p_default_1(self, p): + """default : """ + p[0] = None + + def p_default_2(self, p): + """default : EQUALS constant""" + p[0] = p[2] + + def p_interface(self, p): + """interface : attribute_section INTERFACE NAME LBRACE interface_body \ + RBRACE SEMI""" + p[0] = ast.Interface(p[3], p[1], p[5]) + + def p_interface_body_1(self, p): + """interface_body : """ + p[0] = ast.InterfaceBody() + + def p_interface_body_2(self, p): + """interface_body : interface_body const + | interface_body enum + | interface_body method""" + p[0] = p[1] + p[0].Append(p[2]) + + def p_response_1(self, p): + """response : """ + p[0] = None + + def p_response_2(self, p): + """response : RESPONSE LPAREN parameter_list RPAREN""" + p[0] = p[3] + + def p_method(self, p): + """method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \ + response SEMI""" + p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7]) + + def p_parameter_list_1(self, p): + """parameter_list : """ + p[0] = ast.ParameterList() + + def p_parameter_list_2(self, p): + """parameter_list : nonempty_parameter_list""" + p[0] = p[1] + + def p_nonempty_parameter_list_1(self, p): + """nonempty_parameter_list : parameter""" + p[0] = ast.ParameterList(p[1]) + + def p_nonempty_parameter_list_2(self, p): + """nonempty_parameter_list : nonempty_parameter_list COMMA parameter""" + p[0] = p[1] + p[0].Append(p[3]) + + def p_parameter(self, p): + """parameter : attribute_section typename NAME ordinal""" + p[0] = ast.Parameter(p[3], p[1], p[4], p[2], + filename=self.filename, lineno=p.lineno(3)) + + def p_typename(self, p): + """typename : nonnullable_typename QSTN + | nonnullable_typename""" + if len(p) == 2: + p[0] = p[1] + else: + p[0] = p[1] + "?" + + def p_nonnullable_typename(self, p): + """nonnullable_typename : basictypename + | array + | fixed_array + | associative_array + | interfacerequest""" + p[0] = p[1] + + def p_basictypename(self, p): + """basictypename : identifier + | ASSOCIATED identifier + | handletype""" + if len(p) == 2: + p[0] = p[1] + else: + p[0] = "asso<" + p[2] + ">" + + def p_handletype(self, p): + """handletype : HANDLE + | HANDLE LANGLE NAME RANGLE""" + if len(p) == 2: + p[0] = p[1] + else: + if p[3] not in ('data_pipe_consumer', + 'data_pipe_producer', + 'message_pipe', + 'shared_buffer'): + # Note: We don't enable tracking of line numbers for everything, so we + # can't use |p.lineno(3)|. + raise ParseError(self.filename, "Invalid handle type %r:" % p[3], + lineno=p.lineno(1), + snippet=self._GetSnippet(p.lineno(1))) + p[0] = "handle<" + p[3] + ">" + + def p_array(self, p): + """array : ARRAY LANGLE typename RANGLE""" + p[0] = p[3] + "[]" + + def p_fixed_array(self, p): + """fixed_array : ARRAY LANGLE typename COMMA INT_CONST_DEC RANGLE""" + value = int(p[5]) + if value == 0 or value > _MAX_ARRAY_SIZE: + raise ParseError(self.filename, "Fixed array size %d invalid:" % value, + lineno=p.lineno(5), + snippet=self._GetSnippet(p.lineno(5))) + p[0] = p[3] + "[" + p[5] + "]" + + def p_associative_array(self, p): + """associative_array : MAP LANGLE identifier COMMA typename RANGLE""" + p[0] = p[5] + "{" + p[3] + "}" + + def p_interfacerequest(self, p): + """interfacerequest : identifier AMP + | ASSOCIATED identifier AMP""" + if len(p) == 3: + p[0] = p[1] + "&" + else: + p[0] = "asso<" + p[2] + "&>" + + def p_ordinal_1(self, p): + """ordinal : """ + p[0] = None + + def p_ordinal_2(self, p): + """ordinal : ORDINAL""" + value = int(p[1][1:]) + if value > _MAX_ORDINAL_VALUE: + raise ParseError(self.filename, "Ordinal value %d too large:" % value, + lineno=p.lineno(1), + snippet=self._GetSnippet(p.lineno(1))) + p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1)) + + def p_enum_1(self, p): + """enum : attribute_section ENUM NAME LBRACE enum_value_list \ + RBRACE SEMI + | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \ + COMMA RBRACE SEMI""" + p[0] = ast.Enum(p[3], p[1], p[5], filename=self.filename, + lineno=p.lineno(2)) + + def p_enum_2(self, p): + """enum : attribute_section ENUM NAME SEMI""" + p[0] = ast.Enum(p[3], p[1], None, filename=self.filename, + lineno=p.lineno(2)) + + def p_enum_value_list_1(self, p): + """enum_value_list : """ + p[0] = ast.EnumValueList() + + def p_enum_value_list_2(self, p): + """enum_value_list : nonempty_enum_value_list""" + p[0] = p[1] + + def p_nonempty_enum_value_list_1(self, p): + """nonempty_enum_value_list : enum_value""" + p[0] = ast.EnumValueList(p[1]) + + def p_nonempty_enum_value_list_2(self, p): + """nonempty_enum_value_list : nonempty_enum_value_list COMMA enum_value""" + p[0] = p[1] + p[0].Append(p[3]) + + def p_enum_value(self, p): + """enum_value : attribute_section NAME + | attribute_section NAME EQUALS int + | attribute_section NAME EQUALS identifier_wrapped""" + p[0] = ast.EnumValue(p[2], p[1], p[4] if len(p) == 5 else None, + filename=self.filename, lineno=p.lineno(2)) + + def p_const(self, p): + """const : CONST typename NAME EQUALS constant SEMI""" + p[0] = ast.Const(p[3], p[2], p[5]) + + def p_constant(self, p): + """constant : literal + | identifier_wrapped""" + p[0] = p[1] + + def p_identifier_wrapped(self, p): + """identifier_wrapped : identifier""" + p[0] = ('IDENTIFIER', p[1]) + + # TODO(vtl): Make this produce a "wrapped" identifier (probably as an + # |ast.Identifier|, to be added) and get rid of identifier_wrapped. + def p_identifier(self, p): + """identifier : NAME + | NAME DOT identifier""" + p[0] = ''.join(p[1:]) + + def p_literal(self, p): + """literal : int + | float + | TRUE + | FALSE + | DEFAULT + | STRING_LITERAL""" + p[0] = p[1] + + def p_int(self, p): + """int : int_const + | PLUS int_const + | MINUS int_const""" + p[0] = ''.join(p[1:]) + + def p_int_const(self, p): + """int_const : INT_CONST_DEC + | INT_CONST_HEX""" + p[0] = p[1] + + def p_float(self, p): + """float : FLOAT_CONST + | PLUS FLOAT_CONST + | MINUS FLOAT_CONST""" + p[0] = ''.join(p[1:]) + + def p_error(self, e): + if e is None: + # Unexpected EOF. + # TODO(vtl): Can we figure out what's missing? + raise ParseError(self.filename, "Unexpected end of file") + + raise ParseError(self.filename, "Unexpected %r:" % e.value, lineno=e.lineno, + snippet=self._GetSnippet(e.lineno)) + + def _GetSnippet(self, lineno): + return self.source.split('\n')[lineno - 1] + + +def Parse(source, filename): + """Parse source file to AST. + + Args: + source: The source text as a str. + filename: The filename that |source| originates from. + + Returns: + The AST as a mojom.parse.ast.Mojom object. + """ + lexer = Lexer(filename) + parser = Parser(lexer, source, filename) + + lex.lex(object=lexer) + yacc.yacc(module=parser, debug=0, write_tables=0) + + tree = yacc.parse(source) + return tree diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py new file mode 100644 index 0000000000..d56faadb19 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py @@ -0,0 +1,55 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import shutil +import sys +import tempfile +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom import fileutil + + +class FileUtilTest(unittest.TestCase): + + def testEnsureDirectoryExists(self): + """Test that EnsureDirectoryExists fuctions correctly.""" + + temp_dir = tempfile.mkdtemp() + try: + self.assertTrue(os.path.exists(temp_dir)) + + # Directory does not exist, yet. + full = os.path.join(temp_dir, "foo", "bar") + self.assertFalse(os.path.exists(full)) + + # Create the directory. + fileutil.EnsureDirectoryExists(full) + self.assertTrue(os.path.exists(full)) + + # Trying to create it again does not cause an error. + fileutil.EnsureDirectoryExists(full) + self.assertTrue(os.path.exists(full)) + + # Bypass check for directory existence to tickle error handling that + # occurs in response to a race. + fileutil.EnsureDirectoryExists(full, always_try_to_create=True) + self.assertTrue(os.path.exists(full)) + finally: + shutil.rmtree(temp_dir) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py new file mode 100644 index 0000000000..70c92a38fb --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py @@ -0,0 +1,156 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.generate import data +from mojom.generate import module as mojom + + +class DataTest(unittest.TestCase): + + def testStructDataConversion(self): + """Tests that a struct can be converted from data.""" + module = mojom.Module('test_module', 'test_namespace') + struct_data = { + 'name': 'SomeStruct', + 'enums': [], + 'constants': [], + 'fields': [ + {'name': 'field1', 'kind': 'i32'}, + {'name': 'field2', 'kind': 'i32', 'ordinal': 10}, + {'name': 'field3', 'kind': 'i32', 'default': 15}]} + + struct = data.StructFromData(module, struct_data) + struct.fields = map(lambda field: + data.StructFieldFromData(module, field, struct), struct.fields_data) + self.assertEquals(struct_data, data.StructToData(struct)) + + def testUnionDataConversion(self): + """Tests that a union can be converted from data.""" + module = mojom.Module('test_module', 'test_namespace') + union_data = { + 'name': 'SomeUnion', + 'fields': [ + {'name': 'field1', 'kind': 'i32'}, + {'name': 'field2', 'kind': 'i32', 'ordinal': 10}]} + + union = data.UnionFromData(module, union_data) + union.fields = map(lambda field: + data.UnionFieldFromData(module, field, union), union.fields_data) + self.assertEquals(union_data, data.UnionToData(union)) + + def testImportFromDataNoMissingImports(self): + """Tests that unions, structs, interfaces and enums are imported.""" + module = mojom.Module('test_module', 'test_namespace') + imported_module = mojom.Module('import_module', 'import_namespace') + #TODO(azani): Init values in module.py. + #TODO(azani): Test that values are imported. + imported_module.values = {} + imported_data = {'module' : imported_module} + + + struct = mojom.Struct('TestStruct', module=module) + imported_module.kinds[struct.spec] = struct + + union = mojom.Union('TestUnion', module=module) + imported_module.kinds[union.spec] = union + + interface = mojom.Interface('TestInterface', module=module) + imported_module.kinds[interface.spec] = interface + + enum = mojom.Enum('TestEnum', module=module) + imported_module.kinds[enum.spec] = enum + + data.ImportFromData(module, imported_data) + + # Test that the kind was imported. + self.assertIn(struct.spec, module.kinds) + self.assertEquals(struct.name, module.kinds[struct.spec].name) + + self.assertIn(union.spec, module.kinds) + self.assertEquals(union.name, module.kinds[union.spec].name) + + self.assertIn(interface.spec, module.kinds) + self.assertEquals(interface.name, module.kinds[interface.spec].name) + + self.assertIn(enum.spec, module.kinds) + self.assertEquals(enum.name, module.kinds[enum.spec].name) + + # Test that the imported kind is a copy and not the original. + self.assertIsNot(struct, module.kinds[struct.spec]) + self.assertIsNot(union, module.kinds[union.spec]) + self.assertIsNot(interface, module.kinds[interface.spec]) + self.assertIsNot(enum, module.kinds[enum.spec]) + + def testImportFromDataNoExtraneousImports(self): + """Tests that arrays, maps and interface requests are not imported.""" + module = mojom.Module('test_module', 'test_namespace') + imported_module = mojom.Module('import_module', 'import_namespace') + #TODO(azani): Init values in module.py. + imported_module.values = {} + imported_data = {'module' : imported_module} + + array = mojom.Array(mojom.INT16, length=20) + imported_module.kinds[array.spec] = array + + map_kind = mojom.Map(mojom.INT16, mojom.INT16) + imported_module.kinds[map_kind.spec] = map_kind + + interface = mojom.Interface('TestInterface', module=module) + imported_module.kinds[interface.spec] = interface + + interface_req = mojom.InterfaceRequest(interface) + imported_module.kinds[interface_req.spec] = interface_req + + data.ImportFromData(module, imported_data) + + self.assertNotIn(array.spec, module.kinds) + self.assertNotIn(map_kind.spec, module.kinds) + self.assertNotIn(interface_req.spec, module.kinds) + + def testNonInterfaceAsInterfaceRequest(self): + """Tests that a non-interface cannot be used for interface requests.""" + module = mojom.Module('test_module', 'test_namespace') + interface = mojom.Interface('TestInterface', module=module) + method_dict = { + 'name': 'Foo', + 'parameters': [{'name': 'foo', 'kind': 'r:i32'}], + } + with self.assertRaises(Exception) as e: + data.MethodFromData(module, method_dict, interface) + self.assertEquals(e.exception.__str__(), + 'Interface request requires \'i32\' to be an interface.') + + def testNonInterfaceAsAssociatedInterface(self): + """Tests that a non-interface type cannot be used for associated + interfaces.""" + module = mojom.Module('test_module', 'test_namespace') + interface = mojom.Interface('TestInterface', module=module) + method_dict = { + 'name': 'Foo', + 'parameters': [{'name': 'foo', 'kind': 'asso:i32'}], + } + with self.assertRaises(Exception) as e: + data.MethodFromData(module, method_dict, interface) + self.assertEquals( + e.exception.__str__(), + 'Associated interface requires \'i32\' to be an interface.') diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py new file mode 100644 index 0000000000..a684773719 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py @@ -0,0 +1,37 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.generate import generator + + +class StringManipulationTest(unittest.TestCase): + """generator contains some string utilities, this tests only those.""" + + def testUnderToCamel(self): + """Tests UnderToCamel which converts underscore_separated to CamelCase.""" + self.assertEquals("CamelCase", generator.UnderToCamel("camel_case")) + self.assertEquals("CamelCase", generator.UnderToCamel("CAMEL_CASE")) + +if __name__ == "__main__": + unittest.main() + diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py new file mode 100644 index 0000000000..75b7cd5437 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py @@ -0,0 +1,48 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.generate import module as mojom + + +class ModuleTest(unittest.TestCase): + + def testNonInterfaceAsInterfaceRequest(self): + """Tests that a non-interface cannot be used for interface requests.""" + module = mojom.Module('test_module', 'test_namespace') + struct = mojom.Struct('TestStruct', module=module) + with self.assertRaises(Exception) as e: + mojom.InterfaceRequest(struct) + self.assertEquals( + e.exception.__str__(), + 'Interface request requires \'x:TestStruct\' to be an interface.') + + def testNonInterfaceAsAssociatedInterface(self): + """Tests that a non-interface type cannot be used for associated interfaces. + """ + module = mojom.Module('test_module', 'test_namespace') + struct = mojom.Struct('TestStruct', module=module) + with self.assertRaises(Exception) as e: + mojom.AssociatedInterface(struct) + self.assertEquals( + e.exception.__str__(), + 'Associated interface requires \'x:TestStruct\' to be an interface.') diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py new file mode 100644 index 0000000000..75f6d5163e --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py @@ -0,0 +1,136 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.generate import pack +from mojom.generate import module as mojom + + +# TODO(yzshen): Move tests in pack_tests.py here. +class PackTest(unittest.TestCase): + def _CheckPackSequence(self, kinds, fields, offsets): + """Checks the pack order and offsets of a sequence of mojom.Kinds. + + Args: + kinds: A sequence of mojom.Kinds that specify the fields that are to be + created. + fields: The expected order of the resulting fields, with the integer "1" + first. + offsets: The expected order of offsets, with the integer "0" first. + """ + struct = mojom.Struct('test') + index = 1 + for kind in kinds: + struct.AddField('%d' % index, kind) + index += 1 + ps = pack.PackedStruct(struct) + num_fields = len(ps.packed_fields) + self.assertEquals(len(kinds), num_fields) + for i in xrange(num_fields): + self.assertEquals('%d' % fields[i], ps.packed_fields[i].field.name) + self.assertEquals(offsets[i], ps.packed_fields[i].offset) + + def testMinVersion(self): + """Tests that |min_version| is properly set for packed fields.""" + struct = mojom.Struct('test') + struct.AddField('field_2', mojom.BOOL, 2) + struct.AddField('field_0', mojom.INT32, 0) + struct.AddField('field_1', mojom.INT64, 1) + ps = pack.PackedStruct(struct) + + self.assertEquals('field_0', ps.packed_fields[0].field.name) + self.assertEquals('field_2', ps.packed_fields[1].field.name) + self.assertEquals('field_1', ps.packed_fields[2].field.name) + + self.assertEquals(0, ps.packed_fields[0].min_version) + self.assertEquals(0, ps.packed_fields[1].min_version) + self.assertEquals(0, ps.packed_fields[2].min_version) + + struct.fields[0].attributes = {'MinVersion': 1} + ps = pack.PackedStruct(struct) + + self.assertEquals(0, ps.packed_fields[0].min_version) + self.assertEquals(1, ps.packed_fields[1].min_version) + self.assertEquals(0, ps.packed_fields[2].min_version) + + def testGetVersionInfoEmptyStruct(self): + """Tests that pack.GetVersionInfo() never returns an empty list, even for + empty structs. + """ + struct = mojom.Struct('test') + ps = pack.PackedStruct(struct) + + versions = pack.GetVersionInfo(ps) + self.assertEquals(1, len(versions)) + self.assertEquals(0, versions[0].version) + self.assertEquals(0, versions[0].num_fields) + self.assertEquals(8, versions[0].num_bytes) + + def testGetVersionInfoComplexOrder(self): + """Tests pack.GetVersionInfo() using a struct whose definition order, + ordinal order and pack order for fields are all different. + """ + struct = mojom.Struct('test') + struct.AddField('field_3', mojom.BOOL, ordinal=3, + attributes={'MinVersion': 3}) + struct.AddField('field_0', mojom.INT32, ordinal=0) + struct.AddField('field_1', mojom.INT64, ordinal=1, + attributes={'MinVersion': 2}) + struct.AddField('field_2', mojom.INT64, ordinal=2, + attributes={'MinVersion': 3}) + ps = pack.PackedStruct(struct) + + versions = pack.GetVersionInfo(ps) + self.assertEquals(3, len(versions)) + + self.assertEquals(0, versions[0].version) + self.assertEquals(1, versions[0].num_fields) + self.assertEquals(16, versions[0].num_bytes) + + self.assertEquals(2, versions[1].version) + self.assertEquals(2, versions[1].num_fields) + self.assertEquals(24, versions[1].num_bytes) + + self.assertEquals(3, versions[2].version) + self.assertEquals(4, versions[2].num_fields) + self.assertEquals(32, versions[2].num_bytes) + + def testInterfaceAlignment(self): + """Tests that interfaces are aligned on 4-byte boundaries, although the size + of an interface is 8 bytes. + """ + kinds = (mojom.INT32, mojom.Interface('test_interface')) + fields = (1, 2) + offsets = (0, 4) + self._CheckPackSequence(kinds, fields, offsets) + + def testAssociatedInterfaceAlignment(self): + """Tests that associated interfaces are aligned on 4-byte boundaries, + although the size of an associated interface is 8 bytes. + """ + kinds = (mojom.INT32, + mojom.AssociatedInterface(mojom.Interface('test_interface'))) + fields = (1, 2) + offsets = (0, 4) + self._CheckPackSequence(kinds, fields, offsets) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py new file mode 100644 index 0000000000..dd28cdd120 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py @@ -0,0 +1,135 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +import mojom.parse.ast as ast + + +class _TestNode(ast.NodeBase): + """Node type for tests.""" + + def __init__(self, value, **kwargs): + super(_TestNode, self).__init__(**kwargs) + self.value = value + + def __eq__(self, other): + return super(_TestNode, self).__eq__(other) and self.value == other.value + + +class _TestNodeList(ast.NodeListBase): + """Node list type for tests.""" + + _list_item_type = _TestNode + + +class ASTTest(unittest.TestCase): + """Tests various AST classes.""" + + def testNodeBase(self): + # Test |__eq__()|; this is only used for testing, where we want to do + # comparison by value and ignore filenames/line numbers (for convenience). + node1 = ast.NodeBase(filename="hello.mojom", lineno=123) + node2 = ast.NodeBase() + self.assertEquals(node1, node2) + self.assertEquals(node2, node1) + + # Check that |__ne__()| just defers to |__eq__()| properly. + self.assertFalse(node1 != node2) + self.assertFalse(node2 != node1) + + # Check that |filename| and |lineno| are set properly (and are None by + # default). + self.assertEquals(node1.filename, "hello.mojom") + self.assertEquals(node1.lineno, 123) + self.assertIsNone(node2.filename) + self.assertIsNone(node2.lineno) + + # |NodeBase|'s |__eq__()| should compare types (and a subclass's |__eq__()| + # should first defer to its superclass's). + node3 = _TestNode(123) + self.assertNotEqual(node1, node3) + self.assertNotEqual(node3, node1) + # Also test |__eq__()| directly. + self.assertFalse(node1 == node3) + self.assertFalse(node3 == node1) + + node4 = _TestNode(123, filename="world.mojom", lineno=123) + self.assertEquals(node4, node3) + node5 = _TestNode(456) + self.assertNotEquals(node5, node4) + + def testNodeListBase(self): + node1 = _TestNode(1, filename="foo.mojom", lineno=1) + # Equal to, but not the same as, |node1|: + node1b = _TestNode(1, filename="foo.mojom", lineno=1) + node2 = _TestNode(2, filename="foo.mojom", lineno=2) + + nodelist1 = _TestNodeList() # Contains: (empty). + self.assertEquals(nodelist1, nodelist1) + self.assertEquals(nodelist1.items, []) + self.assertIsNone(nodelist1.filename) + self.assertIsNone(nodelist1.lineno) + + nodelist2 = _TestNodeList(node1) # Contains: 1. + self.assertEquals(nodelist2, nodelist2) + self.assertEquals(nodelist2.items, [node1]) + self.assertNotEqual(nodelist2, nodelist1) + self.assertEquals(nodelist2.filename, "foo.mojom") + self.assertEquals(nodelist2.lineno, 1) + + nodelist3 = _TestNodeList([node2]) # Contains: 2. + self.assertEquals(nodelist3.items, [node2]) + self.assertNotEqual(nodelist3, nodelist1) + self.assertNotEqual(nodelist3, nodelist2) + self.assertEquals(nodelist3.filename, "foo.mojom") + self.assertEquals(nodelist3.lineno, 2) + + nodelist1.Append(node1b) # Contains: 1. + self.assertEquals(nodelist1.items, [node1]) + self.assertEquals(nodelist1, nodelist2) + self.assertNotEqual(nodelist1, nodelist3) + self.assertEquals(nodelist1.filename, "foo.mojom") + self.assertEquals(nodelist1.lineno, 1) + + nodelist1.Append(node2) # Contains: 1, 2. + self.assertEquals(nodelist1.items, [node1, node2]) + self.assertNotEqual(nodelist1, nodelist2) + self.assertNotEqual(nodelist1, nodelist3) + self.assertEquals(nodelist1.lineno, 1) + + nodelist2.Append(node2) # Contains: 1, 2. + self.assertEquals(nodelist2.items, [node1, node2]) + self.assertEquals(nodelist2, nodelist1) + self.assertNotEqual(nodelist2, nodelist3) + self.assertEquals(nodelist2.lineno, 1) + + nodelist3.Insert(node1) # Contains: 1, 2. + self.assertEquals(nodelist3.items, [node1, node2]) + self.assertEquals(nodelist3, nodelist1) + self.assertEquals(nodelist3, nodelist2) + self.assertEquals(nodelist3.lineno, 1) + + # Test iteration: + i = 1 + for item in nodelist1: + self.assertEquals(item.value, i) + i += 1 diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py new file mode 100644 index 0000000000..6822cbc8d0 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py @@ -0,0 +1,192 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply import lex + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +import mojom.parse.lexer + + +# This (monkey-patching LexToken to make comparison value-based) is evil, but +# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing +# for object identity.) +def _LexTokenEq(self, other): + return self.type == other.type and self.value == other.value and \ + self.lineno == other.lineno and self.lexpos == other.lexpos +setattr(lex.LexToken, '__eq__', _LexTokenEq) + + +def _MakeLexToken(token_type, value, lineno=1, lexpos=0): + """Makes a LexToken with the given parameters. (Note that lineno is 1-based, + but lexpos is 0-based.)""" + rv = lex.LexToken() + rv.type, rv.value, rv.lineno, rv.lexpos = token_type, value, lineno, lexpos + return rv + + +def _MakeLexTokenForKeyword(keyword, **kwargs): + """Makes a LexToken for the given keyword.""" + return _MakeLexToken(keyword.upper(), keyword.lower(), **kwargs) + + +class LexerTest(unittest.TestCase): + """Tests |mojom.parse.lexer.Lexer|.""" + + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + # Clone all lexer instances from this one, since making a lexer is slow. + self._zygote_lexer = lex.lex(mojom.parse.lexer.Lexer("my_file.mojom")) + + def testValidKeywords(self): + """Tests valid keywords.""" + self.assertEquals(self._SingleTokenForInput("handle"), + _MakeLexTokenForKeyword("handle")) + self.assertEquals(self._SingleTokenForInput("import"), + _MakeLexTokenForKeyword("import")) + self.assertEquals(self._SingleTokenForInput("module"), + _MakeLexTokenForKeyword("module")) + self.assertEquals(self._SingleTokenForInput("struct"), + _MakeLexTokenForKeyword("struct")) + self.assertEquals(self._SingleTokenForInput("union"), + _MakeLexTokenForKeyword("union")) + self.assertEquals(self._SingleTokenForInput("interface"), + _MakeLexTokenForKeyword("interface")) + self.assertEquals(self._SingleTokenForInput("enum"), + _MakeLexTokenForKeyword("enum")) + self.assertEquals(self._SingleTokenForInput("const"), + _MakeLexTokenForKeyword("const")) + self.assertEquals(self._SingleTokenForInput("true"), + _MakeLexTokenForKeyword("true")) + self.assertEquals(self._SingleTokenForInput("false"), + _MakeLexTokenForKeyword("false")) + self.assertEquals(self._SingleTokenForInput("default"), + _MakeLexTokenForKeyword("default")) + self.assertEquals(self._SingleTokenForInput("array"), + _MakeLexTokenForKeyword("array")) + self.assertEquals(self._SingleTokenForInput("map"), + _MakeLexTokenForKeyword("map")) + self.assertEquals(self._SingleTokenForInput("associated"), + _MakeLexTokenForKeyword("associated")) + + def testValidIdentifiers(self): + """Tests identifiers.""" + self.assertEquals(self._SingleTokenForInput("abcd"), + _MakeLexToken("NAME", "abcd")) + self.assertEquals(self._SingleTokenForInput("AbC_d012_"), + _MakeLexToken("NAME", "AbC_d012_")) + self.assertEquals(self._SingleTokenForInput("_0123"), + _MakeLexToken("NAME", "_0123")) + + def testInvalidIdentifiers(self): + with self.assertRaisesRegexp( + mojom.parse.lexer.LexError, + r"^my_file\.mojom:1: Error: Illegal character '\$'$"): + self._TokensForInput("$abc") + with self.assertRaisesRegexp( + mojom.parse.lexer.LexError, + r"^my_file\.mojom:1: Error: Illegal character '\$'$"): + self._TokensForInput("a$bc") + + def testDecimalIntegerConstants(self): + self.assertEquals(self._SingleTokenForInput("0"), + _MakeLexToken("INT_CONST_DEC", "0")) + self.assertEquals(self._SingleTokenForInput("1"), + _MakeLexToken("INT_CONST_DEC", "1")) + self.assertEquals(self._SingleTokenForInput("123"), + _MakeLexToken("INT_CONST_DEC", "123")) + self.assertEquals(self._SingleTokenForInput("10"), + _MakeLexToken("INT_CONST_DEC", "10")) + + def testValidTokens(self): + """Tests valid tokens (which aren't tested elsewhere).""" + # Keywords tested in |testValidKeywords|. + # NAME tested in |testValidIdentifiers|. + self.assertEquals(self._SingleTokenForInput("@123"), + _MakeLexToken("ORDINAL", "@123")) + self.assertEquals(self._SingleTokenForInput("456"), + _MakeLexToken("INT_CONST_DEC", "456")) + self.assertEquals(self._SingleTokenForInput("0x01aB2eF3"), + _MakeLexToken("INT_CONST_HEX", "0x01aB2eF3")) + self.assertEquals(self._SingleTokenForInput("123.456"), + _MakeLexToken("FLOAT_CONST", "123.456")) + self.assertEquals(self._SingleTokenForInput("\"hello\""), + _MakeLexToken("STRING_LITERAL", "\"hello\"")) + self.assertEquals(self._SingleTokenForInput("+"), + _MakeLexToken("PLUS", "+")) + self.assertEquals(self._SingleTokenForInput("-"), + _MakeLexToken("MINUS", "-")) + self.assertEquals(self._SingleTokenForInput("&"), + _MakeLexToken("AMP", "&")) + self.assertEquals(self._SingleTokenForInput("?"), + _MakeLexToken("QSTN", "?")) + self.assertEquals(self._SingleTokenForInput("="), + _MakeLexToken("EQUALS", "=")) + self.assertEquals(self._SingleTokenForInput("=>"), + _MakeLexToken("RESPONSE", "=>")) + self.assertEquals(self._SingleTokenForInput("("), + _MakeLexToken("LPAREN", "(")) + self.assertEquals(self._SingleTokenForInput(")"), + _MakeLexToken("RPAREN", ")")) + self.assertEquals(self._SingleTokenForInput("["), + _MakeLexToken("LBRACKET", "[")) + self.assertEquals(self._SingleTokenForInput("]"), + _MakeLexToken("RBRACKET", "]")) + self.assertEquals(self._SingleTokenForInput("{"), + _MakeLexToken("LBRACE", "{")) + self.assertEquals(self._SingleTokenForInput("}"), + _MakeLexToken("RBRACE", "}")) + self.assertEquals(self._SingleTokenForInput("<"), + _MakeLexToken("LANGLE", "<")) + self.assertEquals(self._SingleTokenForInput(">"), + _MakeLexToken("RANGLE", ">")) + self.assertEquals(self._SingleTokenForInput(";"), + _MakeLexToken("SEMI", ";")) + self.assertEquals(self._SingleTokenForInput(","), + _MakeLexToken("COMMA", ",")) + self.assertEquals(self._SingleTokenForInput("."), + _MakeLexToken("DOT", ".")) + + def _TokensForInput(self, input_string): + """Gets a list of tokens for the given input string.""" + lexer = self._zygote_lexer.clone() + lexer.input(input_string) + rv = [] + while True: + tok = lexer.token() + if not tok: + return rv + rv.append(tok) + + def _SingleTokenForInput(self, input_string): + """Gets the single token for the given input string. (Raises an exception if + the input string does not result in exactly one token.)""" + toks = self._TokensForInput(input_string) + assert len(toks) == 1 + return toks[0] + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py new file mode 100644 index 0000000000..3f4ca871e3 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py @@ -0,0 +1,1497 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +import mojom.parse.ast as ast +import mojom.parse.lexer as lexer +import mojom.parse.parser as parser + + +class ParserTest(unittest.TestCase): + """Tests |parser.Parse()|.""" + + def testTrivialValidSource(self): + """Tests a trivial, but valid, .mojom source.""" + + source = """\ + // This is a comment. + + module my_module; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + []) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testSourceWithCrLfs(self): + """Tests a .mojom source with CR-LFs instead of LFs.""" + + source = "// This is a comment.\r\n\r\nmodule my_module;\r\n" + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + []) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testUnexpectedEOF(self): + """Tests a "truncated" .mojom source.""" + + source = """\ + // This is a comment. + + module my_module + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom: Error: Unexpected end of file$"): + parser.Parse(source, "my_file.mojom") + + def testCommentLineNumbers(self): + """Tests that line numbers are correctly tracked when comments are + present.""" + + source1 = """\ + // Isolated C++-style comments. + + // Foo. + asdf1 + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'asdf1':\n *asdf1$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + // Consecutive C++-style comments. + // Foo. + // Bar. + + struct Yada { // Baz. + // Quux. + int32 x; + }; + + asdf2 + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:10: Error: Unexpected 'asdf2':\n *asdf2$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + /* Single-line C-style comments. */ + /* Foobar. */ + + /* Baz. */ + asdf3 + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:5: Error: Unexpected 'asdf3':\n *asdf3$"): + parser.Parse(source3, "my_file.mojom") + + source4 = """\ + /* Multi-line C-style comments. + */ + /* + Foo. + Bar. + */ + + /* Baz + Quux. */ + asdf4 + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:10: Error: Unexpected 'asdf4':\n *asdf4$"): + parser.Parse(source4, "my_file.mojom") + + + def testSimpleStruct(self): + """Tests a simple .mojom source that just defines a struct.""" + + source = """\ + module my_module; + + struct MyStruct { + int32 a; + double b; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None, 'int32', None), + ast.StructField('b', None, None, 'double', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testSimpleStructWithoutModule(self): + """Tests a simple struct without an explict module statement.""" + + source = """\ + struct MyStruct { + int32 a; + double b; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None, 'int32', None), + ast.StructField('b', None, None, 'double', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidStructDefinitions(self): + """Tests all types of definitions that can occur in a struct.""" + + source = """\ + struct MyStruct { + enum MyEnum { VALUE }; + const double kMyConst = 1.23; + int32 a; + SomeOtherStruct b; // Invalidity detected at another stage. + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.Enum('MyEnum', + None, + ast.EnumValueList( + ast.EnumValue('VALUE', None, None))), + ast.Const('kMyConst', 'double', '1.23'), + ast.StructField('a', None, None, 'int32', None), + ast.StructField('b', None, None, 'SomeOtherStruct', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidStructDefinitions(self): + """Tests that definitions that aren't allowed in a struct are correctly + detected.""" + + source1 = """\ + struct MyStruct { + MyMethod(int32 a); + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '\(':\n" + r" *MyMethod\(int32 a\);$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + struct MyStruct { + struct MyInnerStruct { + int32 a; + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" + r" *struct MyInnerStruct {$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + struct MyStruct { + interface MyInterface { + MyMethod(int32 a); + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'interface':\n" + r" *interface MyInterface {$"): + parser.Parse(source3, "my_file.mojom") + + def testMissingModuleName(self): + """Tests an (invalid) .mojom with a missing module name.""" + + source1 = """\ + // Missing module name. + module ; + struct MyStruct { + int32 a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected ';':\n *module ;$"): + parser.Parse(source1, "my_file.mojom") + + # Another similar case, but make sure that line-number tracking/reporting + # is correct. + source2 = """\ + module + // This line intentionally left unblank. + + struct MyStruct { + int32 a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source2, "my_file.mojom") + + def testMultipleModuleStatements(self): + """Tests an (invalid) .mojom with multiple module statements.""" + + source = """\ + module foo; + module bar; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Multiple \"module\" statements not " + r"allowed:\n *module bar;$"): + parser.Parse(source, "my_file.mojom") + + def testModuleStatementAfterImport(self): + """Tests an (invalid) .mojom with a module statement after an import.""" + + source = """\ + import "foo.mojom"; + module foo; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: \"module\" statements must precede imports " + r"and definitions:\n *module foo;$"): + parser.Parse(source, "my_file.mojom") + + def testModuleStatementAfterDefinition(self): + """Tests an (invalid) .mojom with a module statement after a definition.""" + + source = """\ + struct MyStruct { + int32 a; + }; + module foo; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: \"module\" statements must precede imports " + r"and definitions:\n *module foo;$"): + parser.Parse(source, "my_file.mojom") + + def testImportStatementAfterDefinition(self): + """Tests an (invalid) .mojom with an import statement after a definition.""" + + source = """\ + struct MyStruct { + int32 a; + }; + import "foo.mojom"; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: \"import\" statements must precede " + r"definitions:\n *import \"foo.mojom\";$"): + parser.Parse(source, "my_file.mojom") + + def testEnums(self): + """Tests that enum statements are correctly parsed.""" + + source = """\ + module my_module; + enum MyEnum1 { VALUE1, VALUE2 }; // No trailing comma. + enum MyEnum2 { + VALUE1 = -1, + VALUE2 = 0, + VALUE3 = + 987, // Check that space is allowed. + VALUE4 = 0xAF12, + VALUE5 = -0x09bcd, + VALUE6 = VALUE5, + VALUE7, // Leave trailing comma. + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Enum( + 'MyEnum1', + None, + ast.EnumValueList([ast.EnumValue('VALUE1', None, None), + ast.EnumValue('VALUE2', None, None)])), + ast.Enum( + 'MyEnum2', + None, + ast.EnumValueList([ast.EnumValue('VALUE1', None, '-1'), + ast.EnumValue('VALUE2', None, '0'), + ast.EnumValue('VALUE3', None, '+987'), + ast.EnumValue('VALUE4', None, '0xAF12'), + ast.EnumValue('VALUE5', None, '-0x09bcd'), + ast.EnumValue('VALUE6', None, ('IDENTIFIER', + 'VALUE5')), + ast.EnumValue('VALUE7', None, None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidEnumInitializers(self): + """Tests that invalid enum initializers are correctly detected.""" + + # No values. + source1 = """\ + enum MyEnum { + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '}':\n" + r" *};$"): + parser.Parse(source1, "my_file.mojom") + + # Floating point value. + source2 = "enum MyEnum { VALUE = 0.123 };" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected '0\.123':\n" + r"enum MyEnum { VALUE = 0\.123 };$"): + parser.Parse(source2, "my_file.mojom") + + # Boolean value. + source2 = "enum MyEnum { VALUE = true };" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected 'true':\n" + r"enum MyEnum { VALUE = true };$"): + parser.Parse(source2, "my_file.mojom") + + def testConsts(self): + """Tests some constants and struct members initialized with them.""" + + source = """\ + module my_module; + + struct MyStruct { + const int8 kNumber = -1; + int8 number@0 = kNumber; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Struct( + 'MyStruct', None, + ast.StructBody( + [ast.Const('kNumber', 'int8', '-1'), + ast.StructField('number', None, ast.Ordinal(0), 'int8', + ('IDENTIFIER', 'kNumber'))]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testNoConditionals(self): + """Tests that ?: is not allowed.""" + + source = """\ + module my_module; + + enum MyEnum { + MY_ENUM_1 = 1 ? 2 : 3 + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected '\?':\n" + r" *MY_ENUM_1 = 1 \? 2 : 3$"): + parser.Parse(source, "my_file.mojom") + + def testSimpleOrdinals(self): + """Tests that (valid) ordinal values are scanned correctly.""" + + source = """\ + module my_module; + + // This isn't actually valid .mojom, but the problem (missing ordinals) + // should be handled at a different level. + struct MyStruct { + int32 a0@0; + int32 a1@1; + int32 a2@2; + int32 a9@9; + int32 a10 @10; + int32 a11 @11; + int32 a29 @29; + int32 a1234567890 @1234567890; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a0', None, ast.Ordinal(0), 'int32', None), + ast.StructField('a1', None, ast.Ordinal(1), 'int32', None), + ast.StructField('a2', None, ast.Ordinal(2), 'int32', None), + ast.StructField('a9', None, ast.Ordinal(9), 'int32', None), + ast.StructField('a10', None, ast.Ordinal(10), 'int32', None), + ast.StructField('a11', None, ast.Ordinal(11), 'int32', None), + ast.StructField('a29', None, ast.Ordinal(29), 'int32', None), + ast.StructField('a1234567890', None, ast.Ordinal(1234567890), + 'int32', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidOrdinals(self): + """Tests that (lexically) invalid ordinals are correctly detected.""" + + source1 = """\ + module my_module; + + struct MyStruct { + int32 a_missing@; + }; + """ + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:4: Error: Missing ordinal value$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + module my_module; + + struct MyStruct { + int32 a_octal@01; + }; + """ + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:4: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + module my_module; struct MyStruct { int32 a_invalid_octal@08; }; + """ + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source3, "my_file.mojom") + + source4 = "module my_module; struct MyStruct { int32 a_hex@0x1aB9; };" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source4, "my_file.mojom") + + source5 = "module my_module; struct MyStruct { int32 a_hex@0X0; };" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source5, "my_file.mojom") + + source6 = """\ + struct MyStruct { + int32 a_too_big@999999999999; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: " + r"Ordinal value 999999999999 too large:\n" + r" *int32 a_too_big@999999999999;$"): + parser.Parse(source6, "my_file.mojom") + + def testNestedNamespace(self): + """Tests that "nested" namespaces work.""" + + source = """\ + module my.mod; + + struct MyStruct { + int32 a; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my.mod'), None), + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody(ast.StructField('a', None, None, 'int32', None)))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidHandleTypes(self): + """Tests (valid) handle types.""" + + source = """\ + struct MyStruct { + handle a; + handle<data_pipe_consumer> b; + handle <data_pipe_producer> c; + handle < message_pipe > d; + handle + < shared_buffer + > e; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None, 'handle', None), + ast.StructField('b', None, None, 'handle<data_pipe_consumer>', + None), + ast.StructField('c', None, None, 'handle<data_pipe_producer>', + None), + ast.StructField('d', None, None, 'handle<message_pipe>', None), + ast.StructField('e', None, None, 'handle<shared_buffer>', + None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidHandleType(self): + """Tests an invalid (unknown) handle type.""" + + source = """\ + struct MyStruct { + handle<wtf_is_this> foo; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: " + r"Invalid handle type 'wtf_is_this':\n" + r" *handle<wtf_is_this> foo;$"): + parser.Parse(source, "my_file.mojom") + + def testValidDefaultValues(self): + """Tests default values that are valid (to the parser).""" + + source = """\ + struct MyStruct { + int16 a0 = 0; + uint16 a1 = 0x0; + uint16 a2 = 0x00; + uint16 a3 = 0x01; + uint16 a4 = 0xcd; + int32 a5 = 12345; + int64 a6 = -12345; + int64 a7 = +12345; + uint32 a8 = 0x12cd3; + uint32 a9 = -0x12cD3; + uint32 a10 = +0x12CD3; + bool a11 = true; + bool a12 = false; + float a13 = 1.2345; + float a14 = -1.2345; + float a15 = +1.2345; + float a16 = 123.; + float a17 = .123; + double a18 = 1.23E10; + double a19 = 1.E-10; + double a20 = .5E+10; + double a21 = -1.23E10; + double a22 = +.123E10; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a0', None, None, 'int16', '0'), + ast.StructField('a1', None, None, 'uint16', '0x0'), + ast.StructField('a2', None, None, 'uint16', '0x00'), + ast.StructField('a3', None, None, 'uint16', '0x01'), + ast.StructField('a4', None, None, 'uint16', '0xcd'), + ast.StructField('a5' , None, None, 'int32', '12345'), + ast.StructField('a6', None, None, 'int64', '-12345'), + ast.StructField('a7', None, None, 'int64', '+12345'), + ast.StructField('a8', None, None, 'uint32', '0x12cd3'), + ast.StructField('a9', None, None, 'uint32', '-0x12cD3'), + ast.StructField('a10', None, None, 'uint32', '+0x12CD3'), + ast.StructField('a11', None, None, 'bool', 'true'), + ast.StructField('a12', None, None, 'bool', 'false'), + ast.StructField('a13', None, None, 'float', '1.2345'), + ast.StructField('a14', None, None, 'float', '-1.2345'), + ast.StructField('a15', None, None, 'float', '+1.2345'), + ast.StructField('a16', None, None, 'float', '123.'), + ast.StructField('a17', None, None, 'float', '.123'), + ast.StructField('a18', None, None, 'double', '1.23E10'), + ast.StructField('a19', None, None, 'double', '1.E-10'), + ast.StructField('a20', None, None, 'double', '.5E+10'), + ast.StructField('a21', None, None, 'double', '-1.23E10'), + ast.StructField('a22', None, None, 'double', '+.123E10')]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidFixedSizeArray(self): + """Tests parsing a fixed size array.""" + + source = """\ + struct MyStruct { + array<int32> normal_array; + array<int32, 1> fixed_size_array_one_entry; + array<int32, 10> fixed_size_array_ten_entries; + array<array<array<int32, 1>>, 2> nested_arrays; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('normal_array', None, None, 'int32[]', None), + ast.StructField('fixed_size_array_one_entry', None, None, + 'int32[1]', None), + ast.StructField('fixed_size_array_ten_entries', None, None, + 'int32[10]', None), + ast.StructField('nested_arrays', None, None, + 'int32[1][][2]', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidNestedArray(self): + """Tests parsing a nested array.""" + + source = "struct MyStruct { array<array<int32>> nested_array; };" + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + ast.StructField('nested_array', None, None, 'int32[][]', + None)))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidFixedArraySize(self): + """Tests that invalid fixed array bounds are correctly detected.""" + + source1 = """\ + struct MyStruct { + array<int32, 0> zero_size_array; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Fixed array size 0 invalid:\n" + r" *array<int32, 0> zero_size_array;$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + struct MyStruct { + array<int32, 999999999999> too_big_array; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Fixed array size 999999999999 invalid:\n" + r" *array<int32, 999999999999> too_big_array;$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + struct MyStruct { + array<int32, abcdefg> not_a_number; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'abcdefg':\n" + r" *array<int32, abcdefg> not_a_number;"): + parser.Parse(source3, "my_file.mojom") + + def testValidAssociativeArrays(self): + """Tests that we can parse valid associative array structures.""" + + source1 = "struct MyStruct { map<string, uint8> data; };" + expected1 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('data', None, None, 'uint8{string}', None)]))]) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + source2 = "interface MyInterface { MyMethod(map<string, uint8> a); };" + expected2 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList( + ast.Parameter('a', None, None, 'uint8{string}')), + None)))]) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + source3 = "struct MyStruct { map<string, array<uint8>> data; };" + expected3 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('data', None, None, 'uint8[]{string}', + None)]))]) + self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) + + def testValidMethod(self): + """Tests parsing method declarations.""" + + source1 = "interface MyInterface { MyMethod(int32 a); };" + expected1 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList(ast.Parameter('a', None, None, 'int32')), + None)))]) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + source2 = """\ + interface MyInterface { + MyMethod1@0(int32 a@0, int64 b@1); + MyMethod2@1() => (); + }; + """ + expected2 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + [ast.Method( + 'MyMethod1', + None, + ast.Ordinal(0), + ast.ParameterList([ast.Parameter('a', None, ast.Ordinal(0), + 'int32'), + ast.Parameter('b', None, ast.Ordinal(1), + 'int64')]), + None), + ast.Method( + 'MyMethod2', + None, + ast.Ordinal(1), + ast.ParameterList(), + ast.ParameterList())]))]) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + source3 = """\ + interface MyInterface { + MyMethod(string a) => (int32 a, bool b); + }; + """ + expected3 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList(ast.Parameter('a', None, None, 'string')), + ast.ParameterList([ast.Parameter('a', None, None, 'int32'), + ast.Parameter('b', None, None, + 'bool')]))))]) + self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) + + def testInvalidMethods(self): + """Tests that invalid method declarations are correctly detected.""" + + # No trailing commas. + source1 = """\ + interface MyInterface { + MyMethod(string a,); + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '\)':\n" + r" *MyMethod\(string a,\);$"): + parser.Parse(source1, "my_file.mojom") + + # No leading commas. + source2 = """\ + interface MyInterface { + MyMethod(, string a); + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected ',':\n" + r" *MyMethod\(, string a\);$"): + parser.Parse(source2, "my_file.mojom") + + def testValidInterfaceDefinitions(self): + """Tests all types of definitions that can occur in an interface.""" + + source = """\ + interface MyInterface { + enum MyEnum { VALUE }; + const int32 kMyConst = 123; + MyMethod(int32 x) => (MyEnum y); + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + [ast.Enum('MyEnum', + None, + ast.EnumValueList( + ast.EnumValue('VALUE', None, None))), + ast.Const('kMyConst', 'int32', '123'), + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList(ast.Parameter('x', None, None, 'int32')), + ast.ParameterList(ast.Parameter('y', None, None, + 'MyEnum')))]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidInterfaceDefinitions(self): + """Tests that definitions that aren't allowed in an interface are correctly + detected.""" + + source1 = """\ + interface MyInterface { + struct MyStruct { + int32 a; + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + interface MyInterface { + interface MyInnerInterface { + MyMethod(int32 x); + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'interface':\n" + r" *interface MyInnerInterface {$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + interface MyInterface { + int32 my_field; + }; + """ + # The parser thinks that "int32" is a plausible name for a method, so it's + # "my_field" that gives it away. + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'my_field':\n" + r" *int32 my_field;$"): + parser.Parse(source3, "my_file.mojom") + + def testValidAttributes(self): + """Tests parsing attributes (and attribute lists).""" + + # Note: We use structs because they have (optional) attribute lists. + + # Empty attribute list. + source1 = "[] struct MyStruct {};" + expected1 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct('MyStruct', ast.AttributeList(), ast.StructBody())]) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + # One-element attribute list, with name value. + source2 = "[MyAttribute=MyName] struct MyStruct {};" + expected2 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + ast.AttributeList(ast.Attribute("MyAttribute", "MyName")), + ast.StructBody())]) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + # Two-element attribute list, with one string value and one integer value. + source3 = "[MyAttribute1 = \"hello\", MyAttribute2 = 5] struct MyStruct {};" + expected3 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + ast.AttributeList([ast.Attribute("MyAttribute1", "hello"), + ast.Attribute("MyAttribute2", 5)]), + ast.StructBody())]) + self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) + + # Various places that attribute list is allowed. + source4 = """\ + [Attr0=0] module my_module; + + [Attr1=1] struct MyStruct { + [Attr2=2] int32 a; + }; + [Attr3=3] union MyUnion { + [Attr4=4] int32 a; + }; + [Attr5=5] enum MyEnum { + [Attr6=6] a + }; + [Attr7=7] interface MyInterface { + [Attr8=8] MyMethod([Attr9=9] int32 a) => ([Attr10=10] bool b); + }; + """ + expected4 = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), + ast.AttributeList([ast.Attribute("Attr0", 0)])), + ast.ImportList(), + [ast.Struct( + 'MyStruct', + ast.AttributeList(ast.Attribute("Attr1", 1)), + ast.StructBody( + ast.StructField( + 'a', ast.AttributeList([ast.Attribute("Attr2", 2)]), + None, 'int32', None))), + ast.Union( + 'MyUnion', + ast.AttributeList(ast.Attribute("Attr3", 3)), + ast.UnionBody( + ast.UnionField( + 'a', ast.AttributeList([ast.Attribute("Attr4", 4)]), None, + 'int32'))), + ast.Enum( + 'MyEnum', + ast.AttributeList(ast.Attribute("Attr5", 5)), + ast.EnumValueList( + ast.EnumValue( + 'VALUE', ast.AttributeList([ast.Attribute("Attr6", 6)]), + None))), + ast.Interface( + 'MyInterface', + ast.AttributeList(ast.Attribute("Attr7", 7)), + ast.InterfaceBody( + ast.Method( + 'MyMethod', + ast.AttributeList(ast.Attribute("Attr8", 8)), + None, + ast.ParameterList( + ast.Parameter( + 'a', ast.AttributeList([ast.Attribute("Attr9", 9)]), + None, 'int32')), + ast.ParameterList( + ast.Parameter( + 'b', + ast.AttributeList([ast.Attribute("Attr10", 10)]), + None, 'bool')))))]) + self.assertEquals(parser.Parse(source4, "my_file.mojom"), expected4) + + # TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()| + # literal (non-name) values, which is extremely dubious.) + + def testInvalidAttributes(self): + """Tests that invalid attributes and attribute lists are correctly + detected.""" + + # Trailing commas not allowed. + source1 = "[MyAttribute=MyName,] struct MyStruct {};" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected '\]':\n" + r"\[MyAttribute=MyName,\] struct MyStruct {};$"): + parser.Parse(source1, "my_file.mojom") + + # Missing value. + source2 = "[MyAttribute=] struct MyStruct {};" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected '\]':\n" + r"\[MyAttribute=\] struct MyStruct {};$"): + parser.Parse(source2, "my_file.mojom") + + # Missing key. + source3 = "[=MyName] struct MyStruct {};" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected '=':\n" + r"\[=MyName\] struct MyStruct {};$"): + parser.Parse(source3, "my_file.mojom") + + def testValidImports(self): + """Tests parsing import statements.""" + + # One import (no module statement). + source1 = "import \"somedir/my.mojom\";" + expected1 = ast.Mojom( + None, + ast.ImportList(ast.Import("somedir/my.mojom")), + []) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + # Two imports (no module statement). + source2 = """\ + import "somedir/my1.mojom"; + import "somedir/my2.mojom"; + """ + expected2 = ast.Mojom( + None, + ast.ImportList([ast.Import("somedir/my1.mojom"), + ast.Import("somedir/my2.mojom")]), + []) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + # Imports with module statement. + source3 = """\ + module my_module; + import "somedir/my1.mojom"; + import "somedir/my2.mojom"; + """ + expected3 = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList([ast.Import("somedir/my1.mojom"), + ast.Import("somedir/my2.mojom")]), + []) + self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) + + def testInvalidImports(self): + """Tests that invalid import statements are correctly detected.""" + + source1 = """\ + // Make the error occur on line 2. + import invalid + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'invalid':\n" + r" *import invalid$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + import // Missing string. + struct MyStruct { + int32 a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + import "foo.mojom" // Missing semicolon. + struct MyStruct { + int32 a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source3, "my_file.mojom") + + def testValidNullableTypes(self): + """Tests parsing nullable types.""" + + source = """\ + struct MyStruct { + int32? a; // This is actually invalid, but handled at a different + // level. + string? b; + array<int32> ? c; + array<string ? > ? d; + array<array<int32>?>? e; + array<int32, 1>? f; + array<string?, 1>? g; + some_struct? h; + handle? i; + handle<data_pipe_consumer>? j; + handle<data_pipe_producer>? k; + handle<message_pipe>? l; + handle<shared_buffer>? m; + some_interface&? n; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None,'int32?', None), + ast.StructField('b', None, None,'string?', None), + ast.StructField('c', None, None,'int32[]?', None), + ast.StructField('d', None, None,'string?[]?', None), + ast.StructField('e', None, None,'int32[]?[]?', None), + ast.StructField('f', None, None,'int32[1]?', None), + ast.StructField('g', None, None,'string?[1]?', None), + ast.StructField('h', None, None,'some_struct?', None), + ast.StructField('i', None, None,'handle?', None), + ast.StructField('j', None, None,'handle<data_pipe_consumer>?', + None), + ast.StructField('k', None, None,'handle<data_pipe_producer>?', + None), + ast.StructField('l', None, None,'handle<message_pipe>?', None), + ast.StructField('m', None, None,'handle<shared_buffer>?', + None), + ast.StructField('n', None, None,'some_interface&?', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidNullableTypes(self): + """Tests that invalid nullable types are correctly detected.""" + source1 = """\ + struct MyStruct { + string?? a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '\?':\n" + r" *string\?\? a;$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + struct MyStruct { + handle?<data_pipe_consumer> a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '<':\n" + r" *handle\?<data_pipe_consumer> a;$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + struct MyStruct { + some_interface?& a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '&':\n" + r" *some_interface\?& a;$"): + parser.Parse(source3, "my_file.mojom") + + def testSimpleUnion(self): + """Tests a simple .mojom source that just defines a union.""" + source = """\ + module my_module; + + union MyUnion { + int32 a; + double b; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('a', None, None, 'int32'), + ast.UnionField('b', None, None, 'double') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionWithOrdinals(self): + """Test that ordinals are assigned to fields.""" + source = """\ + module my_module; + + union MyUnion { + int32 a @10; + double b @30; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('a', None, ast.Ordinal(10), 'int32'), + ast.UnionField('b', None, ast.Ordinal(30), 'double') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionWithStructMembers(self): + """Test that struct members are accepted.""" + source = """\ + module my_module; + + union MyUnion { + SomeStruct s; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('s', None, None, 'SomeStruct') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionWithArrayMember(self): + """Test that array members are accepted.""" + source = """\ + module my_module; + + union MyUnion { + array<int32> a; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('a', None, None, 'int32[]') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionWithMapMember(self): + """Test that map members are accepted.""" + source = """\ + module my_module; + + union MyUnion { + map<int32, string> m; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('m', None, None, 'string{int32}') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionDisallowNestedStruct(self): + """Tests that structs cannot be nested in unions.""" + source = """\ + module my_module; + + union MyUnion { + struct MyStruct { + int32 a; + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source, "my_file.mojom") + + def testUnionDisallowNestedInterfaces(self): + """Tests that interfaces cannot be nested in unions.""" + source = """\ + module my_module; + + union MyUnion { + interface MyInterface { + MyMethod(int32 a); + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'interface':\n" + r" *interface MyInterface {$"): + parser.Parse(source, "my_file.mojom") + + def testUnionDisallowNestedUnion(self): + """Tests that unions cannot be nested in unions.""" + source = """\ + module my_module; + + union MyUnion { + union MyOtherUnion { + int32 a; + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'union':\n" + r" *union MyOtherUnion {$"): + parser.Parse(source, "my_file.mojom") + + def testUnionDisallowNestedEnum(self): + """Tests that enums cannot be nested in unions.""" + source = """\ + module my_module; + + union MyUnion { + enum MyEnum { + A, + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'enum':\n" + r" *enum MyEnum {$"): + parser.Parse(source, "my_file.mojom") + + def testValidAssociatedKinds(self): + """Tests parsing associated interfaces and requests.""" + source1 = """\ + struct MyStruct { + associated MyInterface a; + associated MyInterface& b; + associated MyInterface? c; + associated MyInterface&? d; + }; + """ + expected1 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None,'asso<MyInterface>', None), + ast.StructField('b', None, None,'asso<MyInterface&>', None), + ast.StructField('c', None, None,'asso<MyInterface>?', None), + ast.StructField('d', None, None,'asso<MyInterface&>?', + None)]))]) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + source2 = """\ + interface MyInterface { + MyMethod(associated A a) =>(associated B& b); + };""" + expected2 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList( + ast.Parameter('a', None, None, 'asso<A>')), + ast.ParameterList( + ast.Parameter('b', None, None, 'asso<B&>')))))]) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + def testInvalidAssociatedKinds(self): + """Tests that invalid associated interfaces and requests are correctly + detected.""" + source1 = """\ + struct MyStruct { + associated associated SomeInterface a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'associated':\n" + r" *associated associated SomeInterface a;$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + struct MyStruct { + associated handle a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'handle':\n" + r" *associated handle a;$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + struct MyStruct { + associated? MyInterface& a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '\?':\n" + r" *associated\? MyInterface& a;$"): + parser.Parse(source3, "my_file.mojom") + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py new file mode 100755 index 0000000000..b160de6690 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Simple testing utility to just run the mojom parser.""" + + +import os.path +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.path.pardir, os.path.pardir)) + +from mojom.parse.parser import Parse, ParseError + + +def main(argv): + if len(argv) < 2: + print "usage: %s filename" % argv[0] + return 0 + + for filename in argv[1:]: + with open(filename) as f: + print "%s:" % filename + try: + print Parse(f.read(), filename) + except ParseError, e: + print e + return 1 + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py new file mode 100755 index 0000000000..899d40e584 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Simple testing utility to just run the mojom translate stage.""" + + +import os.path +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.path.pardir, os.path.pardir)) + +from mojom.parse.parser import Parse +from mojom.parse.translate import Translate + + +def main(argv): + if len(argv) < 2: + print "usage: %s filename" % sys.argv[0] + return 1 + + for filename in argv[1:]: + with open(filename) as f: + print "%s:" % filename + print Translate(Parse(f.read(), filename), + os.path.splitext(os.path.basename(filename))[0]) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py new file mode 100644 index 0000000000..25203329f4 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py @@ -0,0 +1,80 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.parse import ast +from mojom.parse import translate + + +class TranslateTest(unittest.TestCase): + """Tests |parser.Parse()|.""" + + def testSimpleArray(self): + """Tests a simple int32[].""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("int32[]"), "a:i32") + + def testAssociativeArray(self): + """Tests a simple uint8{string}.""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("uint8{string}"), "m[s][u8]") + + def testLeftToRightAssociativeArray(self): + """Makes sure that parsing is done from right to left on the internal kinds + in the presence of an associative array.""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("uint8[]{string}"), "m[s][a:u8]") + + def testTranslateSimpleUnions(self): + """Makes sure that a simple union is translated correctly.""" + tree = ast.Mojom( + None, + ast.ImportList(), + [ast.Union("SomeUnion", None, ast.UnionBody( + [ast.UnionField("a", None, None, "int32"), + ast.UnionField("b", None, None, "string")]))]) + expected = [{ + "name": "SomeUnion", + "fields": [{"kind": "i32", "name": "a"}, + {"kind": "s", "name": "b"}]}] + actual = translate.Translate(tree, "mojom_tree") + self.assertEquals(actual["unions"], expected) + + def testMapTreeForTypeRaisesWithDuplicate(self): + """Verifies _MapTreeForType() raises when passed two values with the same + name.""" + methods = [ast.Method('dup', None, None, ast.ParameterList(), None), + ast.Method('dup', None, None, ast.ParameterList(), None)] + self.assertRaises(Exception, translate._MapTreeForType, + (lambda x: x, methods, '', 'scope')) + + def testAssociatedKinds(self): + """Tests type spec translation of associated interfaces and requests.""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("asso<SomeInterface>?"), + "?asso:x:SomeInterface") + self.assertEquals(translate._MapKind("asso<SomeInterface&>?"), + "?asso:r:x:SomeInterface") + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py new file mode 100644 index 0000000000..2a4b17b29b --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py @@ -0,0 +1,32 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import fnmatch +from os import walk +from os.path import join +import sys + + +def FindFiles(top, pattern, **kwargs): + """Finds files under |top| matching the glob pattern |pattern|, returning a + list of paths.""" + matches = [] + for dirpath, _, filenames in walk(top, **kwargs): + for filename in fnmatch.filter(filenames, pattern): + matches.append(join(dirpath, filename)) + return matches + + +def main(argv): + if len(argv) != 3: + print "usage: %s path pattern" % argv[0] + return 1 + + for filename in FindFiles(argv[1], argv[2]): + print filename + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py new file mode 100644 index 0000000000..20ef461969 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py @@ -0,0 +1,47 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os.path +from subprocess import check_call +import sys + + +def RunBindingsGenerator(out_dir, root_dir, mojom_file, extra_flags=None): + out_dir = os.path.abspath(out_dir) + root_dir = os.path.abspath(root_dir) + mojom_file = os.path.abspath(mojom_file) + + # The mojom file should be under the root directory somewhere. + assert mojom_file.startswith(root_dir) + mojom_reldir = os.path.dirname(os.path.relpath(mojom_file, root_dir)) + + # TODO(vtl): Abstract out the "main" functions, so that we can just import + # the bindings generator (which would be more portable and easier to use in + # tests). + this_dir = os.path.dirname(os.path.abspath(__file__)) + # We're in src/mojo/public/tools/bindings/pylib/mojom_tests/support; + # mojom_bindings_generator.py is in .../bindings. + bindings_generator = os.path.join(this_dir, os.pardir, os.pardir, os.pardir, + "mojom_bindings_generator.py") + + args = ["python", bindings_generator, + "-o", os.path.join(out_dir, mojom_reldir)] + if extra_flags: + args.extend(extra_flags) + args.append(mojom_file) + + check_call(args) + + +def main(argv): + if len(argv) < 4: + print "usage: %s out_dir root_dir mojom_file [extra_flags]" % argv[0] + return 1 + + RunBindingsGenerator(argv[1], argv[2], argv[3], extra_flags=argv[4:]) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) |