diff options
Diffstat (limited to 'utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py')
-rw-r--r-- | utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py new file mode 100644 index 00000000..de62260a --- /dev/null +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py @@ -0,0 +1,325 @@ +# 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 __future__ import print_function + +from functools import partial +import os.path +import re + +from mojom import fileutil +from mojom.generate import module as mojom +from mojom.generate import pack + + +def ExpectedArraySize(kind): + if mojom.IsArrayKind(kind): + return kind.length + return None + + +def SplitCamelCase(identifier): + """Splits a camel-cased |identifier| and returns a list of lower-cased + strings. + """ + # Add underscores after uppercase letters when appropriate. An uppercase + # letter is considered the end of a word if it is followed by an upper and a + # lower. E.g. URLLoaderFactory -> URL_LoaderFactory + identifier = re.sub('([A-Z][0-9]*)(?=[A-Z][0-9]*[a-z])', r'\1_', identifier) + # Add underscores after lowercase letters when appropriate. A lowercase letter + # is considered the end of a word if it is followed by an upper. + # E.g. URLLoaderFactory -> URLLoader_Factory + identifier = re.sub('([a-z][0-9]*)(?=[A-Z])', r'\1_', identifier) + return [x.lower() for x in identifier.split('_')] + + +def ToCamel(identifier, lower_initial=False, digits_split=False, delimiter='_'): + """Splits |identifier| using |delimiter|, makes the first character of each + word uppercased (but makes the first character of the first word lowercased + if |lower_initial| is set to True), and joins the words. Please note that for + each word, all the characters except the first one are untouched. + """ + result = '' + capitalize_next = True + for i in range(len(identifier)): + if identifier[i] == delimiter: + capitalize_next = True + elif digits_split and identifier[i].isdigit(): + capitalize_next = True + result += identifier[i] + elif capitalize_next: + capitalize_next = False + result += identifier[i].upper() + else: + result += identifier[i] + + if lower_initial and result: + result = result[0].lower() + result[1:] + + return result + + +def _ToSnakeCase(identifier, upper=False): + """Splits camel-cased |identifier| into lower case words, removes the first + word if it's "k" and joins them using "_" e.g. for "URLLoaderFactory", returns + "URL_LOADER_FACTORY" if upper, otherwise "url_loader_factory". + """ + words = SplitCamelCase(identifier) + if words[0] == 'k' and len(words) > 1: + words = words[1:] + + # Variables cannot start with a digit + if (words[0][0].isdigit()): + words[0] = '_' + words[0] + + + if upper: + words = map(lambda x: x.upper(), words) + + return '_'.join(words) + + +def ToUpperSnakeCase(identifier): + """Splits camel-cased |identifier| into lower case words, removes the first + word if it's "k" and joins them using "_" e.g. for "URLLoaderFactory", returns + "URL_LOADER_FACTORY". + """ + return _ToSnakeCase(identifier, upper=True) + + +def ToLowerSnakeCase(identifier): + """Splits camel-cased |identifier| into lower case words, removes the first + word if it's "k" and joins them using "_" e.g. for "URLLoaderFactory", returns + "url_loader_factory". + """ + return _ToSnakeCase(identifier, upper=False) + + +class Stylizer(object): + """Stylizers specify naming rules to map mojom names to names in generated + code. For example, if you would like method_name in mojom to be mapped to + MethodName in the generated code, you need to define a subclass of Stylizer + and override StylizeMethod to do the conversion.""" + + def StylizeConstant(self, mojom_name): + return mojom_name + + def StylizeField(self, mojom_name): + return mojom_name + + def StylizeStruct(self, mojom_name): + return mojom_name + + def StylizeUnion(self, mojom_name): + return mojom_name + + def StylizeParameter(self, mojom_name): + return mojom_name + + def StylizeMethod(self, mojom_name): + return mojom_name + + def StylizeInterface(self, mojom_name): + return mojom_name + + def StylizeEnumField(self, mojom_name): + return mojom_name + + def StylizeEnum(self, mojom_name): + return mojom_name + + def StylizeModule(self, mojom_namespace): + return mojom_namespace + + +def WriteFile(contents, full_path): + # If |contents| is same with the file content, we skip updating. + if os.path.isfile(full_path): + with open(full_path, 'rb') as destination_file: + if destination_file.read() == contents: + return + + # 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, "wb") as f: + if not isinstance(contents, bytes): + f.write(contents.encode('utf-8')) + else: + f.write(contents) + + +def AddComputedData(module): + """Adds computed data to the given module. The data is computed once and + used repeatedly in the generation process.""" + + def _AddStructComputedData(exported, struct): + struct.packed = pack.PackedStruct(struct) + struct.bytes = pack.GetByteLayout(struct.packed) + struct.versions = pack.GetVersionInfo(struct.packed) + struct.exported = exported + + def _AddInterfaceComputedData(interface): + interface.version = 0 + for method in interface.methods: + # this field is never scrambled + method.sequential_ordinal = method.ordinal + + if method.min_version is not None: + interface.version = max(interface.version, method.min_version) + + method.param_struct = _GetStructFromMethod(method) + if interface.stable: + method.param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True + if method.explicit_ordinal is None: + raise Exception( + 'Stable interfaces must declare explicit method ordinals. The ' + 'method %s on stable interface %s does not declare an explicit ' + 'ordinal.' % (method.mojom_name, interface.qualified_name)) + interface.version = max(interface.version, + method.param_struct.versions[-1].version) + + if method.response_parameters is not None: + method.response_param_struct = _GetResponseStructFromMethod(method) + if interface.stable: + method.response_param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True + interface.version = max( + interface.version, + method.response_param_struct.versions[-1].version) + else: + method.response_param_struct = None + + def _GetStructFromMethod(method): + """Converts a method's parameters into the fields of a struct.""" + params_class = "%s_%s_Params" % (method.interface.mojom_name, + method.mojom_name) + struct = mojom.Struct(params_class, + module=method.interface.module, + attributes={}) + for param in method.parameters: + struct.AddField( + param.mojom_name, + param.kind, + param.ordinal, + attributes=param.attributes) + _AddStructComputedData(False, struct) + return struct + + def _GetResponseStructFromMethod(method): + """Converts a method's response_parameters into the fields of a struct.""" + params_class = "%s_%s_ResponseParams" % (method.interface.mojom_name, + method.mojom_name) + struct = mojom.Struct(params_class, + module=method.interface.module, + attributes={}) + for param in method.response_parameters: + struct.AddField( + param.mojom_name, + param.kind, + param.ordinal, + attributes=param.attributes) + _AddStructComputedData(False, struct) + return struct + + for struct in module.structs: + _AddStructComputedData(True, struct) + for interface in module.interfaces: + _AddInterfaceComputedData(interface) + + +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, + js_bindings_mode="new", + js_generate_struct_deserializers=False, + export_attribute=None, + export_header=None, + generate_non_variant_code=False, + support_lazy_serialization=False, + disallow_native_types=False, + disallow_interfaces=False, + generate_message_ids=False, + generate_fuzzing=False, + enable_kythe_annotations=False, + extra_cpp_template_paths=None, + generate_extra_cpp_only=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.js_bindings_mode = js_bindings_mode + self.js_generate_struct_deserializers = js_generate_struct_deserializers + self.export_attribute = export_attribute + self.export_header = export_header + self.generate_non_variant_code = generate_non_variant_code + self.support_lazy_serialization = support_lazy_serialization + self.disallow_native_types = disallow_native_types + self.disallow_interfaces = disallow_interfaces + self.generate_message_ids = generate_message_ids + self.generate_fuzzing = generate_fuzzing + self.enable_kythe_annotations = enable_kythe_annotations + self.extra_cpp_template_paths = extra_cpp_template_paths + self.generate_extra_cpp_only = generate_extra_cpp_only + + 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 OptimizeEmpty(self, contents): + # Look for .cc files that contain no actual code. There are many of these + # and they collectively take a while to compile. + lines = contents.splitlines() + + for line in lines: + if line.startswith('#') or line.startswith('//'): + continue + if re.match(r'namespace .* {', line) or re.match(r'}.*//.*namespace', + line): + continue + if line.strip(): + # There is some actual code - return the unmodified contents. + return contents + + # If we reach here then we have a .cc file with no actual code. The + # includes are therefore unneeded and can be removed. + new_lines = [line for line in lines if not line.startswith('#include')] + if len(new_lines) < len(lines): + new_lines.append('') + new_lines.append('// Includes removed due to no code being generated.') + return '\n'.join(new_lines) + + def WriteWithComment(self, contents, filename): + generator_name = "mojom_bindings_generator.py" + comment = r"// %s is auto generated by %s, do not edit" % (filename, + generator_name) + contents = comment + '\n' + '\n' + contents; + if filename.endswith('.cc'): + contents = self.OptimizeEmpty(contents) + self.Write(contents, filename) + + 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 {} |