Home | History | Annotate | Download | only in scripts
      1 #!/usr/bin/env python
      2 #
      3 # Copyright 2014 The Chromium Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 import re
      8 
      9 interface_name_map = {
     10     'InjectedScriptHost': 'InjectedScriptHostClass'
     11 }
     12 
     13 type_map = {
     14     'any': '*',
     15     'DOMString': 'string',
     16     'short': 'number',
     17     'unsigned short': 'number',
     18     'long': 'number',
     19     'unsigned long': 'number',
     20     'boolean': 'boolean',
     21     'object': 'Object',
     22     'void': ''
     23 }
     24 
     25 idl_type_exprs = [
     26     r'any',
     27     r'DOMString',
     28     r'short',
     29     r'unsigned\s+short',
     30     r'long',
     31     r'unsigned\s+long',
     32     r'boolean',
     33     r'object',
     34     r'void',
     35     r'\w+'  # Non IDL-specific object types.
     36 ]
     37 
     38 # Groups:
     39 # 1: type name
     40 # 2: array (optional)
     41 # 3: nullable (optional)
     42 type_expr = r'\b(' + r'|'.join(idl_type_exprs) + r')\b(\[\])?(\?)?'
     43 
     44 # Groups:
     45 # 1: return type
     46 # 2:   array (optional)
     47 # 3:   nullable (optional)
     48 # 4: method name
     49 # 5: method arguments
     50 method_expr = r'^\s*(?:\[.+\])?\s+' + type_expr + r'\s+(\w+)\s*\(([^)]*)\)\s*;\s*$'
     51 method_regex = re.compile(method_expr)
     52 
     53 # Groups:
     54 # 1: type name
     55 # 2:   array (optional)
     56 # 3:   nullable (optional)
     57 # 4: attribute name
     58 attribute_expr = r'^\s*(?:\[.+\]\s+)?(?:\breadonly\s+)?\battribute\s+' + type_expr + r'\s+(\w+)\s*;'
     59 attribute_regex = re.compile(attribute_expr)
     60 
     61 # Groups:
     62 # 1: optional (optional)
     63 # 2: type name
     64 # 3: array (optional)
     65 # 4: nullable (optional)
     66 # 5: arg name
     67 arg_regex = re.compile(r'\s*(?:\[[^]]+\]\s*)?(\boptional\s+)?' + type_expr + r'\s+(\w+)')
     68 
     69 interface_regex = r'\binterface\s+(\w+)'
     70 
     71 other_externs = """
     72 /** @type {!Window} */
     73 var inspectedWindow;
     74 /** @type {number} */
     75 var injectedScriptId;
     76 
     77 // FIXME: Remove once ES6 is supported natively by JS compiler.
     78 
     79 /** @typedef {string} */
     80 var symbol;
     81 
     82 /**
     83  * @param {string} description
     84  * @return {symbol}
     85  */
     86 function Symbol(description) {}
     87 """
     88 
     89 
     90 class Type:
     91     def __init__(self, type_name, is_array, is_nullable):
     92         self.type_name = re.sub(r'\s+', ' ', type_name)
     93         self.is_array = is_array
     94         self.is_nullable = is_nullable
     95 
     96     def as_js_type(self):
     97         if self.type_name == 'void':
     98             return ''
     99         result = ''
    100         if self.is_nullable:
    101             result = '?'
    102         elif self._is_object_type():
    103             result = '!'
    104         if self.is_array:
    105             result += 'Array.<%s>' % Type(self.type_name, False, False).as_js_type()
    106         else:
    107             result += type_map.get(self.type_name, self.type_name)
    108         return result
    109 
    110     def _is_object_type(self):
    111         return self.is_array or self.type_name == 'object' or not type_map.get(self.type_name)
    112 
    113 
    114 class Attribute:
    115     def __init__(self, type, name):
    116         self.type = type
    117         self.name = name
    118 
    119 
    120 class Argument:
    121     def __init__(self, type, optional, name):
    122         self.type = type
    123         self.optional = optional
    124         self.name = name
    125 
    126     def as_js_param_type(self):
    127         result = self.type.as_js_type()
    128         if self.optional:
    129             result += '='
    130         return result
    131 
    132 
    133 class Method:
    134     def __init__(self, return_type, name, args):
    135         self.return_type = return_type
    136         self.name = name
    137         self.args = args
    138 
    139     def js_argument_names(self):
    140         result = []
    141         for arg in self.args:
    142             result.append(arg.name)
    143         return ', '.join(result)
    144 
    145 
    146 class Interface:
    147     def __init__(self, name, methods, attributes):
    148         self.name = name
    149         self.methods = methods
    150         self.attributes = attributes
    151 
    152 
    153 def parse_args(text):
    154     arguments = []
    155     for (optional, type_name, is_array, is_nullable, arg_name) in re.findall(arg_regex, text):
    156         arguments.append(Argument(Type(type_name, is_array, is_nullable), optional != '', arg_name))
    157     return arguments
    158 
    159 
    160 def read_interface(idl):
    161     methods = []
    162     attributes = []
    163     with open(idl, "r") as input_file:
    164         for line in input_file.readlines():
    165             match = re.search(method_regex, line)
    166             if match:
    167                 return_type = Type(match.group(1), match.group(2) is not None, match.group(3) is not None)
    168                 name = match.group(4)
    169                 methods.append(Method(return_type, name, parse_args(match.group(5))))
    170                 continue
    171             match = re.search(attribute_regex, line)
    172             if match:
    173                 type = Type(match.group(1), match.group(2) is not None, match.group(3) is not None)
    174                 name = match.group(4)
    175                 attributes.append(Attribute(type, name))
    176                 continue
    177             match = re.search(interface_regex, line)
    178             if match:
    179                 interface_name = match.group(1)
    180     return Interface(interface_name, methods, attributes)
    181 
    182 
    183 def generate_injected_script_externs(input_idls, output):
    184     for idl in input_idls:
    185         ifc = read_interface(idl)
    186         interface_name = interface_name_map.get(ifc.name, ifc.name)
    187         output.write('/** @interface */\nfunction %s()\n{\n' % interface_name)
    188         for attribute in ifc.attributes:
    189             output.write('    /** @type {%s} */\n' % attribute.type.as_js_type())
    190             output.write('    this.%s;\n' % attribute.name)
    191         output.write('}\n')
    192         for method in ifc.methods:
    193             output.write('\n/**\n')
    194             for arg in method.args:
    195                 output.write(' * @param {%s} %s\n' % (arg.as_js_param_type(), arg.name))
    196             return_type = method.return_type.as_js_type()
    197             if return_type:
    198                 output.write(' * @return {%s}\n' % return_type)
    199             output.write(' */\n')
    200             output.write('%s.prototype.%s = function(%s) {}\n' % (interface_name, method.name, method.js_argument_names()))
    201         if interface_name != ifc.name:
    202             output.write('\n/** @type {!%s} */\nvar %s;\n' % (interface_name, ifc.name))
    203         output.write('\n')
    204     output.write(other_externs)
    205 
    206 
    207 def generate_injected_script_externs_to_file(input_idls, output_name):
    208     with open(output_name, 'w') as output:
    209         generate_injected_script_externs(input_idls, output)
    210 
    211 
    212 def main(argv):
    213     import os.path
    214     program_name = os.path.basename(__file__)
    215     if len(argv) < 3:
    216         sys.stderr.write("Usage: %s IDL_1 ... IDL_N OUTPUT_FILE\n" % program_name)
    217         exit(1)
    218     input_idls = argv[1:-1]
    219     generate_injected_script_externs_to_file(input_idls, argv[-1])
    220 
    221 
    222 if __name__ == "__main__":
    223     import sys
    224     sys.exit(main(sys.argv))
    225