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
7import re
8
9interface_name_map = {
10    'InjectedScriptHost': 'InjectedScriptHostClass'
11}
12
13type_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
25idl_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)
42type_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
50method_expr = r'^\s*(?:\[.+\])?\s+' + type_expr + r'\s+(\w+)\s*\(([^)]*)\)\s*;\s*$'
51method_regex = re.compile(method_expr)
52
53# Groups:
54# 1: type name
55# 2:   array (optional)
56# 3:   nullable (optional)
57# 4: attribute name
58attribute_expr = r'^\s*(?:\[.+\]\s+)?(?:\breadonly\s+)?\battribute\s+' + type_expr + r'\s+(\w+)\s*;'
59attribute_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
67arg_regex = re.compile(r'\s*(?:\[[^]]+\]\s*)?(\boptional\s+)?' + type_expr + r'\s+(\w+)')
68
69interface_regex = r'\binterface\s+(\w+)'
70
71other_externs = """
72/** @type {!Window} */
73var inspectedWindow;
74/** @type {number} */
75var injectedScriptId;
76
77// FIXME: Remove once ES6 is supported natively by JS compiler.
78
79/** @typedef {string} */
80var symbol;
81
82/**
83 * @param {string} description
84 * @return {symbol}
85 */
86function Symbol(description) {}
87"""
88
89
90class 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
114class Attribute:
115    def __init__(self, type, name):
116        self.type = type
117        self.name = name
118
119
120class 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
133class 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
146class Interface:
147    def __init__(self, name, methods, attributes):
148        self.name = name
149        self.methods = methods
150        self.attributes = attributes
151
152
153def 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
160def 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
183def 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
207def 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
212def 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
222if __name__ == "__main__":
223    import sys
224    sys.exit(main(sys.argv))
225