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