blob: c4dc860de9dcc62f7fc571b36454643447c7fb9e [file] [log] [blame]
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generates C++ source files from a mojom.Module."""
import datetime
import mojom
import mojom_pack
import os
import sys
from string import Template
# mojom_cpp_generator provides a way to generate c++ code from a mojom.Module.
# cpp = mojom_cpp_generator.CPPGenerator(module)
# cpp.GenerateFiles("/tmp/g")
class DependentKinds(set):
"""Set subclass to find the unique set of non POD types."""
def AddKind(self, kind):
if isinstance(kind, mojom.Struct):
self.add(kind)
if isinstance(kind, mojom.Array):
self.AddKind(kind.kind)
class Forwards(object):
"""Helper class to maintain unique set of forward declarations."""
def __init__(self):
self.kinds = DependentKinds()
def Add(self, kind):
self.kinds.AddKind(kind)
def __repr__(self):
return '\n'.join(
sorted(map(
lambda kind: "class %s;" % kind.name.capitalize(), self.kinds)))
class Lines(object):
"""Helper class to maintain list of template expanded lines."""
def __init__(self, template):
self.template = template
self.lines = []
def Add(self, map = {}, **substitutions):
if len(substitutions) > 0:
map = map.copy()
map.update(substitutions)
self.lines.append(self.template.substitute(map))
def __repr__(self):
return '\n'.join(self.lines)
def GetStructFromMethod(interface, method):
"""Converts a method's parameters into the fields of a struct."""
params_class = \
"%s_%s_Params" % (interface.name.capitalize(), method.name.capitalize())
struct = mojom.Struct(params_class)
for param in method.parameters:
struct.AddField(param.name, param.kind, param.ordinal)
return struct
def IsPointerKind(kind):
return isinstance(kind, (mojom.Struct, mojom.Array)) or kind.spec == 's'
class CPPGenerator(object):
struct_serialization_compute_template = \
Template(" +\n mojo::internal::ComputeSizeOf($NAME->$FIELD())")
struct_serialization_clone_template = Template(
" clone->set_$FIELD(mojo::internal::Clone($NAME->$FIELD(), buf));")
struct_serialization_encode_template = Template(
" Encode(&$NAME->${FIELD}_, handles);")
struct_serialization_encode_handle_template = Template(
" EncodeHandle(&$NAME->${FIELD}_, handles);")
struct_serialization_decode_template = Template(
" if (!Decode(&$NAME->${FIELD}_, message))\n"
" return false;")
struct_serialization_decode_handle_template = Template(
" if (!DecodeHandle(&$NAME->${FIELD}_, message.handles))\n"
" return false;")
param_set_template = Template(" params->set_$NAME($NAME);")
param_struct_set_template = Template(
" params->set_$NAME(mojo::internal::Clone($NAME, builder.buffer()));")
param_struct_compute_template = Template(
" payload_size += mojo::internal::ComputeSizeOf($NAME);")
field_template = Template(" $TYPE ${FIELD}_;")
bool_field_template = Template(" uint8_t ${FIELD}_ : 1;")
setter_template = \
Template(" void set_$FIELD($TYPE $FIELD) { ${FIELD}_ = $FIELD; }")
ptr_setter_template = \
Template(" void set_$FIELD($TYPE $FIELD) { ${FIELD}_.ptr = $FIELD; }")
getter_template = \
Template(" $TYPE $FIELD() const { return ${FIELD}_; }")
ptr_getter_template = \
Template(" $TYPE $FIELD() const { return ${FIELD}_.ptr; }")
pad_template = Template(" uint8_t _pad${COUNT}_[$PAD];")
name_template = \
Template("const uint32_t k${INTERFACE}_${METHOD}_Name = $NAME;")
templates = {}
HEADER_SIZE = 8
kind_to_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.HANDLE: "mojo::Handle",
mojom.INT64: "int64_t",
mojom.UINT64: "uint64_t",
mojom.DOUBLE: "double",
}
@classmethod
def GetTemplate(cls, template_name):
if template_name not in cls.templates:
dir = os.path.dirname(__file__)
filename = os.path.join(dir, 'cpp_templates', template_name)
filename = filename.replace('.h', '.h-template')
filename = filename.replace('.cc', '.cc-template')
with open(filename, 'r') as file:
template = Template(file.read())
cls.templates[template_name] = template
return cls.templates[template_name]
@classmethod
def GetType(cls, kind):
if isinstance(kind, mojom.Struct):
return "%s*" % kind.name.capitalize()
if isinstance(kind, mojom.Array):
return "mojo::Array<%s>*" % cls.GetType(kind.kind)
if kind.spec == 's':
return "mojo::String*"
return cls.kind_to_type[kind]
@classmethod
def GetConstType(cls, kind):
if isinstance(kind, mojom.Struct):
return "const %s*" % kind.name.capitalize()
if isinstance(kind, mojom.Array):
return "const mojo::Array<%s>*" % cls.GetConstType(kind.kind)
if kind.spec == 's':
return "const mojo::String*"
return cls.kind_to_type[kind]
@classmethod
def GetGetterLine(cls, field):
subs = {'FIELD': field.name, 'TYPE': cls.GetType(field.kind)}
if IsPointerKind(field.kind):
return cls.ptr_getter_template.substitute(subs)
else:
return cls.getter_template.substitute(subs)
@classmethod
def GetSetterLine(cls, field):
subs = {'FIELD': field.name, 'TYPE': cls.GetType(field.kind)}
if IsPointerKind(field.kind):
return cls.ptr_setter_template.substitute(subs)
else:
return cls.setter_template.substitute(subs)
@classmethod
def GetFieldLine(cls, field):
kind = field.kind
if kind.spec == 'b':
return cls.bool_field_template.substitute(FIELD=field.name)
itype = None
if isinstance(kind, mojom.Struct):
itype = "mojo::internal::StructPointer<%s>" % kind.name.capitalize()
elif isinstance(kind, mojom.Array):
itype = "mojo::internal::ArrayPointer<%s>" % cls.GetType(kind.kind)
elif kind.spec == 's':
itype = "mojo::internal::StringPointer"
else:
itype = cls.kind_to_type[kind]
return cls.field_template.substitute(FIELD=field.name, TYPE=itype)
@classmethod
def GetCaseLine(cls, interface, method):
params = map(lambda param: "params->%s()" % param.name, method.parameters)
method_call = "%s(%s);" % (method.name, ", ".join(params))
return cls.GetTemplate("interface_stub_case").substitute(
CLASS = interface.name,
METHOD = method.name,
METHOD_CALL = method_call);
@classmethod
def GetSerializedFields(cls, ps):
fields = []
for pf in ps.packed_fields:
if IsPointerKind(pf.field.kind):
fields.append(pf.field)
return fields
@classmethod
def GetHandleFields(cls, ps):
fields = []
for pf in ps.packed_fields:
if pf.field.kind.spec == 'h':
fields.append(pf.field)
return fields
def GetHeaderGuard(self, name):
return "MOJO_GENERATED_BINDINGS_%s_%s_H_" % \
(self.module.name.upper(), name.upper())
def GetHeaderFile(self, *components):
component_string = '_'.join(components)
return os.path.join(
self.header_dir,
"%s_%s.h" % (self.module.name.lower(), component_string.lower()))
# Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all files
# to stdout.
def __init__(self, module, header_dir, output_dir=None):
self.module = module
self.header_dir = header_dir
self.output_dir = output_dir
def WriteTemplateToFile(self, template_name, name, **substitutions):
template = self.GetTemplate(template_name)
filename = "%s_%s" % (self.module.name.lower(), template_name)
filename = filename.replace("interface", name.lower())
filename = filename.replace("struct", name.lower())
substitutions['YEAR'] = datetime.date.today().year
substitutions['NAMESPACE'] = self.module.namespace
if self.output_dir is None:
file = sys.stdout
else:
file = open(os.path.join(self.output_dir, filename), "w+")
try:
file.write(template.substitute(substitutions))
finally:
if self.output_dir is not None:
file.close()
def GetStructDeclaration(self, name, ps):
params_template = self.GetTemplate("struct_declaration")
fields = []
setters = []
getters = []
pad_count = 0
num_fields = len(ps.packed_fields)
for i in xrange(num_fields):
pf = ps.packed_fields[i]
field = pf.field
fields.append(self.GetFieldLine(field))
if i < (num_fields - 1):
next_pf = ps.packed_fields[i+1]
pad = next_pf.offset - (pf.offset + pf.size)
if pad > 0:
fields.append(self.pad_template.substitute(COUNT=pad_count, PAD=pad))
pad_count += 1
setters.append(self.GetSetterLine(field))
getters.append(self.GetGetterLine(field))
if num_fields > 0:
last_field = ps.packed_fields[num_fields - 1]
offset = last_field.offset + last_field.size
pad = mojom_pack.GetPad(offset, 8)
if pad > 0:
fields.append(self.pad_template.substitute(COUNT=pad_count, PAD=pad))
pad_count += 1
size = offset + pad
else:
size = 0
return params_template.substitute(
CLASS = name,
SETTERS = '\n'.join(setters),
GETTERS = '\n'.join(getters),
FIELDS = '\n'.join(fields),
SIZE = size + self.HEADER_SIZE)
def GetStructImplementation(self, name, ps):
return self.GetTemplate("struct_implementation").substitute(
CLASS = name,
NUM_FIELDS = len(ps.packed_fields))
def GetStructSerialization(self, class_name, param_name, ps):
struct = ps.struct
encodes = Lines(self.struct_serialization_encode_template)
encode_handles = Lines(self.struct_serialization_encode_handle_template)
decodes = Lines(self.struct_serialization_decode_template)
decode_handles = Lines(self.struct_serialization_decode_handle_template)
fields = self.GetSerializedFields(ps)
handle_fields = self.GetHandleFields(ps)
for field in fields:
substitutions = {'NAME': param_name, 'FIELD': field.name.lower()}
encodes.Add(substitutions)
decodes.Add(substitutions)
for field in handle_fields:
substitutions = {'NAME': param_name, 'FIELD': field.name.lower()}
encode_handles.Add(substitutions)
decode_handles.Add(substitutions)
return self.GetTemplate("struct_serialization").substitute(
CLASS = "%s::%s" % (self.module.namespace.lower(), class_name),
NAME = param_name,
ENCODES = encodes,
DECODES = decodes,
ENCODE_HANDLES = encode_handles,
DECODE_HANDLES = decode_handles)
def GenerateStructHeader(self, ps):
struct = ps.struct
forwards = Forwards()
for field in struct.fields:
forwards.Add(field.kind)
self.WriteTemplateToFile("struct.h", struct.name,
HEADER_GUARD = self.GetHeaderGuard(struct.name),
CLASS = struct.name.capitalize(),
FORWARDS = forwards,
DECLARATION = self.GetStructDeclaration(struct.name.capitalize(), ps))
def GenerateStructSource(self, ps):
struct = ps.struct
header = self.GetHeaderFile(struct.name)
implementation = self.GetStructImplementation(struct.name.capitalize(), ps)
self.WriteTemplateToFile("struct.cc", struct.name,
CLASS = struct.name.capitalize(),
NUM_FIELDS = len(struct.fields),
HEADER = header,
IMPLEMENTATION = implementation)
def GenerateStructSerializationHeader(self, ps):
struct = ps.struct
self.WriteTemplateToFile("struct_serialization.h", struct.name,
HEADER_GUARD = self.GetHeaderGuard(struct.name + "_SERIALIZATION"),
CLASS = struct.name.capitalize(),
FULL_CLASS = "%s::%s" % \
(self.module.namespace, struct.name.capitalize()))
def GenerateStructSerializationSource(self, ps):
struct = ps.struct
serialization_header = self.GetHeaderFile(struct.name, "serialization")
kinds = DependentKinds()
for field in struct.fields:
kinds.AddKind(field.kind)
headers = \
map(lambda kind: self.GetHeaderFile(kind.name, "serialization"), kinds)
headers.append(self.GetHeaderFile(struct.name))
includes = map(lambda header: "#include \"%s\"" % header, sorted(headers))
class_header = self.GetHeaderFile(struct.name)
clones = Lines(self.struct_serialization_clone_template)
sizes = " return sizeof(*%s)" % struct.name.lower()
fields = self.GetSerializedFields(ps)
for field in fields:
substitutions = {'NAME': struct.name.lower(), 'FIELD': field.name.lower()}
sizes += \
self.struct_serialization_compute_template.substitute(substitutions)
clones.Add(substitutions)
sizes += ";"
serialization = \
self.GetStructSerialization(struct.name.capitalize(), struct.name, ps)
self.WriteTemplateToFile("struct_serialization.cc", struct.name,
NAME = struct.name.lower(),
CLASS = "%s::%s" % \
(self.module.namespace.lower(), struct.name.capitalize()),
SERIALIZATION_HEADER = serialization_header,
INCLUDES = '\n'.join(includes),
SIZES = sizes,
CLONES = clones,
SERIALIZATION = serialization)
def GenerateInterfaceHeader(self, interface):
methods = []
forwards = Forwards()
for method in interface.methods:
params = []
for param in method.parameters:
forwards.Add(param.kind)
params.append("%s %s" % (self.GetConstType(param.kind), param.name))
methods.append(
" virtual void %s(%s) = 0;" % (method.name, ", ".join(params)))
self.WriteTemplateToFile("interface.h", interface.name,
HEADER_GUARD = self.GetHeaderGuard(interface.name),
CLASS = interface.name.capitalize(),
FORWARDS = forwards,
METHODS = '\n'.join(methods))
def GenerateInterfaceStubHeader(self, interface):
header = self.GetHeaderFile(interface.name)
self.WriteTemplateToFile("interface_stub.h", interface.name,
HEADER_GUARD = self.GetHeaderGuard(interface.name + "_STUB"),
CLASS = interface.name.capitalize(),
HEADER = header)
def GenerateInterfaceStubSource(self, interface):
stub_header = self.GetHeaderFile(interface.name, "stub")
serialization_header = self.GetHeaderFile(interface.name, "serialization")
cases = []
for method in interface.methods:
cases.append(self.GetCaseLine(interface, method))
self.WriteTemplateToFile("interface_stub.cc", interface.name,
CLASS = interface.name.capitalize(),
CASES = '\n'.join(cases),
STUB_HEADER = stub_header,
SERIALIZATION_HEADER = serialization_header)
def GenerateInterfaceSerializationHeader(self, interface):
kinds = DependentKinds()
for method in interface.methods:
for param in method.parameters:
kinds.AddKind(param.kind)
headers = \
map(lambda kind: self.GetHeaderFile(kind.name, "serialization"), kinds)
headers.append(self.GetHeaderFile(interface.name))
headers.append("mojo/public/bindings/lib/bindings_serialization.h")
includes = map(lambda header: "#include \"%s\"" % header, sorted(headers))
names = []
name = 1
param_classes = []
param_templates = []
template_declaration = self.GetTemplate("template_declaration")
for method in interface.methods:
names.append(self.name_template.substitute(
INTERFACE = interface.name.capitalize(),
METHOD = method.name.capitalize(),
NAME = name))
name += 1
struct = GetStructFromMethod(interface, method)
ps = mojom_pack.PackedStruct(struct)
param_classes.append(self.GetStructDeclaration(struct.name, ps))
param_templates.append(template_declaration.substitute(CLASS=struct.name))
self.WriteTemplateToFile("interface_serialization.h", interface.name,
HEADER_GUARD = self.GetHeaderGuard(interface.name + "_SERIALIZATION"),
INCLUDES = '\n'.join(includes),
NAMES = '\n'.join(names),
PARAM_CLASSES = '\n'.join(param_classes),
PARAM_TEMPLATES = '\n'.join(param_templates))
def GenerateInterfaceSerializationSource(self, interface):
implementations = []
serializations = []
for method in interface.methods:
struct = GetStructFromMethod(interface, method)
ps = mojom_pack.PackedStruct(struct)
implementations.append(self.GetStructImplementation(struct.name, ps))
serializations.append(
self.GetStructSerialization("internal::" + struct.name, "params", ps))
self.WriteTemplateToFile("interface_serialization.cc", interface.name,
HEADER = self.GetHeaderFile(interface.name.lower(), "serialization"),
IMPLEMENTATIONS = '\n'.join(implementations),
SERIALIZATIONS = '\n'.join(serializations))
def GenerateInterfaceProxyHeader(self, interface):
methods = []
for method in interface.methods:
params = map(
lambda param: "%s %s" % (self.GetConstType(param.kind), param.name),
method.parameters)
methods.append(
" virtual void %s(%s) MOJO_OVERRIDE;" \
% (method.name, ", ".join(params)))
self.WriteTemplateToFile("interface_proxy.h", interface.name,
HEADER_GUARD = self.GetHeaderGuard(interface.name + "_PROXY"),
HEADER = self.GetHeaderFile(interface.name),
CLASS = interface.name.capitalize(),
METHODS = '\n'.join(methods))
def GenerateInterfaceProxySource(self, interface):
implementations = Lines(self.GetTemplate("proxy_implementation"))
for method in interface.methods:
sets = []
computes = Lines(self.param_struct_compute_template)
for param in method.parameters:
if IsPointerKind(param.kind):
sets.append(
self.param_struct_set_template.substitute(NAME=param.name))
computes.Add(NAME=param.name)
else:
sets.append(self.param_set_template.substitute(NAME=param.name))
params_list = map(
lambda param: "%s %s" % (self.GetConstType(param.kind), param.name),
method.parameters)
name = \
"internal::k%s_%s_Name" % (interface.name.capitalize(), method.name)
params_name = \
"internal::%s_%s_Params" % (interface.name.capitalize(), method.name)
implementations.Add(
CLASS = interface.name.capitalize(),
METHOD = method.name,
NAME = name,
PARAMS = params_name,
PARAMS_LIST = ', '.join(params_list),
COMPUTES = computes,
SETS = '\n'.join(sets))
self.WriteTemplateToFile("interface_proxy.cc", interface.name,
HEADER = self.GetHeaderFile(interface.name, "proxy"),
SERIALIZATION_HEADER = \
self.GetHeaderFile(interface.name, "serialization"),
CLASS = interface.name.capitalize(),
IMPLEMENTATIONS = implementations)
def GenerateFiles(self):
for struct in self.module.structs:
ps = mojom_pack.PackedStruct(struct)
self.GenerateStructHeader(ps)
self.GenerateStructSource(ps)
self.GenerateStructSerializationHeader(ps)
self.GenerateStructSerializationSource(ps)
for interface in self.module.interfaces:
self.GenerateInterfaceHeader(interface)
self.GenerateInterfaceStubHeader(interface)
self.GenerateInterfaceStubSource(interface)
self.GenerateInterfaceSerializationHeader(interface)
self.GenerateInterfaceSerializationSource(interface)
self.GenerateInterfaceProxyHeader(interface)
self.GenerateInterfaceProxySource(interface)