#
# 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";
    } 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 ($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 $baseScope = $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 = $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 { $_ 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;
