blob: 951bc0af59eb1f5886c7131319cf66edf78c512b [file] [log] [blame]
# Copyright (C) 2010 Apple Inc. All rights reserved.
# Copyright (C) 2012 Samsung Electronics
#
# 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 CodeGeneratorTestRunner;
use Carp qw<longmess>;
use Data::Dumper;
sub assert
{
my $message = shift;
my $mess = longmess();
print Dumper($mess);
die $message;
}
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 _constantGetterFunctionName
{
my ($constantName) = @_;
return "get".$constantName;
}
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) 2010 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>
#include <wtf/MathExtras.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 const JSClassRef 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);
return JSClassCreate(&definition);
}();
return jsClass;
}
EOF
if (my @constants = @{$interface->constants}) {
push(@contents, "\n// Constants\n");
foreach my $constant (@constants) {
$self->_includeHeaders(\%contentsIncludes, $constant->type);
my $getterName = _constantGetterFunctionName($self->_getterName($constant));
my $getterExpression = "impl->${getterName}()";
my $value = $constant->value;
push(@contents, <<EOF);
static JSValueRef $getterName(JSContextRef context, JSObjectRef, JSStringRef, JSValueRef*)
{
return JSValueMakeNumber(context, $value);
}
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 "undefined";
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));
my $conditionalString = $$self{codeGenerator}->GenerateConditionalString($interface);
unshift(@contents, "\n#if ${conditionalString}\n\n") if $conditionalString;
unshift(@contents, "#include \"config.h\"\n");
unshift(@contents, @contentsPrefix);
push(@contents, "\n#endif // ${conditionalString}\n") if $conditionalString;
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 _nativeNumericType
{
my ($self, $type) = @_;
my %numericTypeHash = (
"byte" => "int8_t",
"long long" => "int64_t",
"long" => "int32_t",
"octet" => "uint8_t",
"short" => "int16_t",
"unsigned long long" => "uint64_t",
"unsigned long" => "uint32_t",
"unsigned short" => "uint16_t",
"float" => "float",
"unrestricted float" => "float",
"double" => "double",
"unrestricted double" => "double",
);
my $result = $numericTypeHash{$type->name};
return ($result) ? $result : "double";
}
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 $self->_nativeNumericType($type) if $$self{codeGenerator}->IsPrimitiveType($type);
return _implementationClassName($type);
}
sub _platformTypeConstructor
{
my ($self, $type, $argumentName) = @_;
return "toOptionalBool(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 "createJSString(context, $argumentName)" if $$self{codeGenerator}->IsStringType($type);
return "toOptionalDouble(context, $argumentName)" if $$self{codeGenerator}->IsPrimitiveType($type) && $type->isNullable;
if ($$self{codeGenerator}->IsPrimitiveType($type)) {
my $convertToDouble = "JSValueToNumber(context, $argumentName, nullptr)";
my $nativeNumericType = $self->_nativeNumericType($type);
return "clampTo<$nativeNumericType>($convertToDouble)" if $nativeNumericType ne "double";
return $convertToDouble;
}
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,
"JSRetainPtr<JSStringRef>" => 1,
"JSValueRef" => 1,
);
my $nullValue = "nullptr";
if ($platformType eq "JSValueRef") {
$nullValue = "JSValueMakeUndefined(context)";
} elsif (defined $nonPointerTypes{$platformType} || $$self{codeGenerator}->IsNumericType($type)) {
$nullValue = $type->isNullable ? "std::nullopt" : "$platformType()";
}
return "bool $variableName = $condition && $constructor;" if $condition && $platformType eq "bool";
return "auto $variableName = $condition ? $constructor : $nullValue;" if $condition;
return "auto $variableName = $constructor;";
}
sub _returnExpression
{
my ($self, $returnType, $expression) = @_;
return "JSValueMakeUndefined(context)" if $returnType->name eq "undefined";
return "makeValue(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 "makeValue(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 $isReadOnly = 1;
my $getterName;
if (ref($_) eq "IDLAttribute") {
$isReadOnly = $_->isReadOnly;
$getterName = $self->_getterName($_);
} elsif (ref($_) eq "IDLConstant") {
$getterName = _constantGetterFunctionName($self->_getterName($_));
}
my $attributeName = $_->name;
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) . " }";
};
my $attributesAndConstants = [];
push (@$attributesAndConstants, @{ $interface->attributes });
if ($interface->constants) {
push (@$attributesAndConstants, @{ $interface->constants });
}
return $self->_staticFunctionsOrValuesGetterImplementation($interface, "value", "{ 0, 0, 0, 0 }", $mapFunction, $attributesAndConstants);
}
1;