2010-07-23 Pavel Feldman <pfeldman@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{"InspectorBackend"} = {
18     "forward" => "InspectorBackend",
19     "header" => "InspectorBackend.h",
20 };
21 $typeTransform{"PassRefPtr"} = {
22     "forwardHeader" => "wtf/PassRefPtr.h",
23 };
24 $typeTransform{"Object"} = {
25     "param" => "PassRefPtr<InspectorObject>",
26     "retVal" => "PassRefPtr<InspectorObject>",
27     "forward" => "InspectorObject",
28     "header" => "InspectorValues.h",
29     "accessorSuffix" => ""
30 };
31 $typeTransform{"Array"} = {
32     "param" => "PassRefPtr<InspectorArray>",
33     "retVal" => "PassRefPtr<InspectorArray>",
34     "forward" => "InspectorArray",
35     "header" => "InspectorValues.h",
36     "accessorSuffix" => ""
37 };
38 $typeTransform{"Value"} = {
39     "param" => "PassRefPtr<InspectorValue>",
40     "retVal" => "PassRefPtr<InspectorValue>",
41     "forward" => "InspectorValue",
42     "header" => "InspectorValues.h",
43     "accessorSuffix" => ""
44 };
45 $typeTransform{"String"} = {
46     "param" => "const String&",
47     "retVal" => "String",
48     "forward" => "String",
49     "header" => "PlatformString.h",
50     "accessorSuffix" => "String"
51 };
52 $typeTransform{"long"} = {
53     "param" => "long",
54     "retVal" => "long",
55     "forward" => "",
56     "header" => "",
57     "accessorSuffix" => "Number"
58 };
59 $typeTransform{"int"} = {
60     "param" => "int",
61     "retVal" => "int",
62     "forward" => "",
63     "header" => "",
64     "accessorSuffix" => "Number",
65 };
66 $typeTransform{"unsigned long"} = {
67     "param" => "unsigned long",
68     "retVal" => "unsigned long",
69     "forward" => "",
70     "header" => "",
71     "accessorSuffix" => "Number"
72 };
73 $typeTransform{"boolean"} = {
74     "param" => "bool",
75     "retVal"=> "bool",
76     "forward" => "",
77     "header" => "",
78     "accessorSuffix" => "Bool"
79 };
80 $typeTransform{"void"} = {
81     "retVal" => "void",
82     "forward" => "",
83     "header" => ""
84 };
85
86 # Default License Templates
87
88 my $licenseTemplate = << "EOF";
89 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
90 // Use of this source code is governed by a BSD-style license that can be
91 // found in the LICENSE file.
92 EOF
93
94 my $codeGenerator;
95 my $outputDir;
96 my $outputHeadersDir;
97 my $writeDependencies;
98 my $verbose;
99
100 my $namespace;
101
102 my $backendClassName;
103 my %backendTypes;
104 my %backendMethods;
105 my @backendMethodsImpl;
106 my $backendConstructor;
107 my $backendFooter;
108
109 my $frontendClassName;
110 my %frontendTypes;
111 my %frontendMethods;
112 my @frontendMethodsImpl;
113 my $frontendConstructor;
114 my $frontendFooter;
115
116 my $callId = new domSignature(); # it is just structure for describing parameters from IDLStructure.pm.
117 $callId->type("long");
118 $callId->name("callId");
119
120 # Default constructor
121 sub new
122 {
123     my $object = shift;
124     my $reference = { };
125
126     $codeGenerator = shift;
127     $outputDir = shift;
128     $outputHeadersDir = shift;
129     shift; # $useLayerOnTop
130     shift; # $preprocessor
131     $writeDependencies = shift;
132     $verbose = shift;
133
134     bless($reference, $object);
135     return $reference;
136 }
137
138 # Params: 'idlDocument' struct
139 sub GenerateModule
140 {
141     my $object = shift;
142     my $dataNode = shift;
143
144     $namespace = $dataNode->module;
145     $namespace =~ s/core/WebCore/;
146 }
147
148 # Params: 'idlDocument' struct
149 sub GenerateInterface
150 {
151     my $object = shift;
152     my $interface = shift;
153     my $defines = shift;
154
155     my $className = $interface->name;
156
157     $frontendClassName = "Remote" . $className . "Frontend";
158     $frontendConstructor = "    ${frontendClassName}(InspectorClient* inspectorClient) : m_inspectorClient(inspectorClient) { }";
159     $frontendFooter = "    InspectorClient* m_inspectorClient;";
160     $frontendTypes{"String"} = 1;
161     $frontendTypes{"InspectorClient"} = 1;
162     $frontendTypes{"PassRefPtr"} = 1;
163
164     $backendClassName = $className . "BackendDispatcher";
165     my @backendHead;
166     push(@backendHead, "    ${backendClassName}(InspectorBackend* inspectorBackend) : m_inspectorBackend(inspectorBackend) { }");
167     push(@backendHead, "    void dispatch(const String& message, String* exception);");
168     push(@backendHead, "private:");
169     $backendConstructor = join("\n", @backendHead);
170     $backendFooter = "    InspectorBackend* m_inspectorBackend;";
171     $backendTypes{"InspectorBackend"} = 1;
172     $backendTypes{"PassRefPtr"} = 1;
173     $backendTypes{"Array"} = 1;
174
175     generateBackendPrivateFunctions();
176     generateFunctions($interface);
177 }
178
179 sub generateFunctions
180 {
181     my $interface = shift;
182
183     foreach my $function (@{$interface->functions}) {
184         generateFrontendFunction($function);
185         generateBackendFunction($function);
186     }
187     push(@backendMethodsImpl, generateBackendDispatcher());
188 }
189
190 sub generateFrontendFunction
191 {
192     my $function = shift;
193
194     my $functionName;
195     my $notify = $function->signature->extendedAttributes->{"notify"};
196     if ($notify) {
197         $functionName = $function->signature->name;
198     } else {
199         my $customResponse = $function->signature->extendedAttributes->{"customResponse"};
200         $functionName = $customResponse ? $customResponse : "did" . ucfirst($function->signature->name);
201     }
202
203     my @argsFiltered = grep($_->direction eq "out", @{$function->parameters}); # just keep only out parameters for frontend interface.
204     unshift(@argsFiltered, $callId) if !$notify; # Add callId as the first argument for all frontend did* methods.
205     map($frontendTypes{$_->type} = 1, @argsFiltered); # register required types.
206     my $arguments = join(", ", map($typeTransform{$_->type}->{"param"} . " " . $_->name, @argsFiltered)); # prepare arguments for function signature.
207     my @pushArguments = map("    arguments->push" . $typeTransform{$_->type}->{"accessorSuffix"} . "(" . $_->name . ");", @argsFiltered);
208
209     my $signature = "    void ${functionName}(${arguments});";
210     if (!$frontendMethods{${signature}}) {
211         $frontendMethods{${signature}} = 1;
212
213         my @function;
214         push(@function, "void ${frontendClassName}::${functionName}(${arguments})");
215         push(@function, "{");
216         push(@function, "    RefPtr<InspectorArray> arguments = InspectorArray::create();");
217         push(@function, "    arguments->pushString(\"$functionName\");");
218         push(@function, @pushArguments);
219         push(@function, "    m_inspectorClient->sendMessageToFrontend(arguments->toJSONString());");
220         push(@function, "}");
221         push(@function, "");
222         push(@frontendMethodsImpl, @function);
223     }
224 }
225
226 sub generateBackendPrivateFunctions
227 {
228     my $privateFunctions = << "EOF";
229 static String formatWrongArgumentsCountMessage(unsigned expected, unsigned actual)
230 {
231     return String::format(\"Wrong number of parameters: %d (expected: %d)\", actual, expected);
232 }
233
234 static String formatWrongArgumentTypeMessage(unsigned position, const char* name, const char* expectedType)
235 {
236     return String::format(\"Failed to convert parameter %d (%s) to %s\", position, name, expectedType);
237 }
238 EOF
239     push(@backendMethodsImpl, $privateFunctions);
240 }
241
242 sub generateBackendFunction
243 {
244     my $function = shift;
245     return if $function->signature->extendedAttributes->{"notify"};
246
247     my $functionName = $function->signature->name;
248
249     my @argsFiltered = grep($_->direction eq "in", @{$function->parameters});
250     map($backendTypes{$_->type} = 1, @argsFiltered); # register required types
251     my $arguments = join(", ", map($typeTransform{$_->type}->{"param"} . " " . $_->name, @argsFiltered));
252
253     my $signature = "    void ${functionName}(PassRefPtr<InspectorArray> args, String* exception);";
254     !$backendMethods{${signature}} || die "Duplicate function was detected for signature '$signature'.";
255     $backendMethods{${signature}} = $functionName;
256
257     my @function;
258     push(@function, "void ${backendClassName}::${functionName}(PassRefPtr<InspectorArray> args, String* exception)");
259     push(@function, "{");
260     my $i = 1; # zero element is the method name.
261     my $expectedParametersCount = scalar(@argsFiltered);
262     my $expectedParametersCountWithMethodName = scalar(@argsFiltered) + 1;
263     push(@function, "    if (args->length() != $expectedParametersCountWithMethodName) {");
264     push(@function, "        *exception = formatWrongArgumentsCountMessage(args->length() - 1, $expectedParametersCount);");
265     push(@function, "        ASSERT_NOT_REACHED();");
266     push(@function, "        return;");
267     push(@function, "    }");
268
269     foreach my $parameter (@argsFiltered) {
270         my $parameterType = $parameter->type;
271         push(@function, "    " . $typeTransform{$parameterType}->{"retVal"} . " " . $parameter->name . ";");
272         push(@function, "    if (!args->get(" . $i . ")->as" . $typeTransform{$parameterType}->{"accessorSuffix"} . "(&" . $parameter->name . ")) {");
273         push(@function, "        *exception = formatWrongArgumentTypeMessage($i, \"" . $parameter->name . "\", \"$parameterType\");");
274         push(@function, "        ASSERT_NOT_REACHED();");
275         push(@function, "        return;");
276         push(@function, "    }");
277         ++$i;
278     }
279     push(@function, "    m_inspectorBackend->$functionName(" . join(", ", map($_->name, @argsFiltered)) . ");");
280     push(@function, "}");
281     push(@function, "");
282     push(@backendMethodsImpl, @function);
283 }
284
285 sub generateBackendDispatcher
286 {
287     my @body;
288     my @methods = map($backendMethods{$_}, keys %backendMethods);
289     my @mapEntries = map("dispatchMap.add(\"$_\", &${backendClassName}::$_);", @methods);
290
291     push(@body, "void ${backendClassName}::dispatch(const String& message, String* exception)");
292     push(@body, "{");
293     push(@body, "    typedef void (${backendClassName}::*CallHandler)(PassRefPtr<InspectorArray> args, String* exception);");
294     push(@body, "    typedef HashMap<String, CallHandler> DispatchMap;");
295     push(@body, "    DEFINE_STATIC_LOCAL(DispatchMap, dispatchMap, );");
296     push(@body, "    if (dispatchMap.isEmpty()) {");
297     push(@body, map("        $_", @mapEntries));
298     push(@body, "    }");
299     push(@body, "");
300     push(@body, "    RefPtr<InspectorValue> parsedMessage = InspectorValue::parseJSON(message);");
301     push(@body, "    if (!parsedMessage) {");
302     push(@body, "        ASSERT_NOT_REACHED();");
303     push(@body, "        *exception = \"Error: Invalid message format. Message should be in JSON format.\";");
304     push(@body, "        return;");
305     push(@body, "    }");
306     push(@body, "");
307     push(@body, "    RefPtr<InspectorArray> messageArray = parsedMessage->asArray();");
308     push(@body, "    if (!messageArray) {");
309     push(@body, "        ASSERT_NOT_REACHED();");
310     push(@body, "        *exception = \"Error: Invalid message format. The message should be a JSONified array of arguments.\";");
311     push(@body, "        return;");
312     push(@body, "    }");
313     push(@body, "");
314     push(@body, "    if (!messageArray->length()) {");
315     push(@body, "        ASSERT_NOT_REACHED();");
316     push(@body, "        *exception = \"Error: Invalid message format. Empty message was received.\";");
317     push(@body, "        return;");
318     push(@body, "    }");
319     push(@body, "");
320     push(@body, "    String methodName;");
321     push(@body, "    if (!messageArray->get(0)->asString(&methodName)) {");
322     push(@body, "        ASSERT_NOT_REACHED();");
323     push(@body, "        *exception = \"Error: Invalid message format. The first element of the message should be method name.\";");
324     push(@body, "        return;");
325     push(@body, "    }");
326     push(@body, "");
327     push(@body, "    HashMap<String, CallHandler>::iterator it = dispatchMap.find(methodName);");
328     push(@body, "    if (it == dispatchMap.end()) {");
329     push(@body, "        ASSERT_NOT_REACHED();");
330     push(@body, "        *exception = String::format(\"Error: Invalid method name. '%s' wasn't found.\", methodName.utf8().data());");
331     push(@body, "        return;");
332     push(@body, "    }");
333     push(@body, "");
334     push(@body, "    ((*this).*it->second)(messageArray, exception);");
335     push(@body, "}");
336     return @body;
337 }
338
339 sub generateHeader
340 {
341     my $className = shift;
342     my $types = shift;
343     my $constructor = shift;
344     my $methods = shift;
345     my $footer = shift;
346
347     my $forwardHeaders = join("\n", sort(map("#include <" . $typeTransform{$_}->{"forwardHeader"} . ">", grep($typeTransform{$_}->{"forwardHeader"}, keys %{$types}))));
348     my $forwardDeclarations = join("\n", sort(map("class " . $typeTransform{$_}->{"forward"} . ";", grep($typeTransform{$_}->{"forward"}, keys %{$types}))));
349     my $methodsDeclarations = join("\n", keys %{$methods});
350
351     my $headerBody = << "EOF";
352 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
353 // Use of this source code is governed by a BSD-style license that can be
354 // found in the LICENSE file.
355 #ifndef ${className}_h
356 #define ${className}_h
357
358 ${forwardHeaders}
359
360 namespace $namespace {
361
362 $forwardDeclarations
363
364 class $className {
365 public:
366 $constructor
367
368 $methodsDeclarations
369
370 private:
371 $footer
372 };
373
374 } // namespace $namespace
375 #endif // !defined(${className}_h)
376
377 EOF
378     return $headerBody;
379 }
380
381 sub generateSource
382 {
383     my $className = shift;
384     my $types = shift;
385     my $methods = shift;
386
387     my @sourceContent = split("\r", $licenseTemplate);
388     push(@sourceContent, "\n#include \"config.h\"");
389     push(@sourceContent, "#include \"$className.h\"");
390     push(@sourceContent, "");
391     push(@sourceContent, "#if ENABLE(INSPECTOR)");
392     push(@sourceContent, "");
393
394     my %headers;
395     foreach my $type (keys %{$types}) {
396         $headers{"#include \"" . $typeTransform{$type}->{"header"} . "\""} = 1 if !$typeTransform{$type}->{"header"} eq  "";
397     }
398     push(@sourceContent, sort keys %headers);
399     push(@sourceContent, "");
400     push(@sourceContent, "namespace $namespace {");
401     push(@sourceContent, "");
402     push(@sourceContent, @{$methods});
403     push(@sourceContent, "");
404     push(@sourceContent, "} // namespace $namespace");
405     push(@sourceContent, "");
406     push(@sourceContent, "#endif // ENABLE(INSPECTOR)");
407     push(@sourceContent, "");
408     return @sourceContent;
409 }
410
411 sub finish
412 {
413     my $object = shift;
414
415     open(my $SOURCE, ">$outputDir/$frontendClassName.cpp") || die "Couldn't open file $outputDir/$frontendClassName.cpp";
416     print $SOURCE join("\n", generateSource($frontendClassName, \%frontendTypes, \@frontendMethodsImpl));
417     close($SOURCE);
418     undef($SOURCE);
419
420     open(my $HEADER, ">$outputHeadersDir/$frontendClassName.h") || die "Couldn't open file $outputHeadersDir/$frontendClassName.h";
421     print $HEADER generateHeader($frontendClassName, \%frontendTypes, $frontendConstructor, \%frontendMethods, $frontendFooter);
422     close($HEADER);
423     undef($HEADER);
424
425     open($SOURCE, ">$outputDir/$backendClassName.cpp") || die "Couldn't open file $outputDir/$backendClassName.cpp";
426     print $SOURCE join("\n", generateSource($backendClassName, \%backendTypes, \@backendMethodsImpl));
427     close($SOURCE);
428     undef($SOURCE);
429
430     open($HEADER, ">$outputHeadersDir/$backendClassName.h") || die "Couldn't open file $outputHeadersDir/$backendClassName.h";
431     print $HEADER join("\n", generateHeader($backendClassName, \%backendTypes, $backendConstructor, \%backendMethods, $backendFooter));
432     close($HEADER);
433     undef($HEADER);
434 }
435
436 1;