# Copyright (C) 2016 Apple Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF # THE POSSIBILITY OF SUCH DAMAGE. use strict; use warnings; use File::Spec; package CodeGeneratorDumpRenderTree; sub new { my ($class, $codeGenerator, $writeDependencies, $verbose, $idlFilePath) = @_; my $reference = { codeGenerator => $codeGenerator, idlFilePath => $idlFilePath, }; bless($reference, $class); return $reference; } sub GenerateInterface { } sub WriteData { my ($self, $interface, $outputDir) = @_; foreach my $file ($self->_generateHeaderFile($interface), $self->_generateImplementationFile($interface)) { $$self{codeGenerator}->UpdateFile(File::Spec->catfile($outputDir, $$file{name}), join("", @{$$file{contents}})); } } sub _className { my ($type) = @_; return "JS" . _implementationClassName($type); } sub _classRefGetter { my ($self, $type) = @_; return $$self{codeGenerator}->WK_lcfirst(_implementationClassName($type)) . "Class"; } sub _parseLicenseBlock { my ($fileHandle) = @_; my ($copyright, $readCount, $buffer, $currentCharacter, $previousCharacter); my $startSentinel = "/*"; my $lengthOfStartSentinel = length($startSentinel); $readCount = read($fileHandle, $buffer, $lengthOfStartSentinel); return "" if ($readCount < $lengthOfStartSentinel || $buffer ne $startSentinel); $copyright = $buffer; while ($readCount = read($fileHandle, $currentCharacter, 1)) { $copyright .= $currentCharacter; return $copyright if $currentCharacter eq "/" && $previousCharacter eq "*"; $previousCharacter = $currentCharacter; } return ""; } sub _parseLicenseBlockFromFile { my ($path) = @_; open my $fileHandle, "<", $path or die "Failed to open $path for reading: $!"; my $licenseBlock = _parseLicenseBlock($fileHandle); close($fileHandle); return $licenseBlock; } sub _defaultLicenseBlock { return <{licenseBlock} if $self->{licenseBlock}; my $licenseBlock = _parseLicenseBlockFromFile($self->{idlFilePath}) || _defaultLicenseBlock(); $self->{licenseBlock} = $licenseBlock; return $licenseBlock; } sub _generateHeaderFile { my ($self, $interface) = @_; my @contents = (); my $type = $interface->type; my $className = _className($type); my $implementationClassName = _implementationClassName($type); my $filename = $className . ".h"; push(@contents, $self->_licenseBlock()); my $parentClassName = _parentClassName($interface); push(@contents, <_classRefGetter($type)]}(); private: static const JSStaticFunction* staticFunctions(); static const JSStaticValue* staticValues(); EOF if (my @functions = @{$interface->functions}) { push(@contents, "\n // Functions\n\n"); foreach my $function (@functions) { push(@contents, " static JSValueRef @{[$function->name]}(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*);\n"); } } if (my @attributes = @{$interface->attributes}) { push(@contents, "\n // Attributes\n\n"); foreach my $attribute (@attributes) { push(@contents, " static JSValueRef @{[$self->_getterName($attribute)]}(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*);\n"); push(@contents, " static bool @{[$self->_setterName($attribute)]}(JSContextRef, JSObjectRef, JSStringRef, JSValueRef, JSValueRef*);\n") unless $attribute->isReadOnly; } } push(@contents, < $filename, contents => \@contents }; } sub _generateImplementationFile { my ($self, $interface) = @_; my @contentsPrefix = (); my %contentsIncludes = (); my @contents = (); my $type = $interface->type; my $className = _className($type); my $implementationClassName = _implementationClassName($type); my $filename = $className . ".cpp"; push(@contentsPrefix, $self->_licenseBlock()); my $classRefGetter = $self->_classRefGetter($type); my $parentClassName = _parentClassName($interface); $contentsIncludes{"${className}.h"} = 1; $contentsIncludes{"${implementationClassName}.h"} = 1; push(@contentsPrefix, < #include namespace WTR { ${implementationClassName}* to${implementationClassName}(JSContextRef context, JSValueRef value) { if (!context || !value || !${className}::${classRefGetter}() || !JSValueIsObjectOfClass(context, value, ${className}::${classRefGetter}())) return 0; return static_cast<${implementationClassName}*>(JSWrapper::unwrap(context, value)); } JSClassRef ${className}::${classRefGetter}() { static JSClassRef jsClass; if (!jsClass) { JSClassDefinition definition = kJSClassDefinitionEmpty; definition.className = "@{[$type->name]}"; definition.parentClass = @{[$self->_parentClassRefGetterExpression($interface)]}; definition.staticValues = staticValues(); definition.staticFunctions = staticFunctions(); EOF push(@contents, " definition.initialize = initialize;\n") unless _parentInterface($interface); push(@contents, " definition.finalize = finalize;\n") unless _parentInterface($interface); push(@contents, <_staticFunctionsGetterImplementation($interface), "\n"); push(@contents, $self->_staticValuesGetterImplementation($interface)); if (my @functions = @{$interface->functions}) { push(@contents, "\n// Functions\n"); foreach my $function (@functions) { push(@contents, <name]}(JSContextRef context, JSObjectRef, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { ${implementationClassName}* impl = to${implementationClassName}(context, thisObject); if (!impl) return JSValueMakeUndefined(context); EOF my $functionCall; if ($function->extendedAttributes->{"CustomArgumentHandling"}) { $functionCall = "impl->" . $function->name . "(context, argumentCount, arguments, exception)"; } else { my @arguments = (); my @specifiedArguments = @{$function->arguments}; $self->_includeHeaders(\%contentsIncludes, $function->type); if ($function->extendedAttributes->{"PassContext"}) { push(@arguments, "context"); } foreach my $i (0..$#specifiedArguments) { my $argument = $specifiedArguments[$i]; $self->_includeHeaders(\%contentsIncludes, $type); push(@contents, " " . $self->_platformTypeVariableDeclaration($argument->type, $argument->name, "arguments[$i]", "argumentCount > $i") . "\n"); push(@arguments, $self->_argumentExpression($argument)); } $functionCall = "impl->" . $function->name . "(" . join(", ", @arguments) . ")"; } push(@contents, " ${functionCall};\n\n") if $function->type->name eq "void"; push(@contents, " return " . $self->_returnExpression($function->type, $functionCall) . ";\n}\n"); } } if (my @attributes = @{$interface->attributes}) { push(@contents, "\n// Attributes\n"); foreach my $attribute (@attributes) { $self->_includeHeaders(\%contentsIncludes, $attribute->type); my $getterName = $self->_getterName($attribute); my $getterExpression = "impl->${getterName}()"; push(@contents, <_returnExpression($attribute->type, $getterExpression)]}; } EOF unless ($attribute->isReadOnly) { push(@contents, <_setterName($attribute)]}(JSContextRef context, JSObjectRef object, JSStringRef, JSValueRef value, JSValueRef* exception) { ${implementationClassName}* impl = to${implementationClassName}(context, object); if (!impl) return false; EOF my $platformValue = $self->_platformTypeConstructor($attribute->type, "value"); push(@contents, <@{[$self->_setterName($attribute)]}(${platformValue}); return true; } EOF } } } push(@contents, < $filename, contents => \@contents }; } sub _getterName { my ($self, $attribute) = @_; return $attribute->name; } sub _includeHeaders { my ($self, $headers, $type) = @_; return unless defined $type; return if $type->name eq "boolean"; return if $type->name eq "object"; return if $$self{codeGenerator}->IsNonPointerType($type); return if $$self{codeGenerator}->IsStringType($type); $$headers{_className($type) . ".h"} = 1; $$headers{_implementationClassName($type) . ".h"} = 1; } sub _implementationClassName { my ($type) = @_; return $type->name; } sub _parentClassName { my ($interface) = @_; my $parentInterface = _parentInterface($interface); return $parentInterface ? _className($parentInterface) : "JSWrapper"; } sub _parentClassRefGetterExpression { my ($self, $interface) = @_; my $parentInterface = _parentInterface($interface); return $parentInterface ? $self->_classRefGetter($parentInterface) . "()" : "0"; } sub _parentInterface { my ($interface) = @_; return $interface->parentType; } sub _platformType { my ($self, $type) = @_; return undef unless defined $type; return "bool" if $type->name eq "boolean"; return "JSValueRef" if $type->name eq "object"; return "JSRetainPtr" if $$self{codeGenerator}->IsStringType($type); return "double" if $$self{codeGenerator}->IsNonPointerType($type); return _implementationClassName($type); } sub _platformTypeConstructor { my ($self, $type, $argumentName) = @_; return "JSValueToNullableBoolean(context, $argumentName)" if $type->name eq "boolean" && $type->isNullable; return "JSValueToBoolean(context, $argumentName)" if $type->name eq "boolean"; return "$argumentName" if $type->name eq "object"; return "JSRetainPtr(Adopt, JSValueToStringCopy(context, $argumentName, 0))" if $$self{codeGenerator}->IsStringType($type); return "JSValueToNumber(context, $argumentName, 0)" if $$self{codeGenerator}->IsNonPointerType($type); return "to" . _implementationClassName($type) . "(context, $argumentName)"; } sub _platformTypeVariableDeclaration { my ($self, $type, $variableName, $argumentName, $condition) = @_; my $platformType = $self->_platformType($type); my $constructor = $self->_platformTypeConstructor($type, $argumentName); my %nonPointerTypes = ( "bool" => 1, "double" => 1, "JSRetainPtr" => 1, "JSValueRef" => 1, ); my $nullValue = "0"; if ($platformType eq "JSValueRef") { $nullValue = "JSValueMakeUndefined(context)"; } elsif (defined $nonPointerTypes{$platformType} && $platformType ne "double") { $nullValue = "$platformType()"; } $platformType .= "*" unless defined $nonPointerTypes{$platformType}; return "$platformType $variableName = $condition && $constructor;" if $condition && $platformType eq "bool"; return "$platformType $variableName = $condition ? $constructor : $nullValue;" if $condition; return "$platformType $variableName = $constructor;"; } sub _returnExpression { my ($self, $returnType, $expression) = @_; return "JSValueMakeUndefined(context)" if $returnType->name eq "void"; return "JSValueMakeBooleanOrNull(context, ${expression})" if $returnType->name eq "boolean" && $returnType->isNullable; return "JSValueMakeBoolean(context, ${expression})" if $returnType->name eq "boolean"; return "${expression}" if $returnType->name eq "object"; return "JSValueMakeNumber(context, ${expression})" if $$self{codeGenerator}->IsNonPointerType($returnType); return "JSValueMakeStringOrNull(context, ${expression}.get())" if $$self{codeGenerator}->IsStringType($returnType); return "toJS(context, WTF::getPtr(${expression}))"; } sub _argumentExpression { my ($self, $argument) = @_; my $type = $argument->type; my $name = $argument->name; return "${name}.get()" if $$self{codeGenerator}->IsStringType($type); return $name; } sub _setterName { my ($self, $attribute) = @_; my $name = $attribute->name; return "set" . $$self{codeGenerator}->WK_ucfirst($name); } sub _staticFunctionsGetterImplementation { my ($self, $interface) = @_; my $mapFunction = sub { my $name = $_->name; my @attributes = qw(kJSPropertyAttributeDontDelete kJSPropertyAttributeReadOnly); push(@attributes, "kJSPropertyAttributeDontEnum") if $_->extendedAttributes->{"DontEnum"}; return "{ \"$name\", $name, " . join(" | ", @attributes) . " }"; }; return $self->_staticFunctionsOrValuesGetterImplementation($interface, "function", "{ 0, 0, 0 }", $mapFunction, $interface->functions); } sub _staticFunctionsOrValuesGetterImplementation { my ($self, $interface, $functionOrValue, $arrayTerminator, $mapFunction, $functionsOrAttributes) = @_; my $className = _className($interface->type); my $uppercaseFunctionOrValue = $$self{codeGenerator}->WK_ucfirst($functionOrValue); my $result = <extendedAttributes->{"NoImplementation"}; my $attributeName = $_->name; my $getterName = $self->_getterName($_); my $setterName = $_->isReadOnly ? "0" : $self->_setterName($_); my @attributes = qw(kJSPropertyAttributeDontDelete); push(@attributes, "kJSPropertyAttributeReadOnly") if $_->isReadOnly; push(@attributes, "kJSPropertyAttributeDontEnum") if $_->extendedAttributes->{"DontEnum"}; return "{ \"$attributeName\", $getterName, $setterName, " . join(" | ", @attributes) . " }"; }; return $self->_staticFunctionsOrValuesGetterImplementation($interface, "value", "{ 0, 0, 0, 0 }", $mapFunction, $interface->attributes); } 1;