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