d4a70fc57709406199edced9427a38ae7f52bb69
[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;
244     if ($notify) {
245         $functionName = $function->signature->name;
246     } else {
247         my $customResponse = $function->signature->extendedAttributes->{"customResponse"};
248         $functionName = $customResponse ? $customResponse : "did" . ucfirst($function->signature->name);
249     }
250
251     my @argsFiltered = grep($_->direction eq "out", @{$function->parameters}); # just keep only out parameters for frontend interface.
252     unshift(@argsFiltered, $callId) if !$notify; # Add callId as the first argument for all frontend did* methods.
253     map($frontendTypes{$_->type} = 1, @argsFiltered); # register required types.
254     my $arguments = join(", ", map($typeTransform{$_->type}->{"param"} . " " . $_->name, @argsFiltered)); # prepare arguments for function signature.
255     my @pushArguments = map("    arguments->push" . $typeTransform{$_->type}->{"accessorSuffix"} . "(" . $_->name . ");", @argsFiltered);
256
257     my $signature = "    void ${functionName}(${arguments});";
258     if (!$frontendMethods{${signature}}) {
259         $frontendMethods{${signature}} = 1;
260
261         my @function;
262         push(@function, "void ${frontendClassName}::${functionName}(${arguments})");
263         push(@function, "{");
264         push(@function, "    RefPtr<InspectorArray> arguments = InspectorArray::create();");
265         push(@function, "    arguments->pushString(\"$functionName\");");
266         push(@function, @pushArguments);
267         push(@function, "    m_inspectorClient->sendMessageToFrontend(arguments->toJSONString());");
268         push(@function, "}");
269         push(@function, "");
270         push(@frontendMethodsImpl, @function);
271     }
272 }
273
274 sub generateBackendPrivateFunctions
275 {
276     my $privateFunctions = << "EOF";
277 static String formatWrongArgumentsCountMessage(unsigned expected, unsigned actual)
278 {
279     return String::format("Wrong number of parameters: %d (expected: %d)", actual, expected);
280 }
281
282 static String formatWrongArgumentTypeMessage(unsigned position, const char* name, const char* expectedType)
283 {
284     return String::format("Failed to convert parameter %d (%s) to %s", position, name, expectedType);
285 }
286 EOF
287     push(@backendMethodsImpl, $privateFunctions);
288 }
289
290 sub generateBackendFunction
291 {
292     my $function = shift;
293     return if $function->signature->extendedAttributes->{"notify"};
294
295     my $functionName = $function->signature->name;
296
297     map($backendTypes{$_->type} = 1, @{$function->parameters}); # register required types
298     my @inArgs = grep($_->direction eq "in", @{$function->parameters});
299     my @outArgs = grep($_->direction eq "out", @{$function->parameters});
300
301     my $signature = "    void ${functionName}(PassRefPtr<InspectorArray> args);";
302     !$backendMethods{${signature}} || die "Duplicate function was detected for signature '$signature'.";
303     $backendMethods{${signature}} = $functionName;
304
305     my @function;
306     push(@function, "void ${backendClassName}::${functionName}(PassRefPtr<InspectorArray> args)");
307     push(@function, "{");
308     push(@function, "    DEFINE_STATIC_LOCAL(String, backendFunctionName, (\"$functionName\"));");
309     push(@function, "    long callId = 0;");
310     push(@function, "");
311
312     my $expectedParametersCount = scalar(@inArgs);
313     my $expectedParametersCountWithMethodName = scalar(@inArgs) + 1;
314     push(@function, "    if (args->length() != $expectedParametersCountWithMethodName) {");
315     push(@function, "        ASSERT_NOT_REACHED();");
316     push(@function, "        reportProtocolError(callId, backendFunctionName, formatWrongArgumentsCountMessage(args->length() - 1, $expectedParametersCount));");
317     push(@function, "        return;");
318     push(@function, "    }");
319     push(@function, "");
320
321     my $i = 1; # zero element is the method name.
322     foreach my $parameter (@inArgs) {
323         my $type = $parameter->type;
324         my $argumentType = $typeTransform{$type}->{$typeTransform{$type}->{"retVal"} ? "retVal" : "variable"};
325         push(@function, "    $argumentType " . $parameter->name . ";") if !($parameter->name eq "callId");
326         push(@function, "    if (!args->get($i)->as" . $typeTransform{$type}->{"accessorSuffix"} . "(&" . $parameter->name . ")) {");
327         push(@function, "        ASSERT_NOT_REACHED();");
328         push(@function, "        reportProtocolError(callId, backendFunctionName, formatWrongArgumentTypeMessage($i, \"" . $parameter->name . "\", \"$type\"));");
329         push(@function, "        return;");
330         push(@function, "    }");
331         push(@function, "");
332         ++$i;
333     }
334
335     my $handler = $function->signature->extendedAttributes->{"handler"} || "Controller";
336     my $handlerAccessor = $typeTransform{$handler}->{"handlerAccessor"};
337     $backendTypes{$handler} = 1;
338     push(@function, "    if (!$handlerAccessor) {");
339     push(@function, "        reportProtocolError(callId, backendFunctionName, \"Error: $handler handler is not available.\");");
340     push(@function, "        return;");
341     push(@function, "    }");
342     push(@function, "");
343
344
345     foreach (@outArgs) { # declare local variables for out arguments.
346         my $initializer = $typeTransform{$_->type}->{"defaultValue"} ? " = " . $typeTransform{$_->type}->{"defaultValue"} : "";
347         push(@function, "    " . $typeTransform{$_->type}->{"variable"} . " " . $_->name . "$initializer;");
348     }
349
350     my $async = $function->signature->extendedAttributes->{"async"};
351     my $args = join(", ", (grep($async || !($_ eq "callId"), map($_->name, @inArgs)), map("&" . $_->name, @outArgs)));
352     push(@function, "    $handlerAccessor->$functionName($args);");
353
354     # The results of function call should be transfered back to frontend (except async methods - need to fix that).
355     if (scalar(grep($_->name eq "callId", @inArgs)) && !$async) {
356         my @pushArguments = map("        arguments->push" . $typeTransform{$_->type}->{"accessorSuffix"} . "(" . $_->name . ");", @outArgs);
357         my $customResponse = $function->signature->extendedAttributes->{"customResponse"};
358         my $didFunctionName = $customResponse ? $customResponse : "did" . ucfirst($function->signature->name);
359
360         push(@function, "");
361         push(@function, "    // use InspectorFrontend as a marker of WebInspector availability");
362         push(@function, "    if (m_inspectorController->remoteInspectorFrontend()) {");
363         push(@function, "        RefPtr<InspectorArray> arguments = InspectorArray::create();");
364         push(@function, "        arguments->pushString(\"$didFunctionName\");");
365         push(@function, "        arguments->pushNumber(callId);");
366         push(@function, @pushArguments);
367         push(@function, "        m_inspectorController->inspectorClient()->sendMessageToFrontend(arguments->toJSONString());");
368         push(@function, "    }");
369     }
370     push(@function, "}");
371     push(@function, "");
372     push(@backendMethodsImpl, @function);
373 }
374
375 sub generateBackendReportProtocolError
376 {
377     my $reportProtocolError = << "EOF";
378
379 void ${backendClassName}::reportProtocolError(const long callId, const String& method, const String& errorText) const
380 {
381     RefPtr<InspectorArray> arguments = InspectorArray::create();
382     arguments->pushString("reportProtocolError");
383     arguments->pushNumber(callId);
384     arguments->pushString(method);
385     arguments->pushString(errorText);
386     m_inspectorController->inspectorClient()->sendMessageToFrontend(arguments->toJSONString());
387 }
388 EOF
389     return split("\n", $reportProtocolError);
390 }
391
392 sub generateBackendDispatcher
393 {
394     my @body;
395     my @methods = map($backendMethods{$_}, keys %backendMethods);
396     my @mapEntries = map("dispatchMap.add(\"$_\", &${backendClassName}::$_);", @methods);
397
398     push(@body, "void ${backendClassName}::dispatch(const String& message)");
399     push(@body, "{");
400     push(@body, "    typedef void (${backendClassName}::*CallHandler)(PassRefPtr<InspectorArray> args);");
401     push(@body, "    typedef HashMap<String, CallHandler> DispatchMap;");
402     push(@body, "    DEFINE_STATIC_LOCAL(DispatchMap, dispatchMap, );");
403     push(@body, "    if (dispatchMap.isEmpty()) {");
404     push(@body, map("        $_", @mapEntries));
405     push(@body, "    }");
406     push(@body, "");
407     push(@body, "    RefPtr<InspectorValue> parsedMessage = InspectorValue::parseJSON(message);");
408     push(@body, "    if (!parsedMessage) {");
409     push(@body, "        ASSERT_NOT_REACHED();");
410     push(@body, "        reportProtocolError(0, \"dispatch\", \"Error: Invalid message format. Message should be in JSON format.\");");
411     push(@body, "        return;");
412     push(@body, "    }");
413     push(@body, "");
414     push(@body, "    RefPtr<InspectorArray> messageArray = parsedMessage->asArray();");
415     push(@body, "    if (!messageArray) {");
416     push(@body, "        ASSERT_NOT_REACHED();");
417     push(@body, "        reportProtocolError(0, \"dispatch\", \"Error: Invalid message format. The message should be a JSONified array of arguments.\");");
418     push(@body, "        return;");
419     push(@body, "    }");
420     push(@body, "");
421     push(@body, "    if (!messageArray->length()) {");
422     push(@body, "        ASSERT_NOT_REACHED();");
423     push(@body, "        reportProtocolError(0, \"dispatch\", \"Error: Invalid message format. Empty message was received.\");");
424     push(@body, "        return;");
425     push(@body, "    }");
426     push(@body, "");
427     push(@body, "    String methodName;");
428     push(@body, "    if (!messageArray->get(0)->asString(&methodName)) {");
429     push(@body, "        ASSERT_NOT_REACHED();");
430     push(@body, "        reportProtocolError(0, \"dispatch\", \"Error: Invalid message format. The first element of the message should be method name.\");");
431     push(@body, "        return;");
432     push(@body, "    }");
433     push(@body, "");
434     push(@body, "    HashMap<String, CallHandler>::iterator it = dispatchMap.find(methodName);");
435     push(@body, "    if (it == dispatchMap.end()) {");
436     push(@body, "        ASSERT_NOT_REACHED();");
437     push(@body, "        reportProtocolError(0, \"dispatch\", String::format(\"Error: Invalid method name. '%s' wasn't found.\", methodName.utf8().data()));");
438     push(@body, "        return;");
439     push(@body, "    }");
440     push(@body, "");
441     push(@body, "    ((*this).*it->second)(messageArray);");
442     push(@body, "}");
443     return @body;
444 }
445
446 sub generateHeader
447 {
448     my $className = shift;
449     my $types = shift;
450     my $constructor = shift;
451     my $methods = shift;
452     my $footer = shift;
453
454     my $forwardHeaders = join("\n", sort(map("#include <" . $typeTransform{$_}->{"forwardHeader"} . ">", grep($typeTransform{$_}->{"forwardHeader"}, keys %{$types}))));
455     my $forwardDeclarations = join("\n", sort(map("class " . $typeTransform{$_}->{"forward"} . ";", grep($typeTransform{$_}->{"forward"}, keys %{$types}))));
456     my $methodsDeclarations = join("\n", keys %{$methods});
457
458     my $headerBody = << "EOF";
459 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
460 // Use of this source code is governed by a BSD-style license that can be
461 // found in the LICENSE file.
462 #ifndef ${className}_h
463 #define ${className}_h
464
465 ${forwardHeaders}
466
467 namespace $namespace {
468
469 $forwardDeclarations
470
471 class $className {
472 public:
473 $constructor
474
475 $methodsDeclarations
476
477 private:
478 $footer
479 };
480
481 } // namespace $namespace
482 #endif // !defined(${className}_h)
483
484 EOF
485     return $headerBody;
486 }
487
488 sub generateSource
489 {
490     my $className = shift;
491     my $types = shift;
492     my $methods = shift;
493
494     my @sourceContent = split("\r", $licenseTemplate);
495     push(@sourceContent, "\n#include \"config.h\"");
496     push(@sourceContent, "#include \"$className.h\"");
497     push(@sourceContent, "");
498     push(@sourceContent, "#if ENABLE(INSPECTOR)");
499     push(@sourceContent, "");
500
501     my %headers;
502     foreach my $type (keys %{$types}) {
503         $headers{"#include \"" . $typeTransform{$type}->{"header"} . "\""} = 1 if !$typeTransform{$type}->{"header"} eq  "";
504     }
505     push(@sourceContent, sort keys %headers);
506     push(@sourceContent, "");
507     push(@sourceContent, "namespace $namespace {");
508     push(@sourceContent, "");
509     push(@sourceContent, @{$methods});
510     push(@sourceContent, "");
511     push(@sourceContent, "} // namespace $namespace");
512     push(@sourceContent, "");
513     push(@sourceContent, "#endif // ENABLE(INSPECTOR)");
514     push(@sourceContent, "");
515     return @sourceContent;
516 }
517
518 sub finish
519 {
520     my $object = shift;
521
522     open(my $SOURCE, ">$outputDir/$frontendClassName.cpp") || die "Couldn't open file $outputDir/$frontendClassName.cpp";
523     print $SOURCE join("\n", generateSource($frontendClassName, \%frontendTypes, \@frontendMethodsImpl));
524     close($SOURCE);
525     undef($SOURCE);
526
527     open(my $HEADER, ">$outputHeadersDir/$frontendClassName.h") || die "Couldn't open file $outputHeadersDir/$frontendClassName.h";
528     print $HEADER generateHeader($frontendClassName, \%frontendTypes, $frontendConstructor, \%frontendMethods, $frontendFooter);
529     close($HEADER);
530     undef($HEADER);
531
532     open($SOURCE, ">$outputDir/$backendClassName.cpp") || die "Couldn't open file $outputDir/$backendClassName.cpp";
533     print $SOURCE join("\n", generateSource($backendClassName, \%backendTypes, \@backendMethodsImpl));
534     close($SOURCE);
535     undef($SOURCE);
536
537     open($HEADER, ">$outputHeadersDir/$backendClassName.h") || die "Couldn't open file $outputHeadersDir/$backendClassName.h";
538     print $HEADER join("\n", generateHeader($backendClassName, \%backendTypes, $backendConstructor, \%backendMethods, $backendFooter));
539     close($HEADER);
540     undef($HEADER);
541 }
542
543 1;