1#!/usr/bin/env python
2# Copyright (c) 2013 Google Inc. All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8#     * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following disclaimer
12# in the documentation and/or other materials provided with the
13# distribution.
14#     * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived from
16# this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30import optparse
31import re
32import string
33import sys
34
35template_h = string.Template("""// Code generated from InspectorInstrumentation.idl
36
37#ifndef ${file_name}_h
38#define ${file_name}_h
39
40${includes}
41
42namespace WebCore {
43
44namespace InspectorInstrumentation {
45
46$methods
47} // namespace InspectorInstrumentation
48
49} // namespace WebCore
50
51#endif // !defined(${file_name}_h)
52""")
53
54template_inline = string.Template("""
55inline void ${name}(${params_public})
56{   ${fast_return}
57    if (${condition})
58        ${name}Impl(${params_impl});
59}
60""")
61
62template_inline_forward = string.Template("""
63inline void ${name}(${params_public})
64{   ${fast_return}
65    ${name}Impl(${params_impl});
66}
67""")
68
69template_inline_returns_value = string.Template("""
70inline ${return_type} ${name}(${params_public})
71{   ${fast_return}
72    if (${condition})
73        return ${name}Impl(${params_impl});
74    return ${default_return_value};
75}
76""")
77
78
79template_cpp = string.Template("""// Code generated from InspectorInstrumentation.idl
80
81#include "config.h"
82
83${includes}
84
85namespace WebCore {
86${extra_definitions}
87
88namespace InspectorInstrumentation {
89$methods
90
91} // namespace InspectorInstrumentation
92
93} // namespace WebCore
94""")
95
96template_outofline = string.Template("""
97${return_type} ${name}Impl(${params_impl})
98{${impl_lines}
99}""")
100
101template_agent_call = string.Template("""
102    if (${agent_class}* agent = ${agent_fetch})
103        ${maybe_return}agent->${name}(${params_agent});""")
104
105template_agent_call_timeline_returns_cookie = string.Template("""
106    int timelineAgentId = 0;
107    if (InspectorTimelineAgent* agent = agents->inspectorTimelineAgent()) {
108        if (agent->${name}(${params_agent}))
109            timelineAgentId = agent->id();
110    }""")
111
112
113template_instrumenting_agents_h = string.Template("""// Code generated from InspectorInstrumentation.idl
114
115#ifndef InstrumentingAgentsInl_h
116#define InstrumentingAgentsInl_h
117
118#include "wtf/FastAllocBase.h"
119#include "wtf/Noncopyable.h"
120#include "wtf/PassRefPtr.h"
121#include "wtf/RefCounted.h"
122
123namespace WebCore {
124
125${forward_list}
126
127class InstrumentingAgents : public RefCounted<InstrumentingAgents> {
128    WTF_MAKE_NONCOPYABLE(InstrumentingAgents);
129    WTF_MAKE_FAST_ALLOCATED;
130public:
131    static PassRefPtr<InstrumentingAgents> create()
132    {
133        return adoptRef(new InstrumentingAgents());
134    }
135    ~InstrumentingAgents() { }
136    void reset();
137
138${accessor_list}
139
140private:
141    InstrumentingAgents();
142
143${member_list}
144};
145
146}
147
148#endif // !defined(InstrumentingAgentsInl_h)
149""")
150
151template_instrumenting_agent_accessor = string.Template("""
152    ${class_name}* ${getter_name}() const { return ${member_name}; }
153    void set${class_name}(${class_name}* agent) { ${member_name} = agent; }""")
154
155template_instrumenting_agents_cpp = string.Template("""
156InstrumentingAgents::InstrumentingAgents()
157    : $init_list
158{
159}
160
161void InstrumentingAgents::reset()
162{
163    $reset_list
164}""")
165
166
167
168def match_and_consume(pattern, source):
169    match = re.match(pattern, source)
170    if match:
171        return match, source[len(match.group(0)):].strip()
172    return None, source
173
174
175def load_model_from_idl(source):
176    source = re.sub("//.*", "", source)  # Remove line comments
177    source = re.sub("/\*(.|\n)*?\*/", "", source, re.MULTILINE)  # Remove block comments
178    source = re.sub("\]\s*?\n\s*", "] ", source)  # Merge the method annotation with the next line
179    source = source.strip()
180
181    model = []
182
183    while len(source):
184        match, source = match_and_consume("interface\s(\w*)\s?\{([^\{]*)\}", source)
185        if not match:
186            sys.stderr.write("Cannot parse %s\n" % source[:100])
187            sys.exit(1)
188        model.append(File(match.group(1), match.group(2)))
189
190    return model
191
192
193class File:
194    def __init__(self, name, source):
195        self.name = name
196        self.header_name = self.name + "Inl"
197        self.includes = [include_inspector_header("InspectorInstrumentation")]
198        self.declarations = []
199        for line in map(str.strip, source.split("\n")):
200            line = re.sub("\s{2,}", " ", line).strip()  # Collapse whitespace
201            if len(line) == 0:
202                continue
203            if line[0] == "#":
204                self.includes.append(line)
205            else:
206                self.declarations.append(Method(line))
207        self.includes.sort()
208
209    def generate(self, cpp_lines, used_agents):
210        header_lines = []
211        for declaration in self.declarations:
212            for agent in set(declaration.agents):
213                used_agents.add(agent)
214            declaration.generate_header(header_lines)
215            declaration.generate_cpp(cpp_lines)
216
217        return template_h.substitute(None,
218                                     file_name=self.header_name,
219                                     includes="\n".join(self.includes),
220                                     methods="\n".join(header_lines))
221
222
223class Method:
224    def __init__(self, source):
225        match = re.match("(\[[\w|,|=|\s]*\])?\s?(\w*\*?) (\w*)\((.*)\)\s?;", source)
226        if not match:
227            sys.stderr.write("Cannot parse %s\n" % source)
228            sys.exit(1)
229
230        self.options = []
231        if match.group(1):
232            options_str = re.sub("\s", "", match.group(1)[1:-1])
233            if len(options_str) != 0:
234                self.options = options_str.split(",")
235
236        self.return_type = match.group(2)
237
238        self.name = match.group(3)
239
240        # Splitting parameters by a comma, assuming that attribute lists contain no more than one attribute.
241        self.params = map(Parameter, map(str.strip, match.group(4).split(",")))
242
243        self.accepts_cookie = len(self.params) and self.params[0].type == "const InspectorInstrumentationCookie&"
244        self.returns_cookie = self.return_type == "InspectorInstrumentationCookie"
245
246        self.returns_value = self.return_type != "void"
247
248        if self.return_type == "bool":
249            self.default_return_value = "false"
250        elif self.return_type == "String":
251            self.default_return_value = "\"\""
252        else:
253            self.default_return_value = self.return_type + "()"
254
255        for param in self.params:
256            if "DefaultReturn" in param.options:
257                self.default_return_value = param.name
258
259        self.params_impl = self.params
260        if not self.accepts_cookie and not "Inline=Forward" in self.options:
261            if not "Keep" in self.params_impl[0].options:
262                self.params_impl = self.params_impl[1:]
263            self.params_impl = [Parameter("InstrumentingAgents* agents")] + self.params_impl
264
265        self.agents = filter(lambda option: not "=" in option, self.options)
266
267    def generate_header(self, header_lines):
268        if "Inline=Custom" in self.options:
269            return
270
271        header_lines.append("%s %sImpl(%s);" % (
272            self.return_type, self.name, ", ".join(map(Parameter.to_str_class, self.params_impl))))
273
274        if "Inline=FastReturn" in self.options or "Inline=Forward" in self.options:
275            fast_return = "\n    FAST_RETURN_IF_NO_FRONTENDS(%s);" % self.default_return_value
276        else:
277            fast_return = ""
278
279        for param in self.params:
280            if "FastReturn" in param.options:
281                fast_return += "\n    if (!%s)\n        return %s;" % (param.name, self.default_return_value)
282
283        if self.accepts_cookie:
284            condition = "%s.isValid()" % self.params_impl[0].name
285            template = template_inline
286        elif "Inline=Forward" in self.options:
287            condition = ""
288            template = template_inline_forward
289        else:
290            condition = "InstrumentingAgents* agents = instrumentingAgentsFor(%s)" % self.params[0].name
291
292            if self.returns_value:
293                template = template_inline_returns_value
294            else:
295                template = template_inline
296
297        header_lines.append(template.substitute(
298            None,
299            name=self.name,
300            fast_return=fast_return,
301            return_type=self.return_type,
302            default_return_value=self.default_return_value,
303            params_public=", ".join(map(Parameter.to_str_full, self.params)),
304            params_impl=", ".join(map(Parameter.to_str_name, self.params_impl)),
305            condition=condition))
306
307    def generate_cpp(self, cpp_lines):
308        if len(self.agents) == 0:
309            return
310
311        body_lines = map(self.generate_agent_call, self.agents)
312
313        if self.returns_cookie:
314            if "Timeline" in self.agents:
315                timeline_agent_id = "timelineAgentId"
316            else:
317                timeline_agent_id = "0"
318            body_lines.append("\n    return InspectorInstrumentationCookie(agents, %s);" % timeline_agent_id)
319        elif self.returns_value:
320            body_lines.append("\n    return %s;" % self.default_return_value)
321
322        cpp_lines.append(template_outofline.substitute(
323            None,
324            return_type=self.return_type,
325            name=self.name,
326            params_impl=", ".join(map(Parameter.to_str_class_and_name, self.params_impl)),
327            impl_lines="".join(body_lines)))
328
329    def generate_agent_call(self, agent):
330        agent_class, agent_getter = agent_getter_signature(agent)
331
332        leading_param_name = self.params_impl[0].name
333        if not self.accepts_cookie:
334            agent_fetch = "%s->%s()" % (leading_param_name, agent_getter)
335        elif agent == "Timeline":
336            agent_fetch = "retrieveTimelineAgent(%s)" % leading_param_name
337        else:
338            agent_fetch = "%s.instrumentingAgents()->%s()" % (leading_param_name, agent_getter)
339
340        if agent == "Timeline" and self.returns_cookie:
341            template = template_agent_call_timeline_returns_cookie
342        else:
343            template = template_agent_call
344
345        if not self.returns_value or self.returns_cookie:
346            maybe_return = ""
347        else:
348            maybe_return = "return "
349
350        return template.substitute(
351            None,
352            name=self.name,
353            agent_class=agent_class,
354            agent_fetch=agent_fetch,
355            maybe_return=maybe_return,
356            params_agent=", ".join(map(Parameter.to_str_value, self.params_impl)[1:]))
357
358
359class Parameter:
360    def __init__(self, source):
361        self.options = []
362        match, source = match_and_consume("\[(\w*)\]", source)
363        if match:
364            self.options.append(match.group(1))
365
366        parts = map(str.strip, source.split("="))
367        if len(parts) == 1:
368            self.default_value = None
369        else:
370            self.default_value = parts[1]
371
372        param_decl = parts[0]
373
374        if re.match("(const|unsigned long) ", param_decl):
375            min_type_tokens = 2
376        else:
377            min_type_tokens = 1
378
379        if len(param_decl.split(" ")) > min_type_tokens:
380            parts = param_decl.split(" ")
381            self.type = " ".join(parts[:-1])
382            self.name = parts[-1]
383        else:
384            self.type = param_decl
385            self.name = generate_param_name(self.type)
386
387        if re.match("PassRefPtr<", param_decl):
388            self.value = "%s.get()" % self.name
389        else:
390            self.value = self.name
391
392
393    def to_str_full(self):
394        if self.default_value is None:
395            return self.to_str_class_and_name()
396        return "%s %s = %s" % (self.type, self.name, self.default_value)
397
398    def to_str_class_and_name(self):
399        return "%s %s" % (self.type, self.name)
400
401    def to_str_class(self):
402        return self.type
403
404    def to_str_name(self):
405        return self.name
406
407    def to_str_value(self):
408        return self.value
409
410
411def generate_param_name(param_type):
412    base_name = re.match("(const |PassRefPtr<)?(\w*)", param_type).group(2)
413    return "param" + base_name
414
415
416def agent_class_name(agent):
417    custom_agent_names = ["PageDebugger", "PageRuntime", "WorkerRuntime"]
418    if agent in custom_agent_names:
419        return "%sAgent" % agent
420    return "Inspector%sAgent" % agent
421
422
423def agent_getter_signature(agent):
424    agent_class = agent_class_name(agent)
425    return agent_class, agent_class[0].lower() + agent_class[1:]
426
427
428def include_header(name):
429    return "#include \"%s.h\"" % name
430
431
432def include_inspector_header(name):
433    return include_header("core/inspector/" + name)
434
435
436def generate_instrumenting_agents(used_agents):
437    agents = list(used_agents)
438
439    forward_list = []
440    accessor_list = []
441    member_list = []
442    init_list = []
443    reset_list = []
444
445    for agent in agents:
446        class_name, getter_name = agent_getter_signature(agent)
447        member_name = "m_" + getter_name
448
449        forward_list.append("class %s;" % class_name)
450        accessor_list.append(template_instrumenting_agent_accessor.substitute(
451            None,
452            class_name=class_name,
453            getter_name=getter_name,
454            member_name=member_name))
455        member_list.append("    %s* %s;" % (class_name, member_name))
456        init_list.append("%s(0)" % member_name)
457        reset_list.append("%s = 0;" % member_name)
458
459    forward_list.sort()
460    accessor_list.sort()
461    member_list.sort()
462    init_list.sort()
463    reset_list.sort()
464
465    header_lines = template_instrumenting_agents_h.substitute(
466        None,
467        forward_list="\n".join(forward_list),
468        accessor_list="\n".join(accessor_list),
469        member_list="\n".join(member_list))
470
471    cpp_lines = template_instrumenting_agents_cpp.substitute(
472        None,
473        init_list="\n    , ".join(init_list),
474        reset_list="\n    ".join(reset_list))
475
476    return header_lines, cpp_lines
477
478
479def generate(input_path, output_dir):
480    fin = open(input_path, "r")
481    files = load_model_from_idl(fin.read())
482    fin.close()
483
484    cpp_includes = []
485    cpp_lines = []
486    used_agents = set()
487    for f in files:
488        cpp_includes.append(include_header(f.header_name))
489
490        fout = open(output_dir + "/" + f.header_name + ".h", "w")
491        fout.write(f.generate(cpp_lines, used_agents))
492        fout.close()
493
494    for agent in used_agents:
495        cpp_includes.append(include_inspector_header(agent_class_name(agent)))
496    cpp_includes.append(include_header("InstrumentingAgentsInl"))
497    cpp_includes.sort()
498
499    instrumenting_agents_header, instrumenting_agents_cpp = generate_instrumenting_agents(used_agents)
500
501    fout = open(output_dir + "/" + "InstrumentingAgentsInl.h", "w")
502    fout.write(instrumenting_agents_header)
503    fout.close()
504
505    fout = open(output_dir + "/InspectorInstrumentationImpl.cpp", "w")
506    fout.write(template_cpp.substitute(None,
507                                       includes="\n".join(cpp_includes),
508                                       extra_definitions=instrumenting_agents_cpp,
509                                       methods="\n".join(cpp_lines)))
510    fout.close()
511
512
513cmdline_parser = optparse.OptionParser()
514cmdline_parser.add_option("--output_dir")
515
516try:
517    arg_options, arg_values = cmdline_parser.parse_args()
518    if (len(arg_values) != 1):
519        raise Exception("Exactly one plain argument expected (found %s)" % len(arg_values))
520    input_path = arg_values[0]
521    output_dirpath = arg_options.output_dir
522    if not output_dirpath:
523        raise Exception("Output directory must be specified")
524except Exception:
525    # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html
526    exc = sys.exc_info()[1]
527    sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc)
528    sys.stderr.write("Usage: <script> --output_dir <output_dir> InspectorInstrumentation.idl\n")
529    exit(1)
530
531generate(input_path, output_dirpath)
532