| # 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 <<EOF; |
| /* |
| * 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. |
| */ |
| EOF |
| } |
| |
| sub _licenseBlock |
| { |
| my ($self) = @_; |
| return $self->{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, <<EOF); |
| |
| #ifndef ${className}_h |
| #define ${className}_h |
| |
| #include "${parentClassName}.h" |
| EOF |
| push(@contents, <<EOF); |
| |
| namespace WTR { |
| |
| class ${implementationClassName}; |
| |
| class ${className} : public ${parentClassName} { |
| public: |
| static JSClassRef @{[$self->_classRefGetter($type)]}(); |
| |
| private: |
| static const JSStaticFunction* staticFunctions(); |
| static const JSStaticValue* staticValues(); |
| EOF |
| |
| if (my @operations = @{$interface->operations}) { |
| push(@contents, "\n // Functions\n\n"); |
| foreach my $operation (@operations) { |
| push(@contents, " static JSValueRef @{[$operation->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, <<EOF); |
| }; |
| |
| ${implementationClassName}* to${implementationClassName}(JSContextRef, JSValueRef); |
| |
| } // namespace WTR |
| |
| #endif // ${className}_h |
| EOF |
| |
| return { name => $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, <<EOF); |
| |
| EOF |
| |
| push(@contents, <<EOF); |
| #include <JavaScriptCore/JSRetainPtr.h> |
| #include <wtf/GetPtr.h> |
| |
| 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, <<EOF); |
| jsClass = JSClassCreate(&definition); |
| } |
| return jsClass; |
| } |
| |
| EOF |
| |
| push(@contents, $self->_staticFunctionsGetterImplementation($interface), "\n"); |
| push(@contents, $self->_staticValuesGetterImplementation($interface)); |
| |
| if (my @operations = @{$interface->operations}) { |
| push(@contents, "\n// Functions\n"); |
| |
| foreach my $operation (@operations) { |
| push(@contents, <<EOF); |
| |
| JSValueRef ${className}::@{[$operation->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 ($operation->extendedAttributes->{"CustomArgumentHandling"}) { |
| $functionCall = "impl->" . $operation->name . "(context, argumentCount, arguments, exception)"; |
| } else { |
| my @arguments = (); |
| my @specifiedArguments = @{$operation->arguments}; |
| |
| $self->_includeHeaders(\%contentsIncludes, $operation->type); |
| |
| if ($operation->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->" . $operation->name . "(" . join(", ", @arguments) . ")"; |
| } |
| |
| push(@contents, " ${functionCall};\n\n") if $operation->type->name eq "void"; |
| push(@contents, " return " . $self->_returnExpression($operation->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, <<EOF); |
| |
| JSValueRef ${className}::${getterName}(JSContextRef context, JSObjectRef object, JSStringRef, JSValueRef* exception) |
| { |
| ${implementationClassName}* impl = to${implementationClassName}(context, object); |
| if (!impl) |
| return JSValueMakeUndefined(context); |
| |
| return @{[$self->_returnExpression($attribute->type, $getterExpression)]}; |
| } |
| EOF |
| |
| unless ($attribute->isReadOnly) { |
| push(@contents, <<EOF); |
| |
| bool ${className}::@{[$self->_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, <<EOF); |
| impl->@{[$self->_setterName($attribute)]}(${platformValue}); |
| |
| return true; |
| } |
| EOF |
| } |
| } |
| } |
| |
| push(@contents, <<EOF); |
| |
| } // namespace WTR |
| |
| EOF |
| |
| unshift(@contents, map { "#include \"$_\"\n" } sort keys(%contentsIncludes)); |
| unshift(@contents, "#include \"config.h\"\n"); |
| unshift(@contents, @contentsPrefix); |
| |
| return { name => $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}->IsPrimitiveType($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<JSStringRef>" if $$self{codeGenerator}->IsStringType($type); |
| return "double" if $$self{codeGenerator}->IsPrimitiveType($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 "adopt(JSValueToStringCopy(context, $argumentName, nullptr))" if $$self{codeGenerator}->IsStringType($type); |
| return "JSValueToNumber(context, $argumentName, nullptr)" if $$self{codeGenerator}->IsPrimitiveType($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<JSStringRef>" => 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}->IsPrimitiveType($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->operations); |
| } |
| |
| sub _staticFunctionsOrValuesGetterImplementation |
| { |
| my ($self, $interface, $functionOrValue, $arrayTerminator, $mapFunction, $operationsOrAttributes) = @_; |
| |
| my $className = _className($interface->type); |
| my $uppercaseFunctionOrValue = $$self{codeGenerator}->WK_ucfirst($functionOrValue); |
| |
| my $result = <<EOF; |
| const JSStatic${uppercaseFunctionOrValue}* ${className}::static${uppercaseFunctionOrValue}s() |
| { |
| EOF |
| |
| my @initializers = map(&$mapFunction, @{$operationsOrAttributes}); |
| return $result . " return 0;\n}\n" unless @initializers; |
| |
| $result .= <<EOF |
| static const JSStatic${uppercaseFunctionOrValue} ${functionOrValue}s[] = { |
| @{[join(",\n ", @initializers)]}, |
| ${arrayTerminator} |
| }; |
| return ${functionOrValue}s; |
| } |
| EOF |
| } |
| |
| sub _staticValuesGetterImplementation |
| { |
| my ($self, $interface) = @_; |
| |
| my $mapFunction = sub { |
| return if $_->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; |