2010-08-09 Ilya Tikhonovsky <loislo@chromium.org>
[WebKit-https.git] / WebCore / inspector / CodeGeneratorInspector.pm
1 # Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 package CodeGeneratorInspector;
6
7 use strict;
8
9 use Class::Struct;
10 use File::stat;
11
12 my %typeTransform;
13 $typeTransform{"InspectorClient"} = {
14     "forward" => "InspectorClient",
15     "header" => "InspectorClient.h",
16 };
17 $typeTransform{"Backend"} = {
18     "forward" => "InspectorBackend",
19     "header" => "InspectorBackend.h",
20     "handlerAccessor" => "m_inspectorController->inspectorBackend()",
21 };
22 $typeTransform{"Controller"} = {
23     "forwardHeader" => "InspectorController.h",
24     "handlerAccessor" => "m_inspectorController",
25 };
26 $typeTransform{"Debug"} = {
27     "forward" => "InspectorDebuggerAgent",
28     "header" => "InspectorDebuggerAgent.h",
29     "handlerAccessor" => "m_inspectorController->debuggerAgent()",
30 };
31 $typeTransform{"DOM"} = {
32     "forward" => "InspectorDOMAgent",
33     "header" => "InspectorDOMAgent.h",
34     "handlerAccessor" => "m_inspectorController->domAgent()",
35 };
36 $typeTransform{"ApplicationCache"} = {
37     "forward" => "InspectorApplicationCacheAgent",
38     "header" => "InspectorApplicationCacheAgent.h",
39     "handlerAccessor" => "m_inspectorController->applicationCacheAgent()",
40 };
41 $typeTransform{"Frontend"} = {
42     "forward" => "RemoteInspectorFrontend",
43     "header" => "RemoteInspectorFrontend.h",
44 };
45 $typeTransform{"PassRefPtr"} = {
46     "forwardHeader" => "wtf/PassRefPtr.h",
47 };
48 $typeTransform{"Object"} = {
49     "param" => "PassRefPtr<InspectorObject>",
50     "retVal" => "PassRefPtr<InspectorObject>",
51     "variable" => "RefPtr<InspectorObject>",
52     "defaultValue" => "InspectorObject::create()",
53     "forward" => "InspectorObject",
54     "header" => "InspectorValues.h",
55     "accessorSuffix" => ""
56 };
57 $typeTransform{"Array"} = {
58     "param" => "PassRefPtr<InspectorArray>",
59     "retVal" => "PassRefPtr<InspectorArray>",
60     "variable" => "RefPtr<InspectorArray>",
61     "defaultValue" => "InspectorArray::create()",
62     "forward" => "InspectorArray",
63     "header" => "InspectorValues.h",
64     "accessorSuffix" => ""
65 };
66 $typeTransform{"Value"} = {
67     "param" => "PassRefPtr<InspectorValue>",
68     "retVal" => "PassRefPtr<InspectorValue>",
69     "variable" => "RefPtr<InspectorValue>",
70     "defaultValue" => "InspectorValue::null()",
71     "forward" => "InspectorValue",
72     "header" => "InspectorValues.h",
73     "accessorSuffix" => ""
74 };
75 $typeTransform{"String"} = {
76     "param" => "const String&",
77     "variable" => "String",
78     "forwardHeader" => "wtf/Forward.h",
79     "header" => "PlatformString.h",
80     "accessorSuffix" => "String"
81 };
82 $typeTransform{"long"} = {
83     "param" => "long",
84     "variable" => "long",
85     "defaultValue" => "0",
86     "forward" => "",
87     "header" => "",
88     "accessorSuffix" => "Number"
89 };
90 $typeTransform{"int"} = {
91     "param" => "int",
92     "variable" => "int",
93     "defaultValue" => "0",
94     "forward" => "",
95     "header" => "",
96     "accessorSuffix" => "Number",
97 };
98 $typeTransform{"unsigned long"} = {
99     "param" => "unsigned long",
100     "variable" => "unsigned long",
101     "defaultValue" => "0u",
102     "forward" => "",
103     "header" => "",
104     "accessorSuffix" => "Number"
105 };
106 $typeTransform{"unsigned int"} = {
107     "param" => "unsigned int",
108     "variable" => "unsigned int",
109     "defaultValue" => "0u",
110     "forward" => "",
111     "header" => "",
112     "accessorSuffix" => "Number"
113 };
114 $typeTransform{"boolean"} = {
115     "param" => "bool",
116     "variable"=> "bool",
117     "defaultValue" => "false",
118     "forward" => "",
119     "header" => "",
120     "accessorSuffix" => "Bool"
121 };
122 $typeTransform{"void"} = {
123     "retVal" => "void",
124     "forward" => "",
125     "header" => ""
126 };
127
128 # Default License Templates
129
130 my $licenseTemplate = << "EOF";
131 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
132 // Use of this source code is governed by a BSD-style license that can be
133 // found in the LICENSE file.
134 EOF
135
136 my $codeGenerator;
137 my $outputDir;
138 my $outputHeadersDir;
139 my $writeDependencies;
140 my $verbose;
141
142 my $namespace;
143
144 my $backendClassName;
145 my %backendTypes;
146 my %backendMethods;
147 my @backendMethodsImpl;
148 my $backendConstructor;
149 my $backendFooter;
150
151 my $frontendClassName;
152 my %frontendTypes;
153 my %frontendMethods;
154 my @frontendMethodsImpl;
155 my $frontendConstructor;
156 my $frontendFooter;
157
158 my $callId = new domSignature(); # it is just structure for describing parameters from IDLStructure.pm.
159 $callId->type("long");
160 $callId->name("callId");
161
162 # Default constructor
163 sub new
164 {
165     my $object = shift;
166     my $reference = { };
167
168     $codeGenerator = shift;
169     $outputDir = shift;
170     $outputHeadersDir = shift;
171     shift; # $useLayerOnTop
172     shift; # $preprocessor
173     $writeDependencies = shift;
174     $verbose = shift;
175
176     bless($reference, $object);
177     return $reference;
178 }
179
180 # Params: 'idlDocument' struct
181 sub GenerateModule
182 {
183     my $object = shift;
184     my $dataNode = shift;
185
186     $namespace = $dataNode->module;
187     $namespace =~ s/core/WebCore/;
188 }
189
190 # Params: 'idlDocument' struct
191 sub GenerateInterface
192 {
193     my $object = shift;
194     my $interface = shift;
195     my $defines = shift;
196
197     my $className = $interface->name;
198
199     $frontendClassName = "Remote" . $className . "Frontend";
200     $frontendConstructor = "    ${frontendClassName}(InspectorClient* inspectorClient) : m_inspectorClient(inspectorClient) { }";
201     $frontendFooter = "    InspectorClient* m_inspectorClient;";
202     $frontendTypes{"String"} = 1;
203     $frontendTypes{"InspectorClient"} = 1;
204     $frontendTypes{"PassRefPtr"} = 1;
205
206     $backendClassName = $className . "BackendDispatcher";
207     my @backendHead;
208     push(@backendHead, "    ${backendClassName}(InspectorController* inspectorController) : m_inspectorController(inspectorController) { }");
209     push(@backendHead, "    RemoteInspectorFrontend* inspectorFrontend() const { return m_inspectorController->remoteInspectorFrontend(); }");
210     push(@backendHead, "    void reportProtocolError(const long callId, const String& method, const String& errorText) const;");
211     push(@backendHead, "    void dispatch(const String& message);");
212     push(@backendHead, "private:");
213     $backendConstructor = join("\n", @backendHead);
214     $backendFooter = "    InspectorController* m_inspectorController;";
215     $backendTypes{"Controller"} = 1;
216     $backendTypes{"InspectorClient"} = 1;
217     $backendTypes{"PassRefPtr"} = 1;
218     $backendTypes{"Array"} = 1;
219
220     generateBackendPrivateFunctions();
221     generateFunctions($interface);
222 }
223
224 sub generateFunctions
225 {
226     my $interface = shift;
227
228     foreach my $function (@{$interface->functions}) {
229         generateFrontendFunction($function);
230         generateBackendFunction($function);
231     }
232     push(@backendMethodsImpl, generateBackendDispatcher());
233     push(@backendMethodsImpl, generateBackendReportProtocolError());
234 }
235
236 sub generateFrontendFunction
237 {
238     my $function = shift;
239
240     my $notify = $function->signature->extendedAttributes->{"notify"};
241     my $async = $function->signature->extendedAttributes->{"async"};
242     return if !$async && !$notify;
243     my $functionName = $notify ? $function->signature->name : "did" . ucfirst($function->signature->name);
244
245     my @argsFiltered = grep($_->direction eq "out", @{$function->parameters}); # just keep only out parameters for frontend interface.
246     unshift(@argsFiltered, $callId) if !$notify; # Add callId as the first argument for all frontend did* methods.
247     map($frontendTypes{$_->type} = 1, @argsFiltered); # register required types.
248     my $arguments = join(", ", map($typeTransform{$_->type}->{"param"} . " " . $_->name, @argsFiltered)); # prepare arguments for function signature.
249     my @pushArguments = map("    arguments->push" . $typeTransform{$_->type}->{"accessorSuffix"} . "(" . $_->name . ");", @argsFiltered);
250
251     my $signature = "    void ${functionName}(${arguments});";
252     if (!$frontendMethods{${signature}}) {
253         $frontendMethods{${signature}} = 1;
254
255         my @function;
256         push(@function, "void ${frontendClassName}::${functionName}(${arguments})");
257         push(@function, "{");
258         push(@function, "    RefPtr<InspectorArray> arguments = InspectorArray::create();");
259         push(@function, "    arguments->pushString(\"" . ($notify ? $functionName : "processResponse") . "\");");
260         push(@function, @pushArguments);
261         push(@function, "    m_inspectorClient->sendMessageToFrontend(arguments->toJSONString());");
262
263         push(@function, "}");
264         push(@function, "");
265         push(@frontendMethodsImpl, @function);
266     }
267 }
268
269 sub generateBackendPrivateFunctions
270 {
271     my $privateFunctions = << "EOF";
272 static String formatWrongArgumentsCountMessage(unsigned expected, unsigned actual)
273 {
274     return String::format("Wrong number of parameters: %d (expected: %d)", actual, expected);
275 }
276
277 static String formatWrongArgumentTypeMessage(unsigned position, const char* name, const char* expectedType)
278 {
279     return String::format("Failed to convert parameter %d (%s) to %s", position, name, expectedType);
280 }
281 EOF
282     push(@backendMethodsImpl, $privateFunctions);
283 }
284
285 sub generateBackendFunction
286 {
287     my $function = shift;
288     return if $function->signature->extendedAttributes->{"notify"};
289
290     my $functionName = $function->signature->name;
291
292     map($backendTypes{$_->type} = 1, @{$function->parameters}); # register required types
293     my @inArgs = grep($_->direction eq "in", @{$function->parameters});
294     my @outArgs = grep($_->direction eq "out", @{$function->parameters});
295
296     my $signature = "    void ${functionName}(PassRefPtr<InspectorArray> args);";
297     !$backendMethods{${signature}} || die "Duplicate function was detected for signature '$signature'.";
298     $backendMethods{${signature}} = $functionName;
299
300     my @function;
301     push(@function, "void ${backendClassName}::${functionName}(PassRefPtr<InspectorArray> args)");
302     push(@function, "{");
303     push(@function, "    DEFINE_STATIC_LOCAL(String, backendFunctionName, (\"$functionName\"));");
304     push(@function, "    long callId = 0;");
305     push(@function, "");
306
307     my $expectedParametersCount = scalar(@inArgs);
308     my $expectedParametersCountWithMethodName = scalar(@inArgs) + 1;
309     push(@function, "    if (args->length() != $expectedParametersCountWithMethodName) {");
310     push(@function, "        ASSERT_NOT_REACHED();");
311     push(@function, "        reportProtocolError(callId, backendFunctionName, formatWrongArgumentsCountMessage(args->length() - 1, $expectedParametersCount));");
312     push(@function, "        return;");
313     push(@function, "    }");
314     push(@function, "");
315
316     my $i = 1; # zero element is the method name.
317     foreach my $parameter (@inArgs) {
318         my $type = $parameter->type;
319         my $argumentType = $typeTransform{$type}->{$typeTransform{$type}->{"retVal"} ? "retVal" : "variable"};
320         push(@function, "    $argumentType " . $parameter->name . ";") if !($parameter->name eq "callId");
321         push(@function, "    if (!args->get($i)->as" . $typeTransform{$type}->{"accessorSuffix"} . "(&" . $parameter->name . ")) {");
322         push(@function, "        ASSERT_NOT_REACHED();");
323         push(@function, "        reportProtocolError(callId, backendFunctionName, formatWrongArgumentTypeMessage($i, \"" . $parameter->name . "\", \"$type\"));");
324         push(@function, "        return;");
325         push(@function, "    }");
326         push(@function, "");
327         ++$i;
328     }
329
330     my $handler = $function->signature->extendedAttributes->{"handler"} || "Controller";
331     my $handlerAccessor = $typeTransform{$handler}->{"handlerAccessor"};
332     $backendTypes{$handler} = 1;
333     push(@function, "    if (!$handlerAccessor) {");
334     push(@function, "        reportProtocolError(callId, backendFunctionName, \"Error: $handler handler is not available.\");");
335     push(@function, "        return;");
336     push(@function, "    }");
337     push(@function, "");
338
339
340     foreach (@outArgs) { # declare local variables for out arguments.
341         my $initializer = $typeTransform{$_->type}->{"defaultValue"} ? " = " . $typeTransform{$_->type}->{"defaultValue"} : "";
342         push(@function, "    " . $typeTransform{$_->type}->{"variable"} . " " . $_->name . "$initializer;");
343     }
344
345     my $async = $function->signature->extendedAttributes->{"async"};
346     my $args = join(", ", (grep($async || !($_ eq "callId"), map($_->name, @inArgs)), map("&" . $_->name, @outArgs)));
347     push(@function, "    $handlerAccessor->$functionName($args);");
348
349     # The results of function call should be transfered back to frontend (except async methods - need to fix that).
350     if (scalar(grep($_->name eq "callId", @inArgs)) && !$async) {
351         my @pushArguments = map("        arguments->push" . $typeTransform{$_->type}->{"accessorSuffix"} . "(" . $_->name . ");", @outArgs);
352
353         push(@function, "");
354         push(@function, "    // use InspectorFrontend as a marker of WebInspector availability");
355         push(@function, "    if (m_inspectorController->remoteInspectorFrontend()) {");
356         push(@function, "        RefPtr<InspectorArray> arguments = InspectorArray::create();");
357         push(@function, "        arguments->pushString(\"processResponse\");");
358         push(@function, "        arguments->pushNumber(callId);");
359         push(@function, @pushArguments);
360         push(@function, "        m_inspectorController->inspectorClient()->sendMessageToFrontend(arguments->toJSONString());");
361         push(@function, "    }");
362     }
363     push(@function, "}");
364     push(@function, "");
365     push(@backendMethodsImpl, @function);
366 }
367
368 sub generateBackendReportProtocolError
369 {
370     my $reportProtocolError = << "EOF";
371
372 void ${backendClassName}::reportProtocolError(const long callId, const String& method, const String& errorText) const
373 {
374     RefPtr<InspectorArray> arguments = InspectorArray::create();
375     arguments->pushString("reportProtocolError");
376     arguments->pushNumber(callId);
377     arguments->pushString(method);
378     arguments->pushString(errorText);
379     m_inspectorController->inspectorClient()->sendMessageToFrontend(arguments->toJSONString());
380 }
381 EOF
382     return split("\n", $reportProtocolError);
383 }
384
385 sub generateBackendDispatcher
386 {
387     my @body;
388     my @methods = map($backendMethods{$_}, keys %backendMethods);
389     my @mapEntries = map("dispatchMap.add(\"$_\", &${backendClassName}::$_);", @methods);
390
391     push(@body, "void ${backendClassName}::dispatch(const String& message)");
392     push(@body, "{");
393     push(@body, "    typedef void (${backendClassName}::*CallHandler)(PassRefPtr<InspectorArray> args);");
394     push(@body, "    typedef HashMap<String, CallHandler> DispatchMap;");
395     push(@body, "    DEFINE_STATIC_LOCAL(DispatchMap, dispatchMap, );");
396     push(@body, "    if (dispatchMap.isEmpty()) {");
397     push(@body, map("        $_", @mapEntries));
398     push(@body, "    }");
399     push(@body, "");
400     push(@body, "    RefPtr<InspectorValue> parsedMessage = InspectorValue::parseJSON(message);");
401     push(@body, "    if (!parsedMessage) {");
402     push(@body, "        ASSERT_NOT_REACHED();");
403     push(@body, "        reportProtocolError(0, \"dispatch\", \"Error: Invalid message format. Message should be in JSON format.\");");
404     push(@body, "        return;");
405     push(@body, "    }");
406     push(@body, "");
407     push(@body, "    RefPtr<InspectorArray> messageArray = parsedMessage->asArray();");
408     push(@body, "    if (!messageArray) {");
409     push(@body, "        ASSERT_NOT_REACHED();");
410     push(@body, "        reportProtocolError(0, \"dispatch\", \"Error: Invalid message format. The message should be a JSONified array of arguments.\");");
411     push(@body, "        return;");
412     push(@body, "    }");
413     push(@body, "");
414     push(@body, "    if (!messageArray->length()) {");
415     push(@body, "        ASSERT_NOT_REACHED();");
416     push(@body, "        reportProtocolError(0, \"dispatch\", \"Error: Invalid message format. Empty message was received.\");");
417     push(@body, "        return;");
418     push(@body, "    }");
419     push(@body, "");
420     push(@body, "    String methodName;");
421     push(@body, "    if (!messageArray->get(0)->asString(&methodName)) {");
422     push(@body, "        ASSERT_NOT_REACHED();");
423     push(@body, "        reportProtocolError(0, \"dispatch\", \"Error: Invalid message format. The first element of the message should be method name.\");");
424     push(@body, "        return;");
425     push(@body, "    }");
426     push(@body, "");
427     push(@body, "    HashMap<String, CallHandler>::iterator it = dispatchMap.find(methodName);");
428     push(@body, "    if (it == dispatchMap.end()) {");
429     push(@body, "        ASSERT_NOT_REACHED();");
430     push(@body, "        reportProtocolError(0, \"dispatch\", String::format(\"Error: Invalid method name. '%s' wasn't found.\", methodName.utf8().data()));");
431     push(@body, "        return;");
432     push(@body, "    }");
433     push(@body, "");
434     push(@body, "    ((*this).*it->second)(messageArray);");
435     push(@body, "}");
436     return @body;
437 }
438
439 sub generateHeader
440 {
441     my $className = shift;
442     my $types = shift;
443     my $constructor = shift;
444     my $methods = shift;
445     my $footer = shift;
446
447     my $forwardHeaders = join("\n", sort(map("#include <" . $typeTransform{$_}->{"forwardHeader"} . ">", grep($typeTransform{$_}->{"forwardHeader"}, keys %{$types}))));
448     my $forwardDeclarations = join("\n", sort(map("class " . $typeTransform{$_}->{"forward"} . ";", grep($typeTransform{$_}->{"forward"}, keys %{$types}))));
449     my $methodsDeclarations = join("\n", keys %{$methods});
450
451     my $headerBody = << "EOF";
452 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
453 // Use of this source code is governed by a BSD-style license that can be
454 // found in the LICENSE file.
455 #ifndef ${className}_h
456 #define ${className}_h
457
458 ${forwardHeaders}
459
460 namespace $namespace {
461
462 $forwardDeclarations
463
464 class $className {
465 public:
466 $constructor
467
468 $methodsDeclarations
469
470 private:
471 $footer
472 };
473
474 } // namespace $namespace
475 #endif // !defined(${className}_h)
476
477 EOF
478     return $headerBody;
479 }
480
481 sub generateSource
482 {
483     my $className = shift;
484     my $types = shift;
485     my $methods = shift;
486
487     my @sourceContent = split("\r", $licenseTemplate);
488     push(@sourceContent, "\n#include \"config.h\"");
489     push(@sourceContent, "#include \"$className.h\"");
490     push(@sourceContent, "");
491     push(@sourceContent, "#if ENABLE(INSPECTOR)");
492     push(@sourceContent, "");
493
494     my %headers;
495     foreach my $type (keys %{$types}) {
496         $headers{"#include \"" . $typeTransform{$type}->{"header"} . "\""} = 1 if !$typeTransform{$type}->{"header"} eq  "";
497     }
498     push(@sourceContent, sort keys %headers);
499     push(@sourceContent, "");
500     push(@sourceContent, "namespace $namespace {");
501     push(@sourceContent, "");
502     push(@sourceContent, @{$methods});
503     push(@sourceContent, "");
504     push(@sourceContent, "} // namespace $namespace");
505     push(@sourceContent, "");
506     push(@sourceContent, "#endif // ENABLE(INSPECTOR)");
507     push(@sourceContent, "");
508     return @sourceContent;
509 }
510
511 sub finish
512 {
513     my $object = shift;
514
515     open(my $SOURCE, ">$outputDir/$frontendClassName.cpp") || die "Couldn't open file $outputDir/$frontendClassName.cpp";
516     print $SOURCE join("\n", generateSource($frontendClassName, \%frontendTypes, \@frontendMethodsImpl));
517     close($SOURCE);
518     undef($SOURCE);
519
520     open(my $HEADER, ">$outputHeadersDir/$frontendClassName.h") || die "Couldn't open file $outputHeadersDir/$frontendClassName.h";
521     print $HEADER generateHeader($frontendClassName, \%frontendTypes, $frontendConstructor, \%frontendMethods, $frontendFooter);
522     close($HEADER);
523     undef($HEADER);
524
525     open($SOURCE, ">$outputDir/$backendClassName.cpp") || die "Couldn't open file $outputDir/$backendClassName.cpp";
526     print $SOURCE join("\n", generateSource($backendClassName, \%backendTypes, \@backendMethodsImpl));
527     close($SOURCE);
528     undef($SOURCE);
529
530     open($HEADER, ">$outputHeadersDir/$backendClassName.h") || die "Couldn't open file $outputHeadersDir/$backendClassName.h";
531     print $HEADER join("\n", generateHeader($backendClassName, \%backendTypes, $backendConstructor, \%backendMethods, $backendFooter));
532     close($HEADER);
533     undef($HEADER);
534 }
535
536 1;