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
21use strict;
22
23use File::Basename;
24use Getopt::Long;
25use Cwd;
26
27my $defines;
28my $preprocessor;
29my $idlFilesList;
30my $supplementalDependencyFile;
31my $windowConstructorsFile;
32my $workerGlobalScopeConstructorsFile;
33my $sharedWorkerGlobalScopeConstructorsFile;
34my $dedicatedWorkerGlobalScopeConstructorsFile;
35my $supplementalMakefileDeps;
36
37GetOptions('defines=s' => \$defines,
38           'preprocessor=s' => \$preprocessor,
39           'idlFilesList=s' => \$idlFilesList,
40           'supplementalDependencyFile=s' => \$supplementalDependencyFile,
41           'windowConstructorsFile=s' => \$windowConstructorsFile,
42           'workerGlobalScopeConstructorsFile=s' => \$workerGlobalScopeConstructorsFile,
43           'sharedWorkerGlobalScopeConstructorsFile=s' => \$sharedWorkerGlobalScopeConstructorsFile,
44           'dedicatedWorkerGlobalScopeConstructorsFile=s' => \$dedicatedWorkerGlobalScopeConstructorsFile,
45           'supplementalMakefileDeps=s' => \$supplementalMakefileDeps);
46
47die('Must specify #define macros using --defines.') unless defined($defines);
48die('Must specify an output file using --supplementalDependencyFile.') unless defined($supplementalDependencyFile);
49die('Must specify an output file using --windowConstructorsFile.') unless defined($windowConstructorsFile);
50die('Must specify an output file using --workerGlobalScopeConstructorsFile.') unless defined($workerGlobalScopeConstructorsFile);
51die('Must specify an output file using --sharedWorkerGlobalScopeConstructorsFile.') unless defined($sharedWorkerGlobalScopeConstructorsFile);
52die('Must specify an output file using --dedicatedWorkerGlobalScopeConstructorsFile.') unless defined($dedicatedWorkerGlobalScopeConstructorsFile);
53die('Must specify the file listing all IDLs using --idlFilesList.') unless defined($idlFilesList);
54
55open FH, "< $idlFilesList" or die "Cannot open $idlFilesList\n";
56my @idlFiles = <FH>;
57chomp(@idlFiles);
58close FH;
59
60my %interfaceNameToIdlFile;
61my %idlFileToInterfaceName;
62my %supplementalDependencies;
63my %supplementals;
64my $windowConstructorsCode = "";
65my $workerGlobalScopeConstructorsCode = "";
66my $sharedWorkerGlobalScopeConstructorsCode = "";
67my $dedicatedWorkerGlobalScopeConstructorsCode = "";
68
69# Get rid of duplicates in idlFiles array.
70my %idlFileHash = map { $_, 1 } @idlFiles;
71
72# Populate $idlFileToInterfaceName and $interfaceNameToIdlFile.
73foreach my $idlFile (keys %idlFileHash) {
74    my $fullPath = Cwd::realpath($idlFile);
75    my $interfaceName = fileparse(basename($idlFile), ".idl");
76    $idlFileToInterfaceName{$fullPath} = $interfaceName;
77    $interfaceNameToIdlFile{$interfaceName} = $fullPath;
78}
79
80# Parse all IDL files.
81foreach my $idlFile (sort keys %idlFileHash) {
82    my $fullPath = Cwd::realpath($idlFile);
83    my $idlFileContents = getFileContents($fullPath);
84    # Handle partial interfaces.
85    my $partialInterfaceName = getPartialInterfaceNameFromIDL($idlFileContents);
86    if ($partialInterfaceName) {
87        $supplementalDependencies{$fullPath} = [$partialInterfaceName];
88        next;
89    }
90    my $interfaceName = fileparse(basename($idlFile), ".idl");
91    # Handle implements statements.
92    my $implementedInterfaces = getImplementedInterfacesFromIDL($idlFileContents, $interfaceName);
93    foreach my $implementedInterface (@{$implementedInterfaces}) {
94        my $implementedIdlFile = $interfaceNameToIdlFile{$implementedInterface};
95        die "Could not find a the IDL file where the following implemented interface is defined: $implementedInterface" unless $implementedIdlFile;
96        if ($supplementalDependencies{$implementedIdlFile}) {
97            push(@{$supplementalDependencies{$implementedIdlFile}}, $interfaceName);
98        } else {
99            $supplementalDependencies{$implementedIdlFile} = [$interfaceName];
100        }
101    }
102    # Handle [NoInterfaceObject].
103    unless (isCallbackInterfaceFromIDL($idlFileContents)) {
104        my $extendedAttributes = getInterfaceExtendedAttributesFromIDL($idlFileContents);
105        unless ($extendedAttributes->{"NoInterfaceObject"}) {
106            my @globalContexts = split("&", $extendedAttributes->{"GlobalContext"} || "DOMWindow");
107            my $attributeCode = GenerateConstructorAttribute($interfaceName, $extendedAttributes);
108            $windowConstructorsCode .= $attributeCode if grep(/^DOMWindow$/, @globalContexts);
109            $workerGlobalScopeConstructorsCode .= $attributeCode if grep(/^WorkerGlobalScope$/, @globalContexts);
110            $sharedWorkerGlobalScopeConstructorsCode .= $attributeCode if grep(/^SharedWorkerGlobalScope$/, @globalContexts);
111            $dedicatedWorkerGlobalScopeConstructorsCode .= $attributeCode if grep(/^DedicatedWorkerGlobalScope$/, @globalContexts);
112        }
113    }
114    $supplementals{$fullPath} = [];
115}
116
117# Generate partial interfaces for Constructors.
118GeneratePartialInterface("DOMWindow", $windowConstructorsCode, $windowConstructorsFile);
119GeneratePartialInterface("WorkerGlobalScope", $workerGlobalScopeConstructorsCode, $workerGlobalScopeConstructorsFile);
120GeneratePartialInterface("SharedWorkerGlobalScope", $sharedWorkerGlobalScopeConstructorsCode, $sharedWorkerGlobalScopeConstructorsFile);
121GeneratePartialInterface("DedicatedWorkerGlobalScope", $dedicatedWorkerGlobalScopeConstructorsCode, $dedicatedWorkerGlobalScopeConstructorsFile);
122
123# Resolves partial interfaces and implements dependencies.
124foreach my $idlFile (keys %supplementalDependencies) {
125    my $baseFiles = $supplementalDependencies{$idlFile};
126    foreach my $baseFile (@{$baseFiles}) {
127        my $targetIdlFile = $interfaceNameToIdlFile{$baseFile};
128        push(@{$supplementals{$targetIdlFile}}, $idlFile);
129    }
130    delete $supplementals{$idlFile};
131}
132
133# Outputs the dependency.
134# The format of a supplemental dependency file:
135#
136# DOMWindow.idl P.idl Q.idl R.idl
137# Document.idl S.idl
138# Event.idl
139# ...
140#
141# The above indicates that DOMWindow.idl is supplemented by P.idl, Q.idl and R.idl,
142# Document.idl is supplemented by S.idl, and Event.idl is supplemented by no IDLs.
143# The IDL that supplements another IDL (e.g. P.idl) never appears in the dependency file.
144my $dependencies = "";
145foreach my $idlFile (sort keys %supplementals) {
146    $dependencies .= "$idlFile @{$supplementals{$idlFile}}\n";
147}
148WriteFileIfChanged($supplementalDependencyFile, $dependencies);
149
150if ($supplementalMakefileDeps) {
151    my $makefileDeps = "";
152    foreach my $idlFile (sort keys %supplementals) {
153        my $basename = $idlFileToInterfaceName{$idlFile};
154
155        my @dependencies = map { basename($_) } @{$supplementals{$idlFile}};
156
157        $makefileDeps .= "JS${basename}.h: @{dependencies}\n";
158        $makefileDeps .= "DOM${basename}.h: @{dependencies}\n";
159        $makefileDeps .= "WebDOM${basename}.h: @{dependencies}\n";
160        foreach my $dependency (@dependencies) {
161            $makefileDeps .= "${dependency}:\n";
162        }
163    }
164
165    WriteFileIfChanged($supplementalMakefileDeps, $makefileDeps);
166}
167
168sub WriteFileIfChanged
169{
170    my $fileName = shift;
171    my $contents = shift;
172
173    if (-f $fileName) {
174        open FH, "<", $fileName or die "Couldn't open $fileName: $!\n";
175        my @lines = <FH>;
176        my $oldContents = join "", @lines;
177        close FH;
178        return if $contents eq $oldContents;
179    }
180    open FH, ">", $fileName or die "Couldn't open $fileName: $!\n";
181    print FH $contents;
182    close FH;
183}
184
185sub GeneratePartialInterface
186{
187    my $interfaceName = shift;
188    my $attributesCode = shift;
189    my $destinationFile = shift;
190
191    my $contents = "partial interface ${interfaceName} {\n$attributesCode};\n";
192    WriteFileIfChanged($destinationFile, $contents);
193
194    my $fullPath = Cwd::realpath($destinationFile);
195    $supplementalDependencies{$fullPath} = [$interfaceName] if $interfaceNameToIdlFile{$interfaceName};
196}
197
198sub GenerateConstructorAttribute
199{
200    my $interfaceName = shift;
201    my $extendedAttributes = shift;
202
203    my $code = "    ";
204    my @extendedAttributesList;
205    foreach my $attributeName (keys %{$extendedAttributes}) {
206      next unless ($attributeName eq "Conditional" || $attributeName eq "EnabledAtRuntime" || $attributeName eq "EnabledBySetting");
207      my $extendedAttribute = $attributeName;
208      $extendedAttribute .= "=" . $extendedAttributes->{$attributeName} unless $extendedAttributes->{$attributeName} eq "VALUE_IS_MISSING";
209      push(@extendedAttributesList, $extendedAttribute);
210    }
211    $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
212
213    my $originalInterfaceName = $interfaceName;
214    $interfaceName = $extendedAttributes->{"InterfaceName"} if $extendedAttributes->{"InterfaceName"};
215    $code .= "attribute " . $originalInterfaceName . "Constructor $interfaceName;\n";
216
217    # In addition to the regular property, for every [NamedConstructor] extended attribute on an interface,
218    # a corresponding property MUST exist on the ECMAScript global object.
219    if ($extendedAttributes->{"NamedConstructor"}) {
220        my $constructorName = $extendedAttributes->{"NamedConstructor"};
221        $constructorName =~ s/\(.*//g; # Extract function name.
222        $code .= "    ";
223        $code .= "[" . join(', ', @extendedAttributesList) . "] " if @extendedAttributesList;
224        $code .= "attribute " . $originalInterfaceName . "NamedConstructor $constructorName;\n";
225    }
226    return $code;
227}
228
229sub getFileContents
230{
231    my $idlFile = shift;
232
233    open FILE, "<", $idlFile;
234    my @lines = <FILE>;
235    close FILE;
236
237    # Filter out preprocessor lines.
238    @lines = grep(!/^\s*#/, @lines);
239
240    return join('', @lines);
241}
242
243sub getPartialInterfaceNameFromIDL
244{
245    my $fileContents = shift;
246
247    if ($fileContents =~ /partial\s+interface\s+(\w+)/gs) {
248        return $1;
249    }
250}
251
252# identifier-A implements identifier-B;
253# http://www.w3.org/TR/WebIDL/#idl-implements-statements
254sub getImplementedInterfacesFromIDL
255{
256    my $fileContents = shift;
257    my $interfaceName = shift;
258
259    my @implementedInterfaces = ();
260    while ($fileContents =~ /^\s*(\w+)\s+implements\s+(\w+)\s*;/mg) {
261        die "Identifier on the left of the 'implements' statement should be $interfaceName in $interfaceName.idl, but found $1" if $1 ne $interfaceName;
262        push(@implementedInterfaces, $2);
263    }
264    return \@implementedInterfaces
265}
266
267sub isCallbackInterfaceFromIDL
268{
269    my $fileContents = shift;
270    return ($fileContents =~ /callback\s+interface\s+\w+/gs);
271}
272
273sub trim
274{
275    my $string = shift;
276    $string =~ s/^\s+|\s+$//g;
277    return $string;
278}
279
280sub getInterfaceExtendedAttributesFromIDL
281{
282    my $fileContents = shift;
283
284    my $extendedAttributes = {};
285
286    if ($fileContents =~ /\[(.*)\]\s+(interface|exception)\s+(\w+)/gs) {
287        my @parts = split(',', $1);
288        foreach my $part (@parts) {
289            my @keyValue = split('=', $part);
290            my $key = trim($keyValue[0]);
291            next unless length($key);
292            my $value = "VALUE_IS_MISSING";
293            $value = trim($keyValue[1]) if @keyValue > 1;
294            $extendedAttributes->{$key} = $value;
295        }
296    }
297
298    return $extendedAttributes;
299}
300