Home | History | Annotate | Download | only in scripts
      1 # Copyright (C) 2013 Google Inc. All rights reserved.
      2 #
      3 # Redistribution and use in source and binary forms, with or without
      4 # modification, are permitted provided that the following conditions are
      5 # met:
      6 #
      7 #     * Redistributions of source code must retain the above copyright
      8 # notice, this list of conditions and the following disclaimer.
      9 #     * Redistributions in binary form must reproduce the above
     10 # copyright notice, this list of conditions and the following disclaimer
     11 # in the documentation and/or other materials provided with the
     12 # distribution.
     13 #     * Neither the name of Google Inc. nor the names of its
     14 # contributors may be used to endorse or promote products derived from
     15 # this software without specific prior written permission.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 """Generate Blink V8 bindings (.h and .cpp files).
     30 
     31 Input: An object of class IdlDefinitions, containing an IDL interface X
     32 Output: V8X.h and V8X.cpp
     33 """
     34 
     35 import os
     36 import posixpath
     37 import re
     38 import sys
     39 
     40 # jinja2 is in chromium's third_party directory.
     41 module_path, module_name = os.path.split(__file__)
     42 third_party = os.path.join(module_path, os.pardir, os.pardir, os.pardir, os.pardir)
     43 sys.path.append(third_party)
     44 import jinja2
     45 
     46 
     47 CALLBACK_INTERFACE_CPP_INCLUDES = set([
     48     'core/dom/ScriptExecutionContext.h',
     49     'bindings/v8/V8Binding.h',
     50     'bindings/v8/V8Callback.h',
     51     'wtf/Assertions.h',
     52 ])
     53 
     54 
     55 CALLBACK_INTERFACE_H_INCLUDES = set([
     56     'bindings/v8/ActiveDOMCallback.h',
     57     'bindings/v8/DOMWrapperWorld.h',
     58     'bindings/v8/ScopedPersistent.h',
     59 ])
     60 
     61 
     62 INTERFACE_CPP_INCLUDES = set([
     63     'RuntimeEnabledFeatures.h',
     64     'bindings/v8/ScriptController.h',
     65     'bindings/v8/V8Binding.h',
     66     'core/dom/ContextFeatures.h',
     67     'core/dom/Document.h',
     68     'core/page/Frame.h',
     69     'core/platform/chromium/TraceEvent.h',
     70     'wtf/UnusedParam.h',
     71 ])
     72 
     73 
     74 INTERFACE_H_INCLUDES = set([
     75     'bindings/v8/V8Binding.h',
     76 ])
     77 
     78 
     79 CPP_TYPE_SPECIAL_CONVERSION_RULES = {
     80     'float': 'float',
     81     'double': 'double',
     82     'long long': 'long long',
     83     'unsigned long long': 'unsigned long long',
     84     'long': 'int',
     85     'short': 'int',
     86     'byte': 'int',
     87     'boolean': 'bool',
     88     'DOMString': 'const String&',
     89 }
     90 
     91 
     92 PRIMITIVE_TYPES = set([
     93     'boolean',
     94     'void',
     95     'Date',
     96     'byte',
     97     'octet',
     98     'short',
     99     'long',
    100     'long long',
    101     'unsigned short',
    102     'unsigned long',
    103     'unsigned long long',
    104     'float',
    105     'double',
    106 ])
    107 
    108 
    109 def apply_template(path_to_template, contents):
    110     dirname, basename = os.path.split(path_to_template)
    111     jinja_env = jinja2.Environment(trim_blocks=True, loader=jinja2.FileSystemLoader([dirname]))
    112     template = jinja_env.get_template(basename)
    113     return template.render(contents)
    114 
    115 
    116 def cpp_value_to_js_value(data_type, cpp_value, isolate, creation_context=''):
    117     """Returns a expression that represent JS value corresponding to a C++ value."""
    118     if data_type == 'boolean':
    119         return 'v8Boolean(%s, %s)' % (cpp_value, isolate)
    120     if data_type in ['long long', 'unsigned long long', 'DOMTimeStamp']:
    121         # long long and unsigned long long are not representable in ECMAScript.
    122         return 'v8::Number::New(static_cast<double>(%s))' % cpp_value
    123     if primitive_type(data_type):
    124         if data_type not in ['float', 'double']:
    125             raise Exception('unexpected data_type %s' % data_type)
    126         return 'v8::Number::New(%s)' % cpp_value
    127     if data_type == 'DOMString':
    128         return 'v8String(%s, %s)' % (cpp_value, isolate)
    129     if array_or_sequence_type(data_type):
    130         return 'v8Array(%s, %s)' % (cpp_value, isolate)
    131     return 'toV8(%s, %s, %s)' % (cpp_value, creation_context, isolate)
    132 
    133 
    134 def generate_conditional_string(interface_or_attribute_or_operation):
    135     if 'Conditional' not in interface_or_attribute_or_operation.extended_attributes:
    136         return ''
    137     conditional = interface_or_attribute_or_operation.extended_attributes['Conditional']
    138     for operator in ['&', '|']:
    139         if operator in conditional:
    140             conditions = set(conditional.split(operator))
    141             operator_separator = ' %s%s ' % (operator, operator)
    142             return operator_separator.join(['ENABLE(%s)' % expression for expression in sorted(conditions)])
    143     return 'ENABLE(%s)' % conditional
    144 
    145 
    146 def includes_for_type(data_type):
    147     if primitive_type(data_type) or data_type == 'DOMString':
    148         return set()
    149     if array_or_sequence_type(data_type):
    150         return includes_for_type(array_or_sequence_type(data_type))
    151     return set(['V8%s.h' % data_type])
    152 
    153 
    154 def includes_for_cpp_class(class_name, relative_dir_posix):
    155     return set([posixpath.join('bindings', relative_dir_posix, class_name + '.h')])
    156 
    157 
    158 def includes_for_operation(operation):
    159     includes = includes_for_type(operation.data_type)
    160     for parameter in operation.arguments:
    161         includes |= includes_for_type(parameter.data_type)
    162     return includes
    163 
    164 
    165 def primitive_type(data_type):
    166     return data_type in PRIMITIVE_TYPES
    167 
    168 
    169 def sequence_type(data_type):
    170     matched = re.match(r'sequence<([\w\d_\s]+)>', data_type)
    171     if not matched:
    172         return None
    173     return matched.group(1)
    174 
    175 
    176 def array_type(data_type):
    177     matched = re.match(r'([\w\d_\s]+)\[\]', data_type)
    178     if not matched:
    179         return None
    180     return matched.group(1)
    181 
    182 
    183 def array_or_sequence_type(data_type):
    184     return array_type(data_type) or sequence_type(data_type)
    185 
    186 def cpp_type(data_type, pointer_type):
    187     """Returns the C++ type corresponding to the IDL type.
    188 
    189     Args:
    190         pointer_type:
    191             'raw': return raw pointer form (e.g. Foo*)
    192             'RefPtr': return RefPtr form (e.g. RefPtr<Foo>)
    193             'PassRefPtr': return PassRefPtr form (e.g. RefPtr<Foo>)
    194     """
    195     if data_type in CPP_TYPE_SPECIAL_CONVERSION_RULES:
    196         return CPP_TYPE_SPECIAL_CONVERSION_RULES[data_type]
    197     if array_or_sequence_type(data_type):
    198         return 'const Vector<%s >&' % cpp_type(array_or_sequence_type(data_type), 'RefPtr')
    199     if pointer_type == 'raw':
    200         return data_type + '*'
    201     if pointer_type in ['RefPtr', 'PassRefPtr']:
    202         return '%s<%s>' % (pointer_type, data_type)
    203     raise Exception('Unrecognized pointer type: "%s"' % pointer_type)
    204 
    205 
    206 def v8_type(data_type):
    207     return 'V8' + data_type
    208 
    209 
    210 def cpp_method_name(attribute_or_operation):
    211     return attribute_or_operation.extended_attributes.get('ImplementedAs', attribute_or_operation.name)
    212 
    213 
    214 def cpp_class_name(interface):
    215     return interface.extended_attributes.get('ImplementedAs', interface.name)
    216 
    217 
    218 def v8_class_name(interface):
    219     return v8_type(interface.name)
    220 
    221 
    222 class CodeGeneratorV8:
    223     def __init__(self, definitions, interface_name, output_directory, relative_dir_posix, idl_directories, verbose=False):
    224         self.idl_definitions = definitions
    225         self.interface_name = interface_name
    226         self.idl_directories = idl_directories
    227         self.output_directory = output_directory
    228         self.relative_dir_posix = relative_dir_posix
    229         self.verbose = verbose
    230         self.interface = None
    231         self.header_includes = set()
    232         self.cpp_includes = set()
    233         if definitions:  # FIXME: remove check when remove write_dummy_header_and_cpp
    234             try:
    235                 self.interface = definitions.interfaces[interface_name]
    236             except KeyError:
    237                 raise Exception('%s not in IDL definitions' % interface_name)
    238 
    239     def generate_cpp_to_js_conversion(self, data_type, cpp_value, format_string, isolate, creation_context=''):
    240         """Returns a statement that converts a C++ value to a JS value.
    241 
    242         Also add necessary includes to self.cpp_includes.
    243         """
    244         self.cpp_includes |= includes_for_type(data_type)
    245         js_value = cpp_value_to_js_value(data_type, cpp_value, isolate, creation_context)
    246         return format_string % js_value
    247 
    248     def write_dummy_header_and_cpp(self):
    249         # FIXME: fix GYP so these files aren't needed and remove this method
    250         target_interface_name = self.interface_name
    251         header_basename = 'V8%s.h' % target_interface_name
    252         cpp_basename = 'V8%s.cpp' % target_interface_name
    253         contents = """/*
    254     This file is generated just to tell build scripts that {header_basename} and
    255     {cpp_basename} are created for {target_interface_name}.idl, and thus
    256     prevent the build scripts from trying to generate {header_basename} and
    257     {cpp_basename} at every build. This file must not be tried to compile.
    258 */
    259 """.format(**locals())
    260         self.write_header_code(header_basename, contents)
    261         self.write_cpp_code(cpp_basename, contents)
    262 
    263     def write_header_and_cpp(self):
    264         header_basename = v8_class_name(self.interface) + '.h'
    265         cpp_basename = v8_class_name(self.interface) + '.cpp'
    266         if self.interface.is_callback:
    267             header_template = 'templates/callback_interface.h'
    268             cpp_template = 'templates/callback_interface.cpp'
    269             template_contents = self.generate_callback_interface()
    270         else:
    271             header_template = 'templates/interface.h'
    272             cpp_template = 'templates/interface.cpp'
    273             template_contents = self.generate_interface()
    274         template_contents['conditional_string'] = generate_conditional_string(self.interface)
    275         header_file_text = apply_template(header_template, template_contents)
    276         cpp_file_text = apply_template(cpp_template, template_contents)
    277         self.write_header_code(header_basename, header_file_text)
    278         self.write_cpp_code(cpp_basename, cpp_file_text)
    279 
    280     def write_header_code(self, header_basename, header_file_text):
    281         header_filename = os.path.join(self.output_directory, header_basename)
    282         with open(header_filename, 'w') as header_file:
    283             header_file.write(header_file_text)
    284 
    285     def write_cpp_code(self, cpp_basename, cpp_file_text):
    286         cpp_filename = os.path.join(self.output_directory, cpp_basename)
    287         with open(cpp_filename, 'w') as cpp_file:
    288             cpp_file.write(cpp_file_text)
    289 
    290     def generate_attribute(self, attribute):
    291         self.cpp_includes |= includes_for_type(attribute.data_type)
    292         return {
    293             'name': attribute.name,
    294             'conditional_string': generate_conditional_string(attribute),
    295             'cpp_method_name': cpp_method_name(attribute),
    296             'cpp_type': cpp_type(attribute.data_type, pointer_type='RefPtr'),
    297             'v8_type': v8_type(attribute.data_type),
    298         }
    299 
    300     def generate_interface(self):
    301         self.header_includes = INTERFACE_H_INCLUDES
    302         self.header_includes |= includes_for_cpp_class(cpp_class_name(self.interface), self.relative_dir_posix)
    303         self.cpp_includes = INTERFACE_CPP_INCLUDES
    304 
    305         template_contents = {
    306             'interface_name': self.interface.name,
    307             'cpp_class_name': cpp_class_name(self.interface),
    308             'v8_class_name': v8_class_name(self.interface),
    309             'attributes': [self.generate_attribute(attribute) for attribute in self.interface.attributes],
    310             # Size 0 constant array is not allowed in VC++
    311             'number_of_attributes': 'WTF_ARRAY_LENGTH(%sAttributes)' % v8_class_name(self.interface) if self.interface.attributes else '0',
    312             'attribute_templates': v8_class_name(self.interface) + 'Attributes' if self.interface.attributes else '0',
    313         }
    314         # Add includes afterwards, as they are modified by generate_attribute etc.
    315         template_contents['header_includes'] = sorted(list(self.header_includes))
    316         template_contents['cpp_includes'] = sorted(list(self.cpp_includes))
    317         return template_contents
    318 
    319     def generate_callback_interface(self):
    320         self.header_includes = CALLBACK_INTERFACE_H_INCLUDES
    321         self.header_includes |= includes_for_cpp_class(cpp_class_name(self.interface), self.relative_dir_posix)
    322         self.cpp_includes = CALLBACK_INTERFACE_CPP_INCLUDES
    323 
    324         def generate_argument(argument):
    325             receiver = 'v8::Handle<v8::Value> %sHandle = %%s;' % argument.name
    326             cpp_to_js_conversion = self.generate_cpp_to_js_conversion(argument.data_type, argument.name, receiver, 'isolate', creation_context='v8::Handle<v8::Object>()')
    327             return {
    328                 'name': argument.name,
    329                 'cpp_to_js_conversion': cpp_to_js_conversion,
    330             }
    331 
    332         def generate_method(operation):
    333             def argument_declaration(argument):
    334                 return '%s %s' % (cpp_type(argument.data_type, 'raw'), argument.name)
    335 
    336             arguments = []
    337             custom = 'Custom' in operation.extended_attributes
    338             if not custom:
    339                 self.cpp_includes |= includes_for_operation(operation)
    340                 if operation.data_type != 'boolean':
    341                     raise Exception("We don't yet support callbacks that return non-boolean values.")
    342                 arguments = [generate_argument(argument) for argument in operation.arguments]
    343             method = {
    344                 'return_cpp_type': cpp_type(operation.data_type, 'RefPtr'),
    345                 'name': operation.name,
    346                 'arguments': arguments,
    347                 'argument_declaration': ', '.join([argument_declaration(argument) for argument in operation.arguments]),
    348                 'handles': ', '.join(['%sHandle' % argument.name for argument in operation.arguments]),
    349                 'custom': custom,
    350             }
    351             return method
    352 
    353         methods = [generate_method(operation) for operation in self.interface.operations]
    354         template_contents = {
    355             'cpp_class_name': self.interface.name,
    356             'v8_class_name': v8_class_name(self.interface),
    357             'cpp_includes': sorted(list(self.cpp_includes)),
    358             'header_includes': sorted(list(self.header_includes)),
    359             'methods': methods,
    360         }
    361         return template_contents
    362