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 blink { 43 44${forward_declarations} 45 46namespace InspectorInstrumentation { 47 48$methods 49} // namespace InspectorInstrumentation 50 51} // namespace blink 52 53#endif // !defined(${file_name}_h) 54""") 55 56template_inline = string.Template(""" 57inline void ${name}(${params_public}) 58{ ${fast_return} 59 if (${condition}) 60 ${name}Impl(${params_impl}); 61} 62""") 63 64template_inline_forward = string.Template(""" 65inline void ${name}(${params_public}) 66{ ${fast_return} 67 ${name}Impl(${params_impl}); 68} 69""") 70 71template_inline_returns_value = string.Template(""" 72inline ${return_type} ${name}(${params_public}) 73{ ${fast_return} 74 if (${condition}) 75 return ${name}Impl(${params_impl}); 76 return ${default_return_value}; 77} 78""") 79 80 81template_cpp = string.Template("""// Code generated from InspectorInstrumentation.idl 82 83#include "config.h" 84 85${includes} 86 87namespace blink { 88${extra_definitions} 89 90namespace InspectorInstrumentation { 91$methods 92 93} // namespace InspectorInstrumentation 94 95} // namespace blink 96""") 97 98template_outofline = string.Template(""" 99${return_type} ${name}Impl(${params_impl}) 100{${impl_lines} 101}""") 102 103template_agent_call = string.Template(""" 104 if (${agent_class}* agent = ${agent_fetch}) 105 ${maybe_return}agent->${name}(${params_agent});""") 106 107template_agent_call_timeline_returns_cookie = string.Template(""" 108 int timelineAgentId = 0; 109 if (InspectorTimelineAgent* agent = agents->inspectorTimelineAgent()) { 110 if (agent->${name}(${params_agent})) 111 timelineAgentId = agent->id(); 112 }""") 113 114 115template_instrumenting_agents_h = string.Template("""// Code generated from InspectorInstrumentation.idl 116 117#ifndef InstrumentingAgentsInl_h 118#define InstrumentingAgentsInl_h 119 120#include "platform/heap/Handle.h" 121#include "wtf/FastAllocBase.h" 122#include "wtf/Noncopyable.h" 123#include "wtf/PassRefPtr.h" 124#include "wtf/RefCounted.h" 125 126namespace blink { 127 128${forward_list} 129 130class InstrumentingAgents : public RefCountedWillBeGarbageCollectedFinalized<InstrumentingAgents> { 131 WTF_MAKE_NONCOPYABLE(InstrumentingAgents); 132 WTF_MAKE_FAST_ALLOCATED_WILL_BE_REMOVED; 133public: 134 static PassRefPtrWillBeRawPtr<InstrumentingAgents> create() 135 { 136 return adoptRefWillBeNoop(new InstrumentingAgents()); 137 } 138 ~InstrumentingAgents() { } 139 void trace(Visitor*); 140 void reset(); 141 142${accessor_list} 143 144private: 145 InstrumentingAgents(); 146 147${member_list} 148}; 149 150} 151 152#endif // !defined(InstrumentingAgentsInl_h) 153""") 154 155template_instrumenting_agent_accessor = string.Template(""" 156 ${class_name}* ${getter_name}() const { return ${member_name}; } 157 void set${class_name}(${class_name}* agent) { ${member_name} = agent; }""") 158 159template_instrumenting_agents_cpp = string.Template(""" 160InstrumentingAgents::InstrumentingAgents() 161 : $init_list 162{ 163} 164 165void InstrumentingAgents::trace(Visitor* visitor) 166{ 167 $trace_list 168} 169 170void InstrumentingAgents::reset() 171{ 172 $reset_list 173}""") 174 175 176 177def match_and_consume(pattern, source): 178 match = re.match(pattern, source) 179 if match: 180 return match, source[len(match.group(0)):].strip() 181 return None, source 182 183 184def load_model_from_idl(source): 185 source = re.sub("//.*", "", source) # Remove line comments 186 source = re.sub("/\*(.|\n)*?\*/", "", source, re.MULTILINE) # Remove block comments 187 source = re.sub("\]\s*?\n\s*", "] ", source) # Merge the method annotation with the next line 188 source = source.strip() 189 190 model = [] 191 192 while len(source): 193 match, source = match_and_consume("interface\s(\w*)\s?\{([^\{]*)\}", source) 194 if not match: 195 sys.stderr.write("Cannot parse %s\n" % source[:100]) 196 sys.exit(1) 197 model.append(File(match.group(1), match.group(2))) 198 199 return model 200 201 202class File: 203 def __init__(self, name, source): 204 self.name = name 205 self.header_name = self.name + "Inl" 206 self.includes = [include_inspector_header("InspectorInstrumentation")] 207 self.forward_declarations = [] 208 self.declarations = [] 209 for line in map(str.strip, source.split("\n")): 210 line = re.sub("\s{2,}", " ", line).strip() # Collapse whitespace 211 if len(line) == 0: 212 continue 213 if line[0] == "#": 214 self.includes.append(line) 215 elif line.startswith("class "): 216 self.forward_declarations.append(line) 217 else: 218 self.declarations.append(Method(line)) 219 self.includes.sort() 220 self.forward_declarations.sort() 221 222 def generate(self, cpp_lines, used_agents): 223 header_lines = [] 224 for declaration in self.declarations: 225 for agent in set(declaration.agents): 226 used_agents.add(agent) 227 declaration.generate_header(header_lines) 228 declaration.generate_cpp(cpp_lines) 229 230 return template_h.substitute(None, 231 file_name=self.header_name, 232 includes="\n".join(self.includes), 233 forward_declarations="\n".join(self.forward_declarations), 234 methods="\n".join(header_lines)) 235 236 237class Method: 238 def __init__(self, source): 239 match = re.match("(\[[\w|,|=|\s]*\])?\s?(\w*\*?) (\w*)\((.*)\)\s?;", source) 240 if not match: 241 sys.stderr.write("Cannot parse %s\n" % source) 242 sys.exit(1) 243 244 self.options = [] 245 if match.group(1): 246 options_str = re.sub("\s", "", match.group(1)[1:-1]) 247 if len(options_str) != 0: 248 self.options = options_str.split(",") 249 250 self.return_type = match.group(2) 251 252 self.name = match.group(3) 253 254 # Splitting parameters by a comma, assuming that attribute lists contain no more than one attribute. 255 self.params = map(Parameter, map(str.strip, match.group(4).split(","))) 256 257 self.accepts_cookie = len(self.params) and self.params[0].type == "const InspectorInstrumentationCookie&" 258 self.returns_cookie = self.return_type == "InspectorInstrumentationCookie" 259 260 self.returns_value = self.return_type != "void" 261 262 if self.return_type == "bool": 263 self.default_return_value = "false" 264 elif self.return_type == "int": 265 self.default_return_value = "0" 266 elif self.return_type == "String": 267 self.default_return_value = "\"\"" 268 else: 269 self.default_return_value = self.return_type + "()" 270 271 for param in self.params: 272 if "DefaultReturn" in param.options: 273 self.default_return_value = param.name 274 275 self.params_impl = self.params 276 if not self.accepts_cookie and not "Inline=Forward" in self.options: 277 if not "Keep" in self.params_impl[0].options: 278 self.params_impl = self.params_impl[1:] 279 self.params_impl = [Parameter("InstrumentingAgents* agents")] + self.params_impl 280 281 self.agents = filter(lambda option: not "=" in option, self.options) 282 283 def generate_header(self, header_lines): 284 if "Inline=Custom" in self.options: 285 return 286 287 header_lines.append("%s %sImpl(%s);" % ( 288 self.return_type, self.name, ", ".join(map(Parameter.to_str_class, self.params_impl)))) 289 290 if "Inline=FastReturn" in self.options or "Inline=Forward" in self.options: 291 fast_return = "\n FAST_RETURN_IF_NO_FRONTENDS(%s);" % self.default_return_value 292 else: 293 fast_return = "" 294 295 for param in self.params: 296 if "FastReturn" in param.options: 297 fast_return += "\n if (!%s)\n return %s;" % (param.name, self.default_return_value) 298 299 if self.accepts_cookie: 300 condition = "%s.isValid()" % self.params_impl[0].name 301 template = template_inline 302 elif "Inline=Forward" in self.options: 303 condition = "" 304 template = template_inline_forward 305 else: 306 condition = "InstrumentingAgents* agents = instrumentingAgentsFor(%s)" % self.params[0].name 307 308 if self.returns_value: 309 template = template_inline_returns_value 310 else: 311 template = template_inline 312 313 header_lines.append(template.substitute( 314 None, 315 name=self.name, 316 fast_return=fast_return, 317 return_type=self.return_type, 318 default_return_value=self.default_return_value, 319 params_public=", ".join(map(Parameter.to_str_full, self.params)), 320 params_impl=", ".join(map(Parameter.to_str_name, self.params_impl)), 321 condition=condition)) 322 323 def generate_cpp(self, cpp_lines): 324 if len(self.agents) == 0: 325 return 326 327 body_lines = map(self.generate_ref_ptr, self.params) 328 body_lines += map(self.generate_agent_call, self.agents) 329 330 if self.returns_cookie: 331 if "Timeline" in self.agents: 332 timeline_agent_id = "timelineAgentId" 333 else: 334 timeline_agent_id = "0" 335 body_lines.append("\n return InspectorInstrumentationCookie(agents, %s);" % timeline_agent_id) 336 elif self.returns_value: 337 body_lines.append("\n return %s;" % self.default_return_value) 338 339 cpp_lines.append(template_outofline.substitute( 340 None, 341 return_type=self.return_type, 342 name=self.name, 343 params_impl=", ".join(map(Parameter.to_str_class_and_name, self.params_impl)), 344 impl_lines="".join(body_lines))) 345 346 def generate_agent_call(self, agent): 347 agent_class, agent_getter = agent_getter_signature(agent) 348 349 leading_param_name = self.params_impl[0].name 350 if not self.accepts_cookie: 351 agent_fetch = "%s->%s()" % (leading_param_name, agent_getter) 352 elif agent == "Timeline": 353 agent_fetch = "retrieveTimelineAgent(%s)" % leading_param_name 354 else: 355 agent_fetch = "%s.instrumentingAgents()->%s()" % (leading_param_name, agent_getter) 356 357 if agent == "Timeline" and self.returns_cookie: 358 template = template_agent_call_timeline_returns_cookie 359 else: 360 template = template_agent_call 361 362 if not self.returns_value or self.returns_cookie: 363 maybe_return = "" 364 else: 365 maybe_return = "return " 366 367 return template.substitute( 368 None, 369 name=self.name, 370 agent_class=agent_class, 371 agent_fetch=agent_fetch, 372 maybe_return=maybe_return, 373 params_agent=", ".join(map(Parameter.to_str_value, self.params_impl)[1:])) 374 375 def generate_ref_ptr(self, param): 376 if param.is_prp: 377 return "\n RefPtr<%s> %s = %s;" % (param.inner_type, param.value, param.name) 378 else: 379 return "" 380 381class Parameter: 382 def __init__(self, source): 383 self.options = [] 384 match, source = match_and_consume("\[(\w*)\]", source) 385 if match: 386 self.options.append(match.group(1)) 387 388 parts = map(str.strip, source.split("=")) 389 if len(parts) == 1: 390 self.default_value = None 391 else: 392 self.default_value = parts[1] 393 394 param_decl = parts[0] 395 396 if re.match("(const|unsigned long) ", param_decl): 397 min_type_tokens = 2 398 else: 399 min_type_tokens = 1 400 401 if len(param_decl.split(" ")) > min_type_tokens: 402 parts = param_decl.split(" ") 403 self.type = " ".join(parts[:-1]) 404 self.name = parts[-1] 405 else: 406 self.type = param_decl 407 self.name = generate_param_name(self.type) 408 409 if re.match("PassRefPtr<", param_decl): 410 self.is_prp = True 411 self.value = self.name 412 self.name = "prp" + self.name[0].upper() + self.name[1:] 413 self.inner_type = re.match("PassRefPtr<(.+)>", param_decl).group(1) 414 else: 415 self.is_prp = False 416 self.value = self.name 417 418 419 def to_str_full(self): 420 if self.default_value is None: 421 return self.to_str_class_and_name() 422 return "%s %s = %s" % (self.type, self.name, self.default_value) 423 424 def to_str_class_and_name(self): 425 return "%s %s" % (self.type, self.name) 426 427 def to_str_class(self): 428 return self.type 429 430 def to_str_name(self): 431 return self.name 432 433 def to_str_value(self): 434 return self.value 435 436 437def generate_param_name(param_type): 438 base_name = re.match("(const |PassRefPtr<)?(\w*)", param_type).group(2) 439 return "param" + base_name 440 441 442def agent_class_name(agent): 443 custom_agent_names = ["PageDebugger", "PageRuntime", "WorkerRuntime"] 444 if agent in custom_agent_names: 445 return "%sAgent" % agent 446 return "Inspector%sAgent" % agent 447 448 449def agent_getter_signature(agent): 450 agent_class = agent_class_name(agent) 451 return agent_class, agent_class[0].lower() + agent_class[1:] 452 453 454def include_header(name): 455 return "#include \"%s.h\"" % name 456 457 458def include_inspector_header(name): 459 return include_header("core/inspector/" + name) 460 461 462def generate_instrumenting_agents(used_agents): 463 agents = list(used_agents) 464 465 forward_list = [] 466 accessor_list = [] 467 member_list = [] 468 init_list = [] 469 trace_list = [] 470 reset_list = [] 471 472 for agent in agents: 473 class_name, getter_name = agent_getter_signature(agent) 474 member_name = "m_" + getter_name 475 476 forward_list.append("class %s;" % class_name) 477 accessor_list.append(template_instrumenting_agent_accessor.substitute( 478 None, 479 class_name=class_name, 480 getter_name=getter_name, 481 member_name=member_name)) 482 member_list.append(" RawPtrWillBeMember<%s> %s;" % (class_name, member_name)) 483 init_list.append("%s(nullptr)" % member_name) 484 trace_list.append("visitor->trace(%s);" % member_name) 485 reset_list.append("%s = nullptr;" % member_name) 486 487 forward_list.sort() 488 accessor_list.sort() 489 member_list.sort() 490 init_list.sort() 491 trace_list.sort() 492 reset_list.sort() 493 494 header_lines = template_instrumenting_agents_h.substitute( 495 None, 496 forward_list="\n".join(forward_list), 497 accessor_list="\n".join(accessor_list), 498 member_list="\n".join(member_list)) 499 500 cpp_lines = template_instrumenting_agents_cpp.substitute( 501 None, 502 init_list="\n , ".join(init_list), 503 trace_list="\n ".join(trace_list), 504 reset_list="\n ".join(reset_list)) 505 506 return header_lines, cpp_lines 507 508 509def generate(input_path, output_dir): 510 fin = open(input_path, "r") 511 files = load_model_from_idl(fin.read()) 512 fin.close() 513 514 cpp_includes = [] 515 cpp_lines = [] 516 used_agents = set() 517 for f in files: 518 cpp_includes.append(include_header(f.header_name)) 519 520 fout = open(output_dir + "/" + f.header_name + ".h", "w") 521 fout.write(f.generate(cpp_lines, used_agents)) 522 fout.close() 523 524 for agent in used_agents: 525 cpp_includes.append(include_inspector_header(agent_class_name(agent))) 526 cpp_includes.append(include_header("InstrumentingAgentsInl")) 527 cpp_includes.sort() 528 529 instrumenting_agents_header, instrumenting_agents_cpp = generate_instrumenting_agents(used_agents) 530 531 fout = open(output_dir + "/" + "InstrumentingAgentsInl.h", "w") 532 fout.write(instrumenting_agents_header) 533 fout.close() 534 535 fout = open(output_dir + "/InspectorInstrumentationImpl.cpp", "w") 536 fout.write(template_cpp.substitute(None, 537 includes="\n".join(cpp_includes), 538 extra_definitions=instrumenting_agents_cpp, 539 methods="\n".join(cpp_lines))) 540 fout.close() 541 542 543cmdline_parser = optparse.OptionParser() 544cmdline_parser.add_option("--output_dir") 545 546try: 547 arg_options, arg_values = cmdline_parser.parse_args() 548 if (len(arg_values) != 1): 549 raise Exception("Exactly one plain argument expected (found %s)" % len(arg_values)) 550 input_path = arg_values[0] 551 output_dirpath = arg_options.output_dir 552 if not output_dirpath: 553 raise Exception("Output directory must be specified") 554except Exception: 555 # Work with python 2 and 3 http://docs.python.org/py3k/howto/pyporting.html 556 exc = sys.exc_info()[1] 557 sys.stderr.write("Failed to parse command-line arguments: %s\n\n" % exc) 558 sys.stderr.write("Usage: <script> --output_dir <output_dir> InspectorInstrumentation.idl\n") 559 exit(1) 560 561generate(input_path, output_dirpath) 562