blob: 9d7e01c579e171551bbcb9926a6679eff4ee1226 [file] [log] [blame]
#
# WebKit IDL parser
#
# Copyright (C) 2005 Nikolas Zimmermann <wildfox@kde.org>
# Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
# Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
# Copyright (C) 2009 Cameron McCormack <cam@mcc.id.au>
# Copyright (C) Research In Motion Limited 2010. All rights reserved.
# Copyright (C) 2013 Samsung Electronics. All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library; see the file COPYING.LIB. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
package CodeGenerator;
use strict;
use File::Basename;
use File::Find;
use Carp qw<longmess>;
use Data::Dumper;
my $useDocument = "";
my $useGenerator = "";
my $useOutputDir = "";
my $useOutputHeadersDir = "";
my $useDirectories = "";
my $preprocessor;
my $idlAttributes;
my $writeDependencies = 0;
my $defines = "";
my $targetIdlFilePath = "";
my $supplementalDependencies;
my $codeGenerator = 0;
my $verbose = 0;
my %integerTypeHash = (
"byte" => 1,
"long long" => 1,
"long" => 1,
"octet" => 1,
"short" => 1,
"unsigned long long" => 1,
"unsigned long" => 1,
"unsigned short" => 1,
);
my %floatingPointTypeHash = (
"float" => 1,
"unrestricted float" => 1,
"double" => 1,
"unrestricted double" => 1,
);
my %stringTypeHash = (
"ByteString" => 1,
"DOMString" => 1,
"USVString" => 1,
);
my %bufferSourceTypes = (
"ArrayBuffer" => 1,
"ArrayBufferView" => 1,
"DataView" => 1,
"Float32Array" => 1,
"Float64Array" => 1,
"Int16Array" => 1,
"Int32Array" => 1,
"Int8Array" => 1,
"Uint16Array" => 1,
"Uint32Array" => 1,
"Uint8Array" => 1,
"Uint8ClampedArray" => 1,
"BigInt64Array" => 1,
"BigUint64Array" => 1,
);
my %primitiveTypeHash = (
"boolean" => 1,
"undefined" => 1,
"Date" => 1
);
my %dictionaryTypeImplementationNameOverrides = ();
my %enumTypeImplementationNameOverrides = ();
my %svgAttributesInHTMLHash = (
"class" => 1,
"id" => 1,
"onabort" => 1,
"onclick" => 1,
"onerror" => 1,
"onload" => 1,
"onmousedown" => 1,
"onmouseenter" => 1,
"onmouseleave" => 1,
"onmousemove" => 1,
"onmouseout" => 1,
"onmouseover" => 1,
"onmouseup" => 1,
"onresize" => 1,
"onscroll" => 1,
"onunload" => 1,
);
# Cache of IDL file pathnames.
my $idlFiles;
my $cachedInterfaces = {};
my $cachedExternalDictionaries = {};
my $cachedExternalEnumerations = {};
my $cachedTypes = {};
sub assert
{
my $message = shift;
my $mess = longmess();
print Dumper($mess);
die $message;
}
# Default constructor
sub new
{
my $object = shift;
my $reference = { };
$useDirectories = shift;
$useGenerator = shift;
$useOutputDir = shift;
$useOutputHeadersDir = shift;
$preprocessor = shift;
$writeDependencies = shift;
$verbose = shift;
$targetIdlFilePath = shift;
$idlAttributes = shift;
$supplementalDependencies = shift;
bless($reference, $object);
return $reference;
}
sub ProcessDocument
{
my $object = shift;
$useDocument = shift;
$defines = shift;
my $ifaceName = "CodeGenerator" . $useGenerator;
require $ifaceName . ".pm";
# Dynamically load external code generation perl module
$codeGenerator = $ifaceName->new($object, $writeDependencies, $verbose, $targetIdlFilePath);
unless (defined($codeGenerator)) {
my $interfaces = $useDocument->interfaces;
foreach my $interface (@$interfaces) {
print "Skipping $useGenerator code generation for IDL interface \"" . $interface->type->name . "\".\n" if $verbose;
}
return;
}
if (@{$useDocument->interfaces}) {
$object->ProcessInterfaces($useDocument, $defines, $codeGenerator);
return;
}
if (@{$useDocument->callbackFunctions}) {
$object->ProcessCallbackFunctions($useDocument, $defines, $codeGenerator);
return;
}
if (@{$useDocument->dictionaries}) {
$object->ProcessDictionaries($useDocument, $defines, $codeGenerator);
return;
}
if (@{$useDocument->enumerations}) {
$object->ProcessEnumerations($useDocument, $defines, $codeGenerator);
return;
}
# die "Processing document " . $useDocument->fileName . " did not generate anything.";
}
sub GenerateEmptyHeaderAndCpp
{
my ($object, $useDocument, $codeGenerator) = @_;
my $targetName = fileparse($useDocument->fileName, ".idl");
my $prefix = $codeGenerator->FileNamePrefix();
my $headerName = "${prefix}${targetName}.h";
my $cppName = "${prefix}${targetName}.cpp";
my $contents = "/*
This file is generated to inform build scripts that ${headerName} and
${cppName} were created for ${targetName}.idl, and prevent the build
scripts from trying to regenerate ${headerName} and ${cppName} on every build.
*/
";
open FH, "> ${useOutputHeadersDir}/${headerName}" or die "Cannot open ${headerName}\n";
print FH $contents;
close FH;
open FH, "> ${useOutputDir}/${cppName}" or die "Cannot open ${cppName}\n";
print FH $contents;
close FH;
}
sub ProcessDictionaryAndEnumerationImplementedAsOverrides
{
my ($object, $useDocument) = @_;
foreach my $dictionary (@{$useDocument->dictionaries}) {
if ($dictionary->extendedAttributes->{"ImplementedAs"}) {
$dictionaryTypeImplementationNameOverrides{$dictionary->type->name} = $dictionary->extendedAttributes->{"ImplementedAs"};
}
}
foreach my $enumeration (@{$useDocument->enumerations}) {
if ($enumeration->extendedAttributes->{"ImplementedAs"}) {
$enumTypeImplementationNameOverrides{$enumeration->type->name} = $enumeration->extendedAttributes->{"ImplementedAs"};
}
}
}
sub ProcessInterfaces
{
my ($object, $useDocument, $defines, $codeGenerator) = @_;
die "Multiple interfaces per document are not supported" if @{$useDocument->interfaces} > 1;
my $interface = $useDocument->interfaces->[0];
if ($interface->isMixin || $interface->isPartial) {
$object->GenerateEmptyHeaderAndCpp($useDocument, $codeGenerator);
return;
}
$object->ProcessInterfaceSupplementalDependencies($interface, $useDocument);
$object->ProcessDictionaryAndEnumerationImplementedAsOverrides($useDocument);
print "Generating $useGenerator bindings code for IDL interface \"" . $interface->type->name . "\"\n" if $verbose;
$codeGenerator->GenerateInterface($interface, $defines, $useDocument->enumerations, $useDocument->dictionaries);
$codeGenerator->WriteData($interface, $useOutputDir, $useOutputHeadersDir);
}
sub ProcessCallbackFunctions
{
my ($object, $useDocument, $defines, $codeGenerator) = @_;
die "Multiple standalone callback functions per document are not supported" if @{$useDocument->callbackFunctions} > 1;
my $callbackFunction = $useDocument->callbackFunctions->[0];
$object->ProcessDictionaryAndEnumerationImplementedAsOverrides($useDocument);
print "Generating $useGenerator bindings code for IDL callback function \"" . $callbackFunction->type->name . "\"\n" if $verbose;
$codeGenerator->GenerateCallbackFunction($callbackFunction, $useDocument->enumerations, $useDocument->dictionaries);
$codeGenerator->WriteData($callbackFunction, $useOutputDir, $useOutputHeadersDir);
}
sub ProcessDictionaries
{
my ($object, $useDocument, $defines, $codeGenerator) = @_;
my $dictionary;
my $otherDictionaries;
if (@{$useDocument->dictionaries} == 1) {
$dictionary = $useDocument->dictionaries->[0];
} else {
my $primaryDictionaryName = fileparse($targetIdlFilePath, ".idl");
foreach my $candidate (@{$useDocument->dictionaries}) {
if ($candidate->type->name eq $primaryDictionaryName) {
$dictionary = $candidate;
} else {
push(@$otherDictionaries, $candidate);
}
}
die "Multiple dictionaries per document are only supported if one matches the filename" unless $dictionary;
}
if ($dictionary->isPartial) {
$object->GenerateEmptyHeaderAndCpp($useDocument, $codeGenerator);
return;
}
$object->ProcessDictionarySupplementalDependencies($dictionary, $useDocument);
$object->ProcessDictionaryAndEnumerationImplementedAsOverrides($useDocument);
print "Generating $useGenerator bindings code for IDL dictionary \"" . $dictionary->type->name . "\"\n" if $verbose;
$codeGenerator->GenerateDictionary($dictionary, $useDocument->enumerations, $otherDictionaries);
$codeGenerator->WriteData($dictionary, $useOutputDir, $useOutputHeadersDir);
}
sub ProcessEnumerations
{
my ($object, $useDocument, $defines, $codeGenerator) = @_;
die "Multiple standalone enumerations per document are not supported" if @{$useDocument->enumerations} > 1;
my $enumeration = $useDocument->enumerations->[0];
$object->ProcessDictionaryAndEnumerationImplementedAsOverrides($useDocument);
print "Generating $useGenerator bindings code for IDL enumeration \"" . $enumeration->type->name . "\"\n" if $verbose;
$codeGenerator->GenerateEnumeration($enumeration);
$codeGenerator->WriteData($enumeration, $useOutputDir, $useOutputHeadersDir);
}
sub MergeExtendedAttributesFromSupplemental
{
my ($object, $supplementalExtendedAttributes, $property, $context) = @_;
foreach my $extendedAttributeName (keys %{$supplementalExtendedAttributes}) {
my $isAllowed = 0;
for my $contextAllowed (@{$idlAttributes->{$extendedAttributeName}->{"contextsAllowed"}}) {
if ($contextAllowed eq $context) {
$isAllowed = 1;
last;
}
}
next unless $isAllowed;
# FIXME: Tests currently depend on special case for Conditional overwriting non-operation cases, but this should
# be fixed. Ultimately, this will probably mean either removing support for disjunctions for Conditional,
# which is not used outside of the tests, or adding support for parentheses to disambiguate.
if ($extendedAttributeName eq "Conditional" && $context ne "operation") {
$property->extendedAttributes->{$extendedAttributeName} = $supplementalExtendedAttributes->{$extendedAttributeName};
next;
}
# Handle case that the attribute already has a extented attribute with this key.
if ($property->extendedAttributes->{$extendedAttributeName}) {
if (!$idlAttributes->{$extendedAttributeName}->{"supportsConjunction"}) {
die "Duplicate non-mergeable extended attribute ($extendedAttributeName) found when merging extended attributes for ${property->name}";
}
$property->extendedAttributes->{$extendedAttributeName} = $property->extendedAttributes->{$extendedAttributeName} . "&" . $supplementalExtendedAttributes->{$extendedAttributeName};
} else {
$property->extendedAttributes->{$extendedAttributeName} = $supplementalExtendedAttributes->{$extendedAttributeName};
}
}
}
sub IsValidSupplementalInterface
{
my ($object, $supplementalInterface, $targetInterface, $includesMap) = @_;
return 1 if $supplementalInterface->isPartial && $supplementalInterface->type->name eq $targetInterface->type->name;
return 1 if $supplementalInterface->isMixin && $includesMap->{$supplementalInterface->type->name};
return 0;
}
sub IsValidSupplementalDictionary
{
my ($object, $supplementalDictionary, $targetDictionary) = @_;
return 1 if $supplementalDictionary->isPartial && $supplementalDictionary->type->name eq $targetDictionary->type->name;
return 0;
}
sub ProcessInterfaceSupplementalDependencies
{
my ($object, $targetInterface, $targetDocument) = @_;
return unless $supplementalDependencies;
my $targetFileName = fileparse($targetDocument->fileName);
# FIXME: Currently, we require all the includes statements to be in the
# same file as the interface they are being mixed into, but that restriction
# is artificial and we could add support for includes statements elsewhere.
my %includesMap = ();
foreach my $include (@{$targetDocument->includes}) {
if ($include->interfaceIdentifier eq $targetInterface->type->name) {
unless (grep { fileparse($_, ".idl") eq $include->mixinIdentifier } @{$supplementalDependencies->{$targetFileName}}) {
die "included interface mixin " . $include->mixinIdentifier . " not in supplemental dependencies."
}
$includesMap{$include->mixinIdentifier} = $include;
}
}
foreach my $idlFile (@{$supplementalDependencies->{$targetFileName}}) {
next if fileparse($idlFile) eq $targetFileName;
my $interfaceName = fileparse($idlFile, ".idl");
my $parser = IDLParser->new(!$verbose);
my $document = $parser->Parse($idlFile, $defines, $preprocessor, $idlAttributes);
foreach my $interface (@{$document->interfaces}) {
next unless $object->IsValidSupplementalInterface($interface, $targetInterface, \%includesMap);
if ($interface->isMixin && !$interface->isPartial) {
# Recursively process any supplemental dependencies for each valid mixin. This
# allows partial partial interface mixins to be merged into the mixin.
$object->ProcessInterfaceSupplementalDependencies($interface, $document);
}
# Support for attributes from supplemental interfaces.
foreach my $attribute (@{$interface->attributes}) {
next unless $targetInterface->isMixin || shouldPropertyBeExposed($attribute, $targetInterface);
if ($interface->isMixin && !$interface->isPartial) {
# Add includes statement specific extended attributes to each attribute.
$object->MergeExtendedAttributesFromSupplemental($includesMap{$interface->type->name}->extendedAttributes, $attribute, "attribute");
}
# Add interface-wide extended attributes to each attribute.
$object->MergeExtendedAttributesFromSupplemental($interface->extendedAttributes, $attribute, "attribute");
push(@{$targetInterface->attributes}, $attribute);
}
# Support for operations from supplemental interfaces.
foreach my $operation (@{$interface->operations}) {
next unless $targetInterface->isMixin || shouldPropertyBeExposed($operation, $targetInterface);
if ($interface->isMixin && !$interface->isPartial) {
# Add includes statement specific extended attributes to each operation.
$object->MergeExtendedAttributesFromSupplemental($includesMap{$interface->type->name}->extendedAttributes, $operation, "operation");
}
# Add interface-wide extended attributes to each operation.
$object->MergeExtendedAttributesFromSupplemental($interface->extendedAttributes, $operation, "operation");
push(@{$targetInterface->operations}, $operation);
}
# Support for constants from supplemental interfaces.
foreach my $constant (@{$interface->constants}) {
next unless $targetInterface->isMixin || shouldPropertyBeExposed($constant, $targetInterface);
if ($interface->isMixin && !$interface->isPartial) {
# Add includes statement specific extended attributes to each operation.
$object->MergeExtendedAttributesFromSupplemental($includesMap{$interface->type->name}->extendedAttributes, $constant, "constant");
}
# Add interface-wide extended attributes to each constant.
$object->MergeExtendedAttributesFromSupplemental($interface->extendedAttributes, $constant, "constant");
push(@{$targetInterface->constants}, $constant);
}
}
}
}
sub ProcessDictionarySupplementalDependencies
{
my ($object, $targetDictionary, $targetDocument) = @_;
return unless $supplementalDependencies;
my $targetFileName = fileparse($targetDocument->fileName);
foreach my $idlFile (@{$supplementalDependencies->{$targetFileName}}) {
next if fileparse($idlFile) eq $targetFileName;
my $dictionaryName = fileparse($idlFile, ".idl");
my $parser = IDLParser->new(!$verbose);
my $document = $parser->Parse($idlFile, $defines, $preprocessor, $idlAttributes);
foreach my $dictionary (@{$document->dictionaries}) {
next unless $object->IsValidSupplementalDictionary($dictionary, $targetDictionary);
# Support for members of partial dictionaries.
foreach my $member (@{$dictionary->members}) {
next unless shouldPropertyBeExposed($member, $targetDictionary);
# Add interface-wide extended attributes to each member.
$object->MergeExtendedAttributesFromSupplemental($dictionary->extendedAttributes, $member, "dictionary-member");
push(@{$targetDictionary->members}, $member);
}
}
}
}
# Attributes / Operations / Constants of supplemental interfaces can have an [Exposed=XX] attribute which restricts
# on which global contexts they should be exposed. [Exposed=*] will expose the attribute on the interface for all
# supported global contexts.
sub shouldPropertyBeExposed
{
my ($context, $target) = @_;
my $exposedAttribute = $target->extendedAttributes->{"Exposed"} || "Window";
return 1 if $exposedAttribute eq "*";
$exposedAttribute = substr($exposedAttribute, 1, -1) if substr($exposedAttribute, 0, 1) eq "(";
my @targetGlobalContexts = split(",", $exposedAttribute);
my $exposed = $context->extendedAttributes->{Exposed};
return 1 unless $exposed;
$exposed = substr($exposed, 1, -1) if substr($exposed, 0, 1) eq "(";
my @sourceGlobalContexts = split(",", $exposed);
foreach my $targetGlobalContext (@targetGlobalContexts) {
return 1 if grep(/^$targetGlobalContext$/, @sourceGlobalContexts);
}
return 0;
}
sub UpdateFile
{
my $object = shift;
my $fileName = shift;
my $contents = shift;
# FIXME: We should only write content if it is different from what is in the file.
# But that would mean running more often the binding generator, see https://bugs.webkit.org/show_bug.cgi?id=131756
open FH, ">", $fileName or die "Couldn't open $fileName: $!\n";
print FH $contents;
close FH;
}
sub ForAllParents
{
my $object = shift;
my $interface = shift;
my $beforeRecursion = shift;
my $afterRecursion = shift;
my $recurse;
$recurse = sub {
my $outerInterface = shift;
my $currentInterface = shift;
if ($currentInterface->parentType) {
my $interfaceName = $currentInterface->parentType->name;
my $parentInterface = $object->ParseInterface($outerInterface, $interfaceName);
if ($beforeRecursion) {
&$beforeRecursion($parentInterface) eq 'prune' and next;
}
&$recurse($outerInterface, $parentInterface);
&$afterRecursion($parentInterface) if $afterRecursion;
}
};
&$recurse($interface, $interface);
}
sub IDLFileForInterface
{
my $object = shift;
my $interfaceName = shift;
unless ($idlFiles) {
my $sourceRoot = $ENV{SOURCE_ROOT};
my @directories = map { $_ = "$sourceRoot/$_" if $sourceRoot && -d "$sourceRoot/$_"; $_ } @$useDirectories;
push(@directories, ".");
$idlFiles = { };
my $wanted = sub {
$idlFiles->{$1} = $File::Find::name if /^([A-Z].*)\.idl$/ && !exists $idlFiles->{$1};
$File::Find::prune = 1 if /^\../;
};
find($wanted, @directories);
}
return $idlFiles->{$interfaceName};
}
sub GetInterfaceForType
{
my ($object, $currentInterface, $type) = @_;
return undef unless $object->IsInterfaceType($type);
return $object->ParseInterface($currentInterface, $type->name);
}
sub GetAttributeFromInterface
{
my ($object, $outerInterface, $interfaceName, $attributeName) = @_;
my $interface = $object->ParseInterface($outerInterface, $interfaceName);
for my $attribute (@{$interface->attributes}) {
return $attribute if $attribute->name eq $attributeName;
}
die("Could not find attribute '$attributeName' on interface '$interfaceName'.");
}
sub ParseInterface
{
my $object = shift;
my $outerInterface = shift;
my $interfaceName = shift;
return undef if $interfaceName eq 'Object';
return undef if $interfaceName eq 'UNION';
if (exists $cachedInterfaces->{$interfaceName}) {
return $cachedInterfaces->{$interfaceName};
}
# Step #1: Find the IDL file associated with 'interface'
my $filename = $object->IDLFileForInterface($interfaceName)
or assert("Could NOT find IDL file for interface \"$interfaceName\", reachable from \"" . $outerInterface->type->name . "\"!\n");
print " | |> Parsing parent IDL \"$filename\" for interface \"$interfaceName\"\n" if $verbose;
# Step #2: Parse the found IDL file (in quiet mode).
my $parser = IDLParser->new(1);
my $document = $parser->Parse($filename, $defines, $preprocessor, $idlAttributes);
foreach my $interface (@{$document->interfaces}) {
if ($interface->type->name eq $interfaceName) {
$cachedInterfaces->{$interfaceName} = $interface;
return $interface;
}
}
die("Could NOT find interface definition for $interfaceName in $filename");
}
sub ParseType
{
my ($object, $typeString) = @_;
return $cachedTypes->{$typeString} if exists($cachedTypes->{$typeString});
my $parser = IDLParser->new(1);
my $type = $parser->ParseType($typeString, $idlAttributes);
$cachedTypes->{$typeString} = $type;
return $type;
}
# Helpers for all CodeGenerator***.pm modules
sub IsNumericType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $integerTypeHash{$type->name};
return 1 if $floatingPointTypeHash{$type->name};
return 0;
}
sub IsStringOrEnumType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $object->IsStringType($type);
return 1 if $object->IsEnumType($type);
return 0;
}
sub IsIntegerType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $integerTypeHash{$type->name};
return 0;
}
sub IsFloatingPointType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $floatingPointTypeHash{$type->name};
return 0;
}
sub IsPrimitiveType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $primitiveTypeHash{$type->name};
return 1 if $object->IsNumericType($type);
return 0;
}
sub IsStringType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $stringTypeHash{$type->name};
return 0;
}
sub IsEnumType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return defined($object->GetEnumByType($type));
}
sub GetEnumByType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
my $name = $type->name;
die "GetEnumByType() was called with an undefined enumeration name" unless defined($name);
for my $enumeration (@{$useDocument->enumerations}) {
return $enumeration if $enumeration->type->name eq $name;
}
return $cachedExternalEnumerations->{$name} if exists($cachedExternalEnumerations->{$name});
# Find the IDL file associated with the dictionary.
my $filename = $object->IDLFileForInterface($name) or return;
# Do a fast check to see if it seems to contain a dictionary.
my $fileContents = slurp($filename);
if ($fileContents =~ /\benum\s+$name/gs) {
# Parse the IDL.
my $parser = IDLParser->new(1);
my $document = $parser->Parse($filename, $defines, $preprocessor, $idlAttributes);
foreach my $enumeration (@{$document->enumerations}) {
next unless $enumeration->type->name eq $name;
$cachedExternalEnumerations->{$name} = $enumeration;
my $implementedAs = $enumeration->extendedAttributes->{ImplementedAs};
$enumTypeImplementationNameOverrides{$enumeration->type->name} = $implementedAs if $implementedAs;
return $enumeration;
}
}
$cachedExternalEnumerations->{$name} = undef;
}
# An enumeration defined in its own IDL file.
sub IsExternalEnumType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $object->IsEnumType($type) && defined($cachedExternalEnumerations->{$type->name});
}
sub HasEnumImplementationNameOverride
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if exists $enumTypeImplementationNameOverrides{$type->name};
return 0;
}
sub GetEnumImplementationNameOverride
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $enumTypeImplementationNameOverrides{$type->name};
}
sub GetDictionaryByType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
my $name = $type->name;
die "GetDictionaryByType() was called with an undefined dictionary name" unless defined($name);
for my $dictionary (@{$useDocument->dictionaries}) {
return $dictionary if $dictionary->type->name eq $name;
}
return $cachedExternalDictionaries->{$name} if exists($cachedExternalDictionaries->{$name});
# Find the IDL file associated with the dictionary.
my $filename = $object->IDLFileForInterface($name) or return;
# Do a fast check to see if it seems to contain a dictionary.
my $fileContents = slurp($filename);
if ($fileContents =~ /\bdictionary\s+$name/gs) {
# Parse the IDL.
my $parser = IDLParser->new(1);
my $document = $parser->Parse($filename, $defines, $preprocessor, $idlAttributes);
foreach my $dictionary (@{$document->dictionaries}) {
if ($dictionary->type->name eq $name) {
$object->ProcessDictionarySupplementalDependencies($dictionary, $document);
$cachedExternalDictionaries->{$name} = $dictionary;
my $implementedAs = $dictionary->extendedAttributes->{ImplementedAs};
$dictionaryTypeImplementationNameOverrides{$name} = $implementedAs if $implementedAs;
return $dictionary;
}
}
}
$cachedExternalDictionaries->{$name} = undef;
}
sub IsDictionaryType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $type->name =~ /^[A-Z]/ && defined($object->GetDictionaryByType($type));
}
# A dictionary defined in its own IDL file.
sub IsExternalDictionaryType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $object->IsDictionaryType($type) && defined($cachedExternalDictionaries->{$type->name});
}
sub HasDictionaryImplementationNameOverride
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if exists $dictionaryTypeImplementationNameOverrides{$type->name};
return 0;
}
sub GetDictionaryImplementationNameOverride
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $dictionaryTypeImplementationNameOverrides{$type->name};
}
sub IsSVGAnimatedTypeName
{
my ($object, $typeName) = @_;
return $typeName =~ /^SVGAnimated/;
}
sub IsSVGAnimatedType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $object->IsSVGAnimatedTypeName($type->name);
}
sub IsSVGPathSegTypeName
{
my ($object, $typeName) = @_;
return $typeName =~ /^SVGPathSeg/;
}
sub IsSVGPathSegType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $object->IsSVGPathSegTypeName($type->name);
}
sub IsConstructorType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $type->name =~ /Constructor$/;
}
sub IsSequenceType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $type->name eq "sequence";
}
sub IsFrozenArrayType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $type->name eq "FrozenArray";
}
sub IsSequenceOrFrozenArrayType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $object->IsSequenceType($type) || $object->IsFrozenArrayType($type);
}
sub IsRecordType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $type->name eq "record";
}
sub IsBufferSourceType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $bufferSourceTypes{$type->name};
return 0;
}
sub IsPromiseType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $type->name eq "Promise";
}
# These match WK_lcfirst and WK_ucfirst defined in builtins_generator.py.
# Uppercase the first letter while respecting WebKit style guidelines.
# E.g., xmlEncoding becomes XMLEncoding, but xmlllang becomes Xmllang.
sub WK_ucfirst
{
my ($object, $param) = @_;
my $ret = ucfirst($param);
$ret =~ s/Xml/XML/ if $ret =~ /^Xml[^a-z]/;
$ret =~ s/Svg/SVG/ if $ret =~ /^Svg/;
$ret =~ s/Srgb/SRGB/ if $ret =~ /^Srgb/;
$ret =~ s/Cenc/cenc/ if $ret =~ /^Cenc/;
$ret =~ s/Cbcs/cbcs/ if $ret =~ /^Cbcs/;
$ret =~ s/Pq/PQ/ if $ret =~ /^Pq$/;
$ret =~ s/Hlg/HLG/ if $ret =~ /^Hlg/;
$ret =~ s/Ios/iOS/ if $ret =~ /^Ios/;
return $ret;
}
# Lowercase the first letter while respecting WebKit style guidelines.
# URL becomes url, but SetURL becomes setURL.
sub WK_lcfirst
{
my ($object, $param) = @_;
my $ret = lcfirst($param);
$ret =~ s/dOM/dom/ if $ret =~ /^dOM/;
$ret =~ s/hTML/html/ if $ret =~ /^hTML/;
$ret =~ s/uRL/url/ if $ret =~ /^uRL/;
$ret =~ s/jS/js/ if $ret =~ /^jS/;
$ret =~ s/xML/xml/ if $ret =~ /^xML/;
$ret =~ s/xSLT/xslt/ if $ret =~ /^xSLT/;
$ret =~ s/cSS/css/ if $ret =~ /^cSS/;
$ret =~ s/rTC/rtc/ if $ret =~ /^rTC/;
# For HTML5 FileSystem API Flags attributes.
# (create is widely used to instantiate an object and must be avoided.)
$ret =~ s/^create/isCreate/ if $ret =~ /^create$/;
$ret =~ s/^exclusive/isExclusive/ if $ret =~ /^exclusive$/;
return $ret;
}
sub slurp
{
my $file = shift;
open my $fh, '<', $file or die;
local $/ = undef;
my $content = <$fh>;
close $fh;
return $content;
}
sub trim
{
my $string = shift;
$string =~ s/^\s+|\s+$//g;
return $string;
}
# Return the C++ namespace that a given attribute name string is defined in.
sub NamespaceForAttributeName
{
my ($object, $interfaceName, $attributeName) = @_;
return "SVGNames" if $interfaceName =~ /^SVG/ && !$svgAttributesInHTMLHash{$attributeName};
return "HTMLNames";
}
# Identifies overloaded functions and for each function adds an array with
# links to its respective overloads (including itself).
sub LinkOverloadedOperations
{
my ($object, $interface) = @_;
my %nameToOperationsMap = ();
foreach my $operation (@{$interface->operations}) {
my $name = $operation->name;
$nameToOperationsMap{$name} = [] if !exists $nameToOperationsMap{$name};
push(@{$nameToOperationsMap{$name}}, $operation);
$operation->{overloads} = $nameToOperationsMap{$name};
$operation->{overloadIndex} = @{$nameToOperationsMap{$name}};
}
my $index = 1;
foreach my $constructor (@{$interface->constructors}) {
$constructor->{overloads} = $interface->constructors;
$constructor->{overloadIndex} = $index;
$index++;
}
}
sub AttributeNameForGetterAndSetter
{
my ($generator, $attribute) = @_;
my $attributeName = $attribute->name;
if ($attribute->extendedAttributes->{"ImplementedAs"}) {
$attributeName = $attribute->extendedAttributes->{"ImplementedAs"};
}
my $attributeType = $attribute->type;
# SVG animated types need to use a special attribute name.
# The rest of the special casing for SVG animated types is handled in the language-specific code generators.
$attributeName .= "Animated" if $generator->IsSVGAnimatedType($attributeType);
return $attributeName;
}
sub ContentAttributeName
{
my ($generator, $implIncludes, $interfaceName, $attribute) = @_;
my $contentAttributeName = $attribute->extendedAttributes->{"Reflect"};
return undef if !$contentAttributeName;
$contentAttributeName = lc $generator->AttributeNameForGetterAndSetter($attribute) if $contentAttributeName eq "VALUE_IS_MISSING";
my $namespace = $generator->NamespaceForAttributeName($interfaceName, $contentAttributeName);
$implIncludes->{"${namespace}.h"} = 1;
return "WebCore::${namespace}::${contentAttributeName}Attr";
}
sub GetterExpression
{
my ($generator, $implIncludes, $interfaceName, $attribute) = @_;
my $contentAttributeName = $generator->ContentAttributeName($implIncludes, $interfaceName, $attribute);
if (!$contentAttributeName) {
return ($generator->WK_lcfirst($generator->AttributeNameForGetterAndSetter($attribute)));
}
my $attributeType = $attribute->type;
my $functionName;
if ($attribute->extendedAttributes->{"URL"}) {
$functionName = "getURLAttribute";
} elsif ($attributeType->name eq "boolean") {
$functionName = "hasAttributeWithoutSynchronization";
} elsif ($attributeType->name eq "long") {
$functionName = "getIntegralAttribute";
} elsif ($attributeType->name eq "unsigned long") {
$functionName = "getUnsignedIntegralAttribute";
} elsif ($attributeType->name eq "Element") {
$functionName = "getElementAttribute";
} elsif ($attributeType->name eq "FrozenArray" && scalar @{$attributeType->subtypes} == 1 && @{$attributeType->subtypes}[0]->name eq "Element") {
$functionName = "getElementsArrayAttribute";
} else {
if ($contentAttributeName eq "WebCore::HTMLNames::idAttr") {
$functionName = "getIdAttribute";
$contentAttributeName = "";
} elsif ($contentAttributeName eq "WebCore::HTMLNames::nameAttr") {
$functionName = "getNameAttribute";
$contentAttributeName = "";
} elsif ($generator->IsSVGAnimatedType($attributeType)) {
$functionName = "getAttribute";
} else {
$implIncludes->{"ElementInlines.h"} = 1;
$functionName = "attributeWithoutSynchronization";
}
}
return ($functionName, $contentAttributeName);
}
sub SetterExpression
{
my ($generator, $implIncludes, $interfaceName, $attribute) = @_;
my $contentAttributeName = $generator->ContentAttributeName($implIncludes, $interfaceName, $attribute);
if (!$contentAttributeName) {
return ("set" . $generator->WK_ucfirst($generator->AttributeNameForGetterAndSetter($attribute)));
}
my $attributeType = $attribute->type;
my $functionName;
if ($attributeType->name eq "boolean") {
$functionName = "setBooleanAttribute";
} elsif ($attributeType->name eq "long") {
$functionName = "setIntegralAttribute";
} elsif ($attributeType->name eq "unsigned long") {
$functionName = "setUnsignedIntegralAttribute";
} elsif ($attributeType->name eq "Element") {
$functionName = "setElementAttribute";
} elsif ($attributeType->name eq "FrozenArray" && scalar @{$attributeType->subtypes} == 1 && @{$attributeType->subtypes}[0]->name eq "Element") {
$functionName = "setElementsArrayAttribute";
} elsif ($generator->IsSVGAnimatedType($attributeType)) {
$functionName = "setAttribute";
} else {
$functionName = "setAttributeWithoutSynchronization";
}
return ($functionName, $contentAttributeName);
}
sub IsBuiltinType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $object->IsPrimitiveType($type);
return 1 if $object->IsSequenceOrFrozenArrayType($type);
return 1 if $object->IsRecordType($type);
return 1 if $object->IsStringType($type);
return 1 if $object->IsBufferSourceType($type);
return 1 if $type->isUnion;
return 1 if $type->name eq "EventListener";
return 1 if $type->name eq "JSON";
return 1 if $type->name eq "Promise";
return 1 if $type->name eq "ScheduledAction";
return 1 if $type->name eq "SerializedScriptValue";
return 1 if $type->name eq "any";
return 1 if $type->name eq "object";
return 0;
}
sub IsInterfaceType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 0 if $object->IsBuiltinType($type);
return 0 if $object->IsDictionaryType($type);
return 0 if $object->IsEnumType($type);
return 1;
}
sub IsWrapperType
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 1 if $object->IsInterfaceType($type);
return 0;
}
sub InterfaceHasRegularToJSONOperation
{
my ($object, $interface) = @_;
foreach my $operation (@{$interface->operations}) {
if (!$operation->isStatic && $operation->name eq "toJSON") {
return 1;
}
}
return 0;
}
# https://webidl.spec.whatwg.org/#dfn-json-types
sub IsJSONType
{
my ($object, $interface, $type) = @_;
return 1 if $type->name eq "object";
return 1 if $type->name eq "boolean";
return 1 if $object->IsNumericType($type);
return 1 if $object->IsStringOrEnumType($type);
if ($object->IsSequenceOrFrozenArrayType($type)) {
my $subtype = @{$type->subtypes}[0];
return $object->IsJSONType($interface, $subtype);
}
if ($object->IsRecordType($type)) {
my $valueSubtype = @{$type->subtypes}[0];
return $object->IsJSONType($interface, $valueSubtype);
}
if ($object->IsInterfaceType($type)) {
# Special case EventHandler, since there is no real IDL for it.
return 0 if $type->name eq "EventHandler";
my $interface = $object->GetInterfaceForType($interface, $type);
if ($object->InterfaceHasRegularToJSONOperation($interface)) {
return 1;
}
my $anyParentHasRegularToJSONOperation = 0;
$object->ForAllParents($interface, sub {
my $parentInterface = shift;
$anyParentHasRegularToJSONOperation = 1 if $object->InterfaceHasRegularToJSONOperation($parentInterface);
}, 0);
if ($anyParentHasRegularToJSONOperation) {
return 1;
}
}
if ($type->isUnion) {
# FIXME: Union types should be supported if all the member types are JSON types.
die "Default toJSON is currently not supported for union types.\n";
}
if ($object->IsDictionaryType($type)) {
# FIXME: Dictionary types should be supported if all members declared on the dictionary and all its inherited dictionaries are JSON types.
die "Default toJSON is currently not supported for dictionary types.\n";
}
return 0;
}
sub hasCachedAttributeOrCustomGetterExtendedAttribute
{
my ($attribute) = @_;
return $attribute->extendedAttributes->{CachedAttribute} || $attribute->extendedAttributes->{CustomGetter};
}
sub GetInterfaceExtendedAttributesFromName
{
# FIXME: It's bad to have a function like this that opens another IDL file to answer a question.
# Overusing this kind of function can make things really slow. Lets avoid these if we can.
my ($object, $interfaceName) = @_;
my $idlFile = $object->IDLFileForInterface($interfaceName) or assert("Could NOT find IDL file for interface \"$interfaceName\"!\n");
open FILE, "<", $idlFile or die;
my @lines = <FILE>;
close FILE;
my $fileContents = join('', @lines);
my $extendedAttributes = {};
if ($fileContents =~ /\[(.*)\]\s+(callback interface|interface)\s+(\w+)/gs) {
my @parts = split(',', $1);
foreach my $part (@parts) {
my @keyValue = split('=', $part);
my $key = trim($keyValue[0]);
next unless length($key);
my $value = "VALUE_IS_MISSING";
$value = trim($keyValue[1]) if @keyValue > 1;
$extendedAttributes->{$key} = $value;
}
}
return $extendedAttributes;
}
sub ComputeIsCallbackInterface
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 0 unless $object->IsInterfaceType($type);
return 0 if $type->name eq "WindowProxy";
my $typeName = $type->name;
my $idlFile = $object->IDLFileForInterface($typeName) or assert("Could NOT find IDL file for interface \"$typeName\"!\n");
open FILE, "<", $idlFile or die;
my @lines = <FILE>;
close FILE;
my $fileContents = join('', @lines);
return ($fileContents =~ /callback\s+interface\s+(\w+)/gs);
}
my %isCallbackInterface = ();
sub IsCallbackInterface
{
# FIXME: It's bad to have a function like this that opens another IDL file to answer a question.
# Overusing this kind of function can make things really slow. Lets avoid these if we can.
# To mitigate that, lets cache what we learn in a hash so we don't open the same file over and over.
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $isCallbackInterface{$type->name} if exists $isCallbackInterface{$type->name};
my $result = $object->ComputeIsCallbackInterface($type);
$isCallbackInterface{$type->name} = $result;
return $result;
}
sub ComputeIsCallbackFunction
{
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return 0 unless $object->IsInterfaceType($type);
return 0 if $type->name eq "WindowProxy";
my $typeName = $type->name;
my $idlFile = $object->IDLFileForInterface($typeName) or assert("Could NOT find IDL file for interface \"$typeName\"!\n");
open FILE, "<", $idlFile or die;
my @lines = <FILE>;
close FILE;
my $fileContents = join('', @lines);
return ($fileContents =~ /(.*)callback\s+(\w+)\s+=/gs);
}
my %isCallbackFunction = ();
sub IsCallbackFunction
{
# FIXME: It's bad to have a function like this that opens another IDL file to answer a question.
# Overusing this kind of function can make things really slow. Lets avoid these if we can.
# To mitigate that, lets cache what we learn in a hash so we don't open the same file over and over.
my ($object, $type) = @_;
assert("Not a type") if ref($type) ne "IDLType";
return $isCallbackFunction{$type->name} if exists $isCallbackFunction{$type->name};
my $result = $object->ComputeIsCallbackFunction($type);
$isCallbackFunction{$type->name} = $result;
return $result;
}
sub GenerateConditionalString
{
my ($generator, $node) = @_;
my $conditional = $node->extendedAttributes->{"Conditional"};
if ($conditional) {
return $generator->GenerateConditionalStringFromAttributeValue($conditional);
} else {
return "";
}
}
sub GenerateConditionalStringFromAttributeValue
{
my ($generator, $conditional) = @_;
my %disjunction;
map {
my $expression = $_;
my %conjunction;
map { $conjunction{$_} = 1; } split(/&/, $expression);
$expression = "ENABLE(" . join(") && ENABLE(", sort keys %conjunction) . ")";
$disjunction{$expression} = 1
} split(/\|/, $conditional);
return "1" if keys %disjunction == 0;
return (%disjunction)[0] if keys %disjunction == 1;
my @parenthesized;
map {
my $expression = $_;
$expression = "($expression)" if $expression =~ / /;
push @parenthesized, $expression;
} sort keys %disjunction;
return join(" || ", @parenthesized);
}
sub GenerateCompileTimeCheckForEnumsIfNeeded
{
my ($generator, $interface) = @_;
return () if $interface->extendedAttributes->{"DoNotCheckConstants"} || !@{$interface->constants};
my $enum = $interface->extendedAttributes->{"ConstantsEnum"};
my $baseScope = $enum || $interface->extendedAttributes->{"ConstantsScope"} || $interface->type->name;
my @checks = ();
foreach my $constant (@{$interface->constants}) {
my $scope = $constant->extendedAttributes->{"ImplementedBy"} || $baseScope;
my $name = $constant->extendedAttributes->{"ImplementedAs"} || $constant->name;
my $value = $enum ? "static_cast<" . $enum . ">(" . $constant->value . ")" : $constant->value;
my $conditional = $constant->extendedAttributes->{"Conditional"};
push(@checks, "#if " . $generator->GenerateConditionalStringFromAttributeValue($conditional) . "\n") if $conditional;
push(@checks, "static_assert(${scope}::${name} == ${value}, \"${name} in ${scope} does not match value from IDL\");\n");
push(@checks, "#endif\n") if $conditional;
}
push(@checks, "\n");
return @checks;
}
sub ExtendedAttributeContains
{
my $object = shift;
my $extendedAttribute = shift;
return 0 unless $extendedAttribute;
my $keyword = shift;
my @extendedAttributeKeywords = split /\s*\&\s*/, $extendedAttribute;
return grep { /$keyword/ } @extendedAttributeKeywords if ref($keyword) eq "Regexp";
return grep { $_ eq $keyword } @extendedAttributeKeywords;
}
# FIXME: This is backwards. We currently name the interface and the IDL files with the implementation name. We
# should use the real interface name in the IDL files and then use ImplementedAs to map this to the implementation name.
sub GetVisibleInterfaceName
{
my ($object, $interface) = @_;
my $interfaceName = $interface->extendedAttributes->{"InterfaceName"};
return $interfaceName ? $interfaceName : $interface->type->name;
}
sub InheritsInterface
{
my ($object, $interface, $interfaceName) = @_;
return 1 if $interfaceName eq $interface->type->name;
my $found = 0;
$object->ForAllParents($interface, sub {
my $currentInterface = shift;
if ($currentInterface->type->name eq $interfaceName) {
$found = 1;
}
return 1 if $found;
}, 0);
return $found;
}
sub InheritsExtendedAttribute
{
my ($object, $interface, $extendedAttribute) = @_;
return 1 if $interface->extendedAttributes->{$extendedAttribute};
my $found = 0;
$object->ForAllParents($interface, sub {
my $currentInterface = shift;
if ($currentInterface->extendedAttributes->{$extendedAttribute}) {
$found = 1;
}
return 1 if $found;
}, 0);
return $found;
}
1;