Add SW IDLs and stub out basic functionality.
[WebKit-https.git] / Source / WebCore / bindings / scripts / preprocess-idls.pl
1 #!/usr/bin/perl -w
2 #
3 # Copyright (C) 2011 Google Inc.  All rights reserved.
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Library General Public
7 # License as published by the Free Software Foundation; either
8 # version 2 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Library General Public License for more details.
14 #
15 # You should have received a copy of the GNU Library General Public License
16 # along with this library; see the file COPYING.LIB.  If not, write to
17 # the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 # Boston, MA 02110-1301, USA.
19 #
20
21 use strict;
22 use FindBin;
23 use lib $FindBin::Bin;
24
25 use File::Basename;
26 use Getopt::Long;
27 use Cwd;
28 use Config;
29
30 my $defines;
31 my $preprocessor;
32 my $idlFilesList;
33 my $supplementalDependencyFile;
34 my $windowConstructorsFile;
35 my $workerGlobalScopeConstructorsFile;
36 my $dedicatedWorkerGlobalScopeConstructorsFile;
37 my $serviceWorkerGlobalScopeConstructorsFile;
38 my $supplementalMakefileDeps;
39
40 GetOptions('defines=s' => \$defines,
41            'preprocessor=s' => \$preprocessor,
42            'idlFilesList=s' => \$idlFilesList,
43            'supplementalDependencyFile=s' => \$supplementalDependencyFile,
44            'windowConstructorsFile=s' => \$windowConstructorsFile,
45            'workerGlobalScopeConstructorsFile=s' => \$workerGlobalScopeConstructorsFile,
46            'dedicatedWorkerGlobalScopeConstructorsFile=s' => \$dedicatedWorkerGlobalScopeConstructorsFile,
47            'serviceWorkerGlobalScopeConstructorsFile=s' => \$serviceWorkerGlobalScopeConstructorsFile,
48            'supplementalMakefileDeps=s' => \$supplementalMakefileDeps);
49
50 die('Must specify #define macros using --defines.') unless defined($defines);
51 die('Must specify an output file using --supplementalDependencyFile.') unless defined($supplementalDependencyFile);
52 die('Must specify an output file using --windowConstructorsFile.') unless defined($windowConstructorsFile);
53 die('Must specify an output file using --workerGlobalScopeConstructorsFile.') unless defined($workerGlobalScopeConstructorsFile);
54 die('Must specify an output file using --dedicatedWorkerGlobalScopeConstructorsFile.') unless defined($dedicatedWorkerGlobalScopeConstructorsFile);
55 die('Must specify an output file using --serviceWorkerGlobalScopeConstructorsFile.') unless defined($serviceWorkerGlobalScopeConstructorsFile);
56 die('Must specify the file listing all IDLs using --idlFilesList.') unless defined($idlFilesList);
57
58 $supplementalDependencyFile = CygwinPathIfNeeded($supplementalDependencyFile);
59 $windowConstructorsFile = CygwinPathIfNeeded($windowConstructorsFile);
60 $workerGlobalScopeConstructorsFile = CygwinPathIfNeeded($workerGlobalScopeConstructorsFile);
61 $dedicatedWorkerGlobalScopeConstructorsFile = CygwinPathIfNeeded($dedicatedWorkerGlobalScopeConstructorsFile);
62 $serviceWorkerGlobalScopeConstructorsFile = CygwinPathIfNeeded($serviceWorkerGlobalScopeConstructorsFile);
63 $supplementalMakefileDeps = CygwinPathIfNeeded($supplementalMakefileDeps);
64
65 open FH, "< $idlFilesList" or die "Cannot open $idlFilesList\n";
66 my @idlFilesIn = <FH>;
67 chomp(@idlFilesIn);
68 my @idlFiles = ();
69 foreach (@idlFilesIn) {
70     push @idlFiles, CygwinPathIfNeeded($_);
71 }
72 close FH;
73
74 my %interfaceNameToIdlFile;
75 my %idlFileToInterfaceName;
76 my %supplementalDependencies;
77 my %supplementals;
78 my $windowConstructorsCode = "";
79 my $workerGlobalScopeConstructorsCode = "";
80 my $dedicatedWorkerGlobalScopeConstructorsCode = "";
81 my $serviceWorkerGlobalScopeConstructorsCode = "";
82
83 # Get rid of duplicates in idlFiles array.
84 my %idlFileHash = map { $_, 1 } @idlFiles;
85
86 # Populate $idlFileToInterfaceName and $interfaceNameToIdlFile.
87 foreach my $idlFile (sort keys %idlFileHash) {
88     my $fullPath = Cwd::realpath($idlFile);
89     my $interfaceName = fileparse(basename($idlFile), ".idl");
90     $idlFileToInterfaceName{$fullPath} = $interfaceName;
91     $interfaceNameToIdlFile{$interfaceName} = $fullPath;
92 }
93
94 # Parse all IDL files.
95 foreach my $idlFile (sort keys %idlFileHash) {
96     my $fullPath = Cwd::realpath($idlFile);
97     my $idlFileContents = getFileContents($fullPath);
98     # Handle partial interfaces.
99     my $partialInterfaceName = getPartialInterfaceNameFromIDL($idlFileContents);
100     if ($partialInterfaceName) {
101         $supplementalDependencies{$fullPath} = [$partialInterfaceName];
102         next;
103     }
104
105     $supplementals{$fullPath} = [];
106
107     # Skip if the IDL file does not contain an interface, a callback interface or an exception.
108     # The IDL may contain a dictionary.
109     next unless containsInterfaceOrExceptionFromIDL($idlFileContents);
110
111     my $interfaceName = fileparse(basename($idlFile), ".idl");
112     # Handle implements statements.
113     my $implementedInterfaces = getImplementedInterfacesFromIDL($idlFileContents, $interfaceName);
114     foreach my $implementedInterface (@{$implementedInterfaces}) {
115         my $implementedIdlFile = $interfaceNameToIdlFile{$implementedInterface};
116         die "Could not find a the IDL file where the following implemented interface is defined: $implementedInterface" unless $implementedIdlFile;
117         if ($supplementalDependencies{$implementedIdlFile}) {
118             push(@{$supplementalDependencies{$implementedIdlFile}}, $interfaceName);
119         } else {
120             $supplementalDependencies{$implementedIdlFile} = [$interfaceName];
121         }
122     }
123
124     # For every interface that is exposed in a given ECMAScript global environment and:
125     # - is a callback interface that has constants declared on it, or
126     # - is a non-callback interface that is not declared with the [NoInterfaceObject] extended attribute, a corresponding
127     #   property must exist on the ECMAScript environment's global object.
128     # See https://heycam.github.io/webidl/#es-interfaces
129     my $extendedAttributes = getInterfaceExtendedAttributesFromIDL($idlFileContents);
130     unless ($extendedAttributes->{"NoInterfaceObject"}) {
131         if (!isCallbackInterfaceFromIDL($idlFileContents) || interfaceHasConstantAttribute($idlFileContents)) {
132             my $exposedAttribute = $extendedAttributes->{"Exposed"} || "Window";
133             $exposedAttribute = substr($exposedAttribute, 1, -1) if substr($exposedAttribute, 0, 1) eq "(";
134             my @globalContexts = split(",", $exposedAttribute);
135             my ($attributeCode, $windowAliases) = GenerateConstructorAttributes($interfaceName, $extendedAttributes);
136             foreach my $globalContext (@globalContexts) {
137                 if ($globalContext eq "Window") {
138                     $windowConstructorsCode .= $attributeCode;
139                 } elsif ($globalContext eq "Worker") {
140                     $workerGlobalScopeConstructorsCode .= $attributeCode;
141                 } elsif ($globalContext eq "DedicatedWorker") {
142                     $dedicatedWorkerGlobalScopeConstructorsCode .= $attributeCode;
143                 } elsif ($globalContext eq "ServiceWorker") {
144                     $serviceWorkerGlobalScopeConstructorsCode .= $attributeCode;
145                 } else {
146                     die "Unsupported global context '$globalContext' used in [Exposed] at $idlFile";
147                 }
148             }
149             $windowConstructorsCode .= $windowAliases if $windowAliases;
150         }
151     }
152 }
153
154 # Generate partial interfaces for Constructors.
155 GeneratePartialInterface("DOMWindow", $windowConstructorsCode, $windowConstructorsFile);
156 GeneratePartialInterface("WorkerGlobalScope", $workerGlobalScopeConstructorsCode, $workerGlobalScopeConstructorsFile);
157 GeneratePartialInterface("DedicatedWorkerGlobalScope", $dedicatedWorkerGlobalScopeConstructorsCode, $dedicatedWorkerGlobalScopeConstructorsFile);
158 GeneratePartialInterface("ServiceWorker", $serviceWorkerGlobalScopeConstructorsCode, $serviceWorkerGlobalScopeConstructorsFile);
159
160 # Resolves partial interfaces and implements dependencies.
161 foreach my $idlFile (sort keys %supplementalDependencies) {
162     my $baseFiles = $supplementalDependencies{$idlFile};
163     foreach my $baseFile (@{$baseFiles}) {
164         my $targetIdlFile = $interfaceNameToIdlFile{$baseFile};
165         push(@{$supplementals{$targetIdlFile}}, $idlFile);
166     }
167     delete $supplementals{$idlFile};
168 }
169
170 # Outputs the dependency.
171 # The format of a supplemental dependency file:
172 #
173 # DOMWindow.idl P.idl Q.idl R.idl
174 # Document.idl S.idl
175 # Event.idl
176 # ...
177 #
178 # The above indicates that DOMWindow.idl is supplemented by P.idl, Q.idl and R.idl,
179 # Document.idl is supplemented by S.idl, and Event.idl is supplemented by no IDLs.
180 # The IDL that supplements another IDL (e.g. P.idl) never appears in the dependency file.
181 my $dependencies = "";
182 foreach my $idlFile (sort keys %supplementals) {
183     $dependencies .= "$idlFile @{$supplementals{$idlFile}}\n";
184 }
185 WriteFileIfChanged($supplementalDependencyFile, $dependencies);
186
187 if ($supplementalMakefileDeps) {
188     my $makefileDeps = "";
189     foreach my $idlFile (sort keys %supplementals) {
190         my $basename = $idlFileToInterfaceName{$idlFile};
191
192         my @dependencies = map { basename($_) } @{$supplementals{$idlFile}};
193
194         $makefileDeps .= "JS${basename}.h: @{dependencies}\n";
195         $makefileDeps .= "DOM${basename}.h: @{dependencies}\n";
196         $makefileDeps .= "WebDOM${basename}.h: @{dependencies}\n";
197         foreach my $dependency (@dependencies) {
198             $makefileDeps .= "${dependency}:\n";
199         }
200     }
201
202     WriteFileIfChanged($supplementalMakefileDeps, $makefileDeps);
203 }
204
205 my $cygwinPathAdded;
206 sub CygwinPathIfNeeded
207 {
208     my $path = shift;
209     if ($path && $Config{osname} eq "cygwin") {
210         if (not $cygwinPathAdded) {
211             $ENV{PATH} = "$ENV{PATH}:/cygdrive/c/cygwin/bin";
212             $cygwinPathAdded = 1; 
213         }
214         chomp($path = `cygpath -u '$path'`);
215         $path =~ s/[\r\n]//;
216     }
217     return $path;
218 }
219
220 sub WriteFileIfChanged
221 {
222     my $fileName = shift;
223     my $contents = shift;
224
225     if (-f $fileName) {
226         open FH, "<", $fileName or die "Couldn't open $fileName: $!\n";
227         my @lines = <FH>;
228         my $oldContents = join "", @lines;
229         close FH;
230         return if $contents eq $oldContents;
231     }
232     open FH, ">", $fileName or die "Couldn't open $fileName: $!\n";
233     print FH $contents;
234     close FH;
235 }
236
237 sub GeneratePartialInterface
238 {
239     my $interfaceName = shift;
240     my $attributesCode = shift;
241     my $destinationFile = shift;
242
243     my $contents = "partial interface ${interfaceName} {\n$attributesCode};\n";
244     WriteFileIfChanged($destinationFile, $contents);
245
246     my $fullPath = Cwd::realpath($destinationFile);
247     $supplementalDependencies{$fullPath} = [$interfaceName] if $interfaceNameToIdlFile{$interfaceName};
248 }
249
250 sub GenerateConstructorAttributes
251 {
252     my $interfaceName = shift;
253     my $extendedAttributes = shift;
254
255     my $code = "    ";
256     my @extendedAttributesList;
257     foreach my $attributeName (sort keys %{$extendedAttributes}) {
258       next unless ($attributeName eq "Conditional" || $attributeName eq "EnabledAtRuntime" || $attributeName eq "EnabledForWorld"
259         || $attributeName eq "EnabledBySetting" || $attributeName eq "SecureContext" || $attributeName eq "PrivateIdentifier"
260         || $attributeName eq "PublicIdentifier");
261       my $extendedAttribute = $attributeName;
262       $extendedAttribute .= "=" . $extendedAttributes->{$attributeName} unless $extendedAttributes->{$attributeName} eq "VALUE_IS_MISSING";
263       push(@extendedAttributesList, $extendedAttribute);
264     }
265     $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
266
267     my $originalInterfaceName = $interfaceName;
268     $interfaceName = $extendedAttributes->{"InterfaceName"} if $extendedAttributes->{"InterfaceName"};
269     $code .= "attribute " . $originalInterfaceName . "Constructor $interfaceName;\n";
270
271     # In addition to the regular property, for every [NamedConstructor] extended attribute on an interface,
272     # a corresponding property MUST exist on the ECMAScript global object.
273     if ($extendedAttributes->{"NamedConstructor"}) {
274         my $constructorName = $extendedAttributes->{"NamedConstructor"};
275         $constructorName =~ s/\(.*//g; # Extract function name.
276         $code .= "    ";
277         $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
278         $code .= "attribute " . $originalInterfaceName . "NamedConstructor $constructorName;\n";
279     }
280     
281     my $windowAliasesCode;
282     if ($extendedAttributes->{"LegacyWindowAlias"}) {
283         my $attributeValue = $extendedAttributes->{"LegacyWindowAlias"};
284         $attributeValue = substr($attributeValue, 1, -1) if substr($attributeValue, 0, 1) eq "(";
285         my @windowAliases = split(",", $attributeValue);
286         foreach my $windowAlias (@windowAliases) {
287             $windowAliasesCode .= "    ";
288             $windowAliasesCode .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
289             $windowAliasesCode .= "attribute " . $originalInterfaceName . "Constructor $windowAlias; // Legacy Window alias.\n";
290         }
291     }
292     
293     return ($code, $windowAliasesCode);
294 }
295
296 sub getFileContents
297 {
298     my $idlFile = shift;
299
300     open FILE, "<", $idlFile;
301     my @lines = <FILE>;
302     close FILE;
303
304     # Filter out preprocessor lines.
305     @lines = grep(!/^\s*#/, @lines);
306
307     return join('', @lines);
308 }
309
310 sub getPartialInterfaceNameFromIDL
311 {
312     my $fileContents = shift;
313
314     if ($fileContents =~ /partial\s+interface\s+(\w+)/gs) {
315         return $1;
316     }
317 }
318
319 # identifier-A implements identifier-B;
320 # http://www.w3.org/TR/WebIDL/#idl-implements-statements
321 sub getImplementedInterfacesFromIDL
322 {
323     my $fileContents = shift;
324     my $interfaceName = shift;
325
326     my @implementedInterfaces = ();
327     while ($fileContents =~ /^\s*(\w+)\s+implements\s+(\w+)\s*;/mg) {
328         die "Identifier on the left of the 'implements' statement should be $interfaceName in $interfaceName.idl, but found $1" if $1 ne $interfaceName;
329         push(@implementedInterfaces, $2);
330     }
331     return \@implementedInterfaces
332 }
333
334 sub isCallbackInterfaceFromIDL
335 {
336     my $fileContents = shift;
337     return ($fileContents =~ /callback\s+interface\s+\w+/gs);
338 }
339
340 sub containsInterfaceOrExceptionFromIDL
341 {
342     my $fileContents = shift;
343
344     return 1 if $fileContents =~ /\bcallback\s+interface\s+\w+/gs;
345     return 1 if $fileContents =~ /\binterface\s+\w+/gs;
346     return 1 if $fileContents =~ /\bexception\s+\w+/gs;
347     return 0;
348 }
349
350 sub trim
351 {
352     my $string = shift;
353     $string =~ s/^\s+|\s+$//g;
354     return $string;
355 }
356
357 sub getInterfaceExtendedAttributesFromIDL
358 {
359     my $fileContents = shift;
360
361     my $extendedAttributes = {};
362
363     # Remove comments from fileContents before processing.
364     # FIX: Preference to use Regex::Common::comment, however it is not available on
365     # all build systems.
366     $fileContents =~ s/(?:(?:(?:\/\/)(?:[^\n]*)(?:\n))|(?:(?:\/\*)(?:(?:[^\*]+|\*(?!\/))*)(?:\*\/)))//g;
367
368     if ($fileContents =~ /\[(.*)\]\s+(callback interface|interface|exception)\s+(\w+)/gs) {
369         my @parts = split(m/,(?![^()]*\))/, $1);
370         foreach my $part (@parts) {
371             my @keyValue = split('=', $part);
372             my $key = trim($keyValue[0]);
373             next unless length($key);
374             my $value = "VALUE_IS_MISSING";
375             $value = trim($keyValue[1]) if @keyValue > 1;
376             $extendedAttributes->{$key} = $value;
377         }
378     }
379
380     return $extendedAttributes;
381 }
382
383 sub interfaceHasConstantAttribute
384 {
385     my $fileContents = shift;
386
387     return $fileContents =~ /\s+const[\s\w]+=\s+[\w]+;/gs;
388 }