# Copyright (C) 2008 Luke Kenneth Casson Leighton # Copyright (C) 2008 Martin Soto # Copyright (C) 2008 Alp Toker # Copyright (C) 2009 Adam Dingle # Copyright (C) 2009 Jim Nelson # Copyright (C) 2009, 2010 Igalia S.L. # # 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., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. package CodeGeneratorGObject; use constant FileNamePrefix => "WebKitDOM"; use File::Basename; use FindBin; # Global Variables my %implIncludes = (); my %hdrIncludes = (); my @stableSymbols = (); my $defineTypeMacro = "G_DEFINE_TYPE"; my $defineTypeInterfaceImplementation = ")"; my @txtEventListeners = (); my @txtInstallProps = (); my @txtSetProps = (); my @txtGetProps = (); my $className = ""; # FIXME: this should be replaced with a function that recurses up the tree # to find the actual base type. my %baseTypeHash = ("Object" => 1, "Node" => 1, "NodeList" => 1, "NamedNodeMap" => 1, "DOMImplementation" => 1, "Event" => 1, "CSSRule" => 1, "CSSValue" => 1, "StyleSheet" => 1, "MediaList" => 1, "Counter" => 1, "Rect" => 1, "RGBColor" => 1, "XPathExpression" => 1, "XPathResult" => 1, "NodeIterator" => 1, "TreeWalker" => 1, "AbstractView" => 1, "Blob" => 1, "DOMTokenList" => 1, "HTMLCollection" => 1, "TextTrackCue" => 1, "AnimationTimeline" => 1); # Only objects derived from Node are released by the DOM object cache and can be # transfer none. Ideally we could use GetBaseClass with the parent type to check # whether it's Node, but unfortunately we only have the name of the return type, # and we can't know its parent base class. Since there are fewer classes in the # API that are not derived from Node, we will list them here to decide the # transfer type. my %transferFullTypeHash = ("AudioTrack" => 1, "AudioTrackList" => 1, "BarProp" => 1, "BatteryManager" => 1, "CSSRuleList" => 1, "CSSStyleDeclaration" => 1, "CSSStyleSheet" => 1, "DocumentTimeline" => 1, "DOMApplicationCache" => 1, "DOMMimeType" => 1, "DOMMimeTypeArray" => 1, "DOMNamedFlowCollection" => 1, "DOMPlugin" => 1, "DOMPluginArray" => 1, "DOMSelection" => 1, "DOMSettableTokenList" => 1, "DOMStringList" => 1, "DOMWindow" => 1, "DOMWindowCSS" => 1, "EventTarget" => 1, "File" => 1, "FileList" => 1, "Gamepad" => 1, "GamepadList" => 1, "Geolocation" => 1, "HTMLOptionsCollection" => 1, "History" => 1, "KeyboardEvent" => 1, "MediaError" => 1, "MediaController" => 1, "MouseEvent" => 1, "MediaQueryList" => 1, "Navigator" => 1, "NodeFilter" => 1, "Performance" => 1, "PerformanceEntry" => 1, "PerformanceEntryList" => 1, "PerformanceNavigation" => 1, "PerformanceTiming" => 1, "Range" => 1, "Screen" => 1, "SpeechSynthesis" => 1, "SpeechSynthesisVoice" => 1, "Storage" => 1, "StyleMedia" => 1, "TextTrack" => 1, "TextTrackCueList" => 1, "TimeRanges" => 1, "Touch" => 1, "UIEvent" => 1, "UserMessageHandler" => 1, "UserMessageHandlersNamespace" => 1, "ValidityState" => 1, "VideoTrack" => 1, "WebKitNamedFlow" => 1, "WebKitNamespace" => 1, "WebKitPoint" => 1, "WheelEvent" => 1, "XPathNSResolver" => 1); # List of function parameters that are allowed to be NULL my $canBeNullParams = { 'webkit_dom_document_create_attribute_ns' => ['namespaceURI'], 'webkit_dom_document_create_element_ns' => ['namespaceURI'], 'webkit_dom_document_create_entity_reference' => ['name'], 'webkit_dom_document_create_node_iterator' => ['filter'], 'webkit_dom_document_create_tree_walker' => ['filter'], 'webkit_dom_document_evaluate' => ['inResult', 'resolver'], 'webkit_dom_document_get_override_style' => ['pseudoElement'], 'webkit_dom_dom_implementation_create_document' => ['namespaceURI', 'doctype'], 'webkit_dom_dom_window_get_computed_style' => ['pseudoElement'], 'webkit_dom_element_set_attribute_ns' => ['namespaceURI'], 'webkit_dom_node_insert_before' => ['refChild'], }; # Default constructor sub new { my $object = shift; my $reference = { }; $codeGenerator = shift; bless($reference, $object); } my $licenceTemplate = << "EOF"; /* * This file is part of the WebKit open source project. * This file has been generated by generate-bindings.pl. DO NOT MODIFY! * * 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. */ EOF sub ShouldBeExposedAsInterface { my $interface = shift; return $interface eq "EventTarget"; } sub GetParentClassName { my $interface = shift; my $parent = $interface->parent; return "WebKitDOMObject" unless $parent and !ShouldBeExposedAsInterface($parent); return "WebKitDOM" . $parent; } sub GetParentImplClassName { my $interface = shift; my $parent = $interface->parent; return "Object" unless $parent and !ShouldBeExposedAsInterface($parent); return $parent; } sub IsBaseType { my $type = shift; return 1 if $baseTypeHash{$type}; return 0; } sub GetBaseClass { $parent = shift; $interface = shift; return $parent if $parent eq "Object" or IsBaseType($parent); return "Object" if ShouldBeExposedAsInterface($parent); return "Event" if $codeGenerator->InheritsInterface($interface, "Event"); return "CSSValue" if $parent eq "SVGColor" or $parent eq "CSSValueList"; return "Node"; } # From String::CamelCase 0.01 sub camelize { my $s = shift; join('', map{ ucfirst $_ } split(/(?<=[A-Za-z])_(?=[A-Za-z])|\b/, $s)); } sub decamelize { my $s = shift; $s =~ s{([^a-zA-Z]?)([A-Z]*)([A-Z])([a-z]?)}{ my $fc = pos($s)==0; my ($p0,$p1,$p2,$p3) = ($1,lc$2,lc$3,$4); my $t = $p0 || $fc ? $p0 : '_'; $t .= $p3 ? $p1 ? "${p1}_$p2$p3" : "$p2$p3" : "$p1$p2"; $t; }ge; # Some strings are not correctly decamelized, apply fix ups for ($s) { s/domcss/dom_css/; s/domhtml/dom_html/; s/domdom/dom_dom/; s/domcdata/dom_cdata/; s/domui/dom_ui/; s/x_path/xpath/; s/web_kit/webkit/; s/htmli_frame/html_iframe/; s/htmlbr/html_br/; s/htmlli/html_li/; s/htmlhr/html_hr/; s/htmld/html_d/; s/htmlo/html_o/; s/htmlu/html_u/; } return $s; } sub HumanReadableConditional { my @conditional = split('_', shift); my @upperCaseExceptions = ("SQL", "API"); my @humanReadable; for $part (@conditional) { if (!grep {$_ eq $part} @upperCaseExceptions) { $part = camelize(lc($part)); } push(@humanReadable, $part); } return join(' ', @humanReadable); } sub GetParentGObjType { my $interface = shift; my $parent = $interface->parent; return "WEBKIT_DOM_TYPE_OBJECT" unless $parent and !ShouldBeExposedAsInterface($parent); return "WEBKIT_DOM_TYPE_" . uc(decamelize(($parent))); } sub GetClassName { my $name = shift; return "WebKitDOM$name"; } sub SkipAttribute { my $attribute = shift; if ($attribute->signature->extendedAttributes->{"Custom"} || $attribute->signature->extendedAttributes->{"CustomGetter"}) { return 1; } my $propType = $attribute->signature->type; if ($propType =~ /Constructor$/) { return 1; } return 1 if $attribute->isStatic; return 1 if $codeGenerator->IsTypedArrayType($propType); $codeGenerator->AssertNotSequenceType($propType); if ($codeGenerator->GetArrayType($propType)) { return 1; } if ($codeGenerator->IsEnumType($propType)) { return 1; } # This is for DOMWindow.idl location attribute if ($attribute->signature->name eq "location") { return 1; } # This is for HTMLInput.idl valueAsDate if ($attribute->signature->name eq "valueAsDate") { return 1; } # This is for DOMWindow.idl Crypto attribute if ($attribute->signature->type eq "Crypto") { return 1; } return 1 if $attribute->signature->type eq "EventHandler"; return 1 if $attribute->signature->type eq "Symbol"; if ($attribute->signature->type eq "MediaQueryListListener") { return 1; } # Skip indexed database attributes for now, they aren't yet supported for the GObject generator. if ($attribute->signature->name =~ /^(?:webkit)?[Ii]ndexedDB/ or $attribute->signature->name =~ /^(?:webkit)?IDB/) { return 1; } if ($attribute->signature->extendedAttributes->{"JSBuiltin"}) { return 1; } return 0; } sub SkipFunction { my $object = shift; my $function = shift; my $parentNode = shift; my $decamelize = shift; my $prefix = shift; my $functionName = "webkit_dom_" . $decamelize . "_" . $prefix . decamelize($function->signature->name); my $functionReturnType = $prefix eq "set_" ? "void" : $function->signature->type; my $isCustomFunction = $function->signature->extendedAttributes->{"Custom"}; my $callWith = $function->signature->extendedAttributes->{"CallWith"}; my $isUnsupportedCallWith = $codeGenerator->ExtendedAttributeContains($callWith, "ScriptArguments") || $codeGenerator->ExtendedAttributeContains($callWith, "CallStack") || $codeGenerator->ExtendedAttributeContains($callWith, "FirstWindow") || $codeGenerator->ExtendedAttributeContains($callWith, "ActiveWindow"); # Static methods are unsupported return 1 if $function->isStatic; if (($isCustomFunction || $isUnsupportedCallWith) && $functionName ne "webkit_dom_node_replace_child" && $functionName ne "webkit_dom_node_insert_before" && $functionName ne "webkit_dom_node_remove_child" && $functionName ne "webkit_dom_node_append_child" && $functionName ne "webkit_dom_html_collection_item" && $functionName ne "webkit_dom_html_collection_named_item") { return 1; } # Skip functions that have callback parameters, because this code generator doesn't know # how to auto-generate callbacks. Skip functions that have "MediaQueryListListener" or # sequence parameters, because this code generator doesn't know how to auto-generate # MediaQueryListListener or sequence. Skip EventListeners because they are handled elsewhere. foreach my $param (@{$function->parameters}) { return 1 if $codeGenerator->IsFunctionOnlyCallbackInterface($param->type); return 1 if $param->extendedAttributes->{"Clamp"}; return 1 if $param->type eq "MediaQueryListListener"; return 1 if $param->type eq "EventListener"; return 1 if $codeGenerator->GetSequenceType($param->type); } # This is for DataTransferItemList.idl add(File) method if ($functionName eq "webkit_dom_data_transfer_item_list_add" && @{$function->parameters} == 1) { return 1; } # Skip Console::profile() and Console::profileEnd() as they're not correctly generated for the moment. if ($functionName eq "webkit_dom_console_profile" || $functionName eq "webkit_dom_console_profile_end") { return 1; } if ($codeGenerator->IsTypedArrayType($function->signature->type) || $codeGenerator->GetArrayType($function->signature->type)) { return 1; } if ($function->signature->name eq "set" and $parentNode->extendedAttributes->{"TypedArray"}) { return 1; } if ($object eq "MediaQueryListListener") { return 1; } if ($function->signature->name eq "getSVGDocument") { return 1; } if ($function->signature->name eq "getCSSCanvasContext") { return 1; } if ($function->signature->name eq "setRangeText" && @{$function->parameters} == 1) { return 1; } if ($function->signature->name eq "timeEnd") { return 1; } if ($codeGenerator->GetSequenceType($functionReturnType)) { return 1; } if ($function->signature->name eq "supports" && @{$function->parameters} == 1) { return 1; } return 1 if $function->signature->type eq "Promise"; return 1 if $function->signature->type eq "Symbol"; return 1 if $function->signature->type eq "Date"; return 1 if $function->signature->extendedAttributes->{"JSBuiltin"}; return 1 if $function->signature->extendedAttributes->{"PrivateIdentifier"} and not $function->signature->extendedAttributes->{"PublicIdentifier"}; return 0; } # Name type used in the g_value_{set,get}_* functions sub GetGValueTypeName { my $type = shift; my %types = ("DOMString", "string", "DOMTimeStamp", "uint", "float", "float", "unrestricted float", "float", "double", "double", "unrestricted double", "double", "boolean", "boolean", "char", "char", "long", "long", "long long", "int64", "byte", "int8", "octet", "uint8", "short", "int", "uchar", "uchar", "unsigned", "uint", "int", "int", "unsigned int", "uint", "unsigned long long", "uint64", "unsigned long", "ulong", "unsigned short", "uint"); return $types{$type} ? $types{$type} : "object"; } # Name type used in C declarations sub GetGlibTypeName { my $type = shift; my $name = GetClassName($type); my %types = ("DOMString", "gchar*", "DOMTimeStamp", "guint32", "SerializedScriptValue", "gchar*", "float", "gfloat", "unrestricted float", "gfloat", "double", "gdouble", "unrestricted double", "gdouble", "boolean", "gboolean", "char", "gchar", "long", "glong", "long long", "gint64", "byte", "gint8", "octet", "guint8", "short", "gshort", "uchar", "guchar", "unsigned", "guint", "int", "gint", "unsigned int", "guint", "unsigned long", "gulong", "unsigned long long", "guint64", "unsigned short", "gushort", "void", "void"); return $types{$type} ? $types{$type} : "$name*"; } sub IsGDOMClassType { my $type = shift; return 0 if $codeGenerator->IsNonPointerType($type) || $codeGenerator->IsStringType($type) || $type eq "SerializedScriptValue"; return 1; } sub IsPropertyReadable { my $property = shift; return !SkipAttribute($property); } sub IsPropertyWriteable { my $property = shift; if (!IsPropertyReadable($property)) { return 0; } if ($property->isReadOnly) { return 0; } my $gtype = GetGValueTypeName($property->signature->type); my $hasGtypeSignature = $gtype eq "boolean" || $gtype eq "float" || $gtype eq "double" || $gtype eq "int64" || $gtype eq "uint64" || $gtype eq "long" || $gtype eq "ulong" || $gtype eq "int" || $gtype eq "uint" || $gtype eq "short" || $gtype eq "ushort" || $gtype eq "int8" || $gtype eq "uint8" || $gtype eq "char" || $gtype eq "uchar" || $gtype eq "string"; if (!$hasGtypeSignature) { return 0; } # FIXME: We are not generating setters for 'Replaceable' attributes now, but we should somehow. if ($property->signature->extendedAttributes->{"Replaceable"}) { return 0; } if ($property->signature->extendedAttributes->{"CustomSetter"}) { return 0; } return 0 if $property->signature->extendedAttributes->{"CallWith"} || $property->signature->extendedAttributes->{"SetterCallWith"}; return 1; } sub GenerateConditionalWarning { my $node = shift; my $indentSize = shift; if (!$indentSize) { $indentSize = 4; } my $conditional = $node->extendedAttributes->{"Conditional"}; my @warn; if ($conditional) { if ($conditional =~ /&/) { my @splitConditionals = split(/&/, $conditional); foreach $condition (@splitConditionals) { push(@warn, "#if !ENABLE($condition)\n"); push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($condition) . "\")\n"); push(@warn, "#endif\n"); } } elsif ($conditional =~ /\|/) { foreach $condition (split(/\|/, $conditional)) { push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($condition) . "\")\n"); } } else { push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($conditional) . "\")\n"); } } return @warn; } sub GenerateProperty { my $attribute = shift; my $interfaceName = shift; my @writeableProperties = @{shift @_}; my $parentNode = shift; my $hasGetterException = $attribute->signature->extendedAttributes->{"GetterRaisesException"}; my $hasSetterException = $attribute->signature->extendedAttributes->{"SetterRaisesException"}; my $decamelizeInterfaceName = decamelize($interfaceName); my $propName = decamelize($attribute->signature->name); my $propFunctionName = GetFunctionSignatureName($interfaceName, $attribute); my $propNameCaps = uc($propName); my ${propEnum} = "PROP_${propNameCaps}"; push(@cBodyProperties, " ${propEnum},\n"); my $propType = $attribute->signature->type; my ${propGType} = decamelize($propType); my ${ucPropGType} = uc($propGType); my $gtype = GetGValueTypeName($propType); my $gparamflag = "WEBKIT_PARAM_READABLE"; my $writeable = IsPropertyWriteable($attribute); my $mutableString = "read-only"; my $hasCustomSetter = $attribute->signature->extendedAttributes->{"CustomSetter"}; if ($writeable && $hasCustomSetter) { $mutableString = "read-only (due to custom functions needed in webkitdom)"; } elsif ($writeable) { $gparamflag = "WEBKIT_PARAM_READWRITE"; $mutableString = "read-write"; } my $getterFunctionName = "webkit_dom_${decamelizeInterfaceName}_get_" . $propFunctionName; if (FunctionUsedToNotRaiseException($getterFunctionName)) { $getterFunctionName = $getterFunctionName . "_with_error"; } my @getterArguments = (); push(@getterArguments, "self"); push(@getterArguments, "nullptr") if $hasGetterException || FunctionUsedToRaiseException($getterFunctionName); if (grep {$_ eq $attribute} @writeableProperties) { my $setterFunctionName = "webkit_dom_${decamelizeInterfaceName}_set_" . $propFunctionName; if (FunctionUsedToNotRaiseException($setterFunctionName)) { $setterFunctionName = $setterFunctionName . "_with_error"; } my @setterArguments = (); push(@setterArguments, "self, g_value_get_$gtype(value)"); push(@setterArguments, "nullptr") if $hasSetterException || FunctionUsedToRaiseException($setterFunctionName); push(@txtSetProps, " case ${propEnum}:\n"); push(@txtSetProps, " " . $setterFunctionName . "(" . join(", ", @setterArguments) . ");\n"); push(@txtSetProps, " break;\n"); } push(@txtGetProps, " case ${propEnum}:\n"); my $postConvertFunction = ""; if ($gtype eq "string") { push(@txtGetProps, " g_value_take_string(value, " . $getterFunctionName . "(" . join(", ", @getterArguments) . "));\n"); } else { push(@txtGetProps, " g_value_set_$gtype(value, " . $getterFunctionName . "(" . join(", ", @getterArguments) . "));\n"); } push(@txtGetProps, " break;\n"); my %parameterSpecOptions = ("int" => [ "G_MININT", "G_MAXINT", "0" ], "int8" => [ "G_MININT8", "G_MAXINT8", "0" ], "boolean" => [ "FALSE" ], "float" => [ "-G_MAXFLOAT", "G_MAXFLOAT", "0" ], "double" => [ "-G_MAXDOUBLE", "G_MAXDOUBLE", "0" ], "uint64" => [ "0", "G_MAXUINT64", "0" ], "long" => [ "G_MINLONG", "G_MAXLONG", "0" ], "int64" => [ "G_MININT64", "G_MAXINT64", "0" ], "ulong" => [ "0", "G_MAXULONG", "0" ], "uint" => [ "0", "G_MAXUINT", "0" ], "uint8" => [ "0", "G_MAXUINT8", "0" ], "ushort" => [ "0", "G_MAXUINT16", "0" ], "uchar" => [ "G_MININT8", "G_MAXINT8", "0" ], "char" => [ "0", "G_MAXUINT8", "0" ], "string" => [ '""', ], "object" => [ "WEBKIT_DOM_TYPE_${ucPropGType}" ]); my $extraParameters = join(", ", @{$parameterSpecOptions{$gtype}}); my $glibTypeName = GetGlibTypeName($propType); $propName =~ s/_/-/g; my $txtInstallProp = << "EOF"; g_object_class_install_property( gobjectClass, $propEnum, g_param_spec_$gtype( "$propName", "$interfaceName:$propName", "$mutableString $glibTypeName $interfaceName:$propName", $extraParameters, $gparamflag)); EOF push(@txtInstallProps, $txtInstallProp); } sub GenerateProperties { my ($object, $interfaceName, $interface) = @_; my $decamelize = decamelize($interfaceName); my $clsCaps = uc($decamelize); my $lowerCaseIfaceName = "webkit_dom_$decamelize"; my $parentImplClassName = GetParentImplClassName($interface); my $conditionGuardStart = ""; my $conditionGuardEnd = ""; my $conditionalString = $codeGenerator->GenerateConditionalString($interface); if ($conditionalString) { $conditionGuardStart = "#if ${conditionalString}"; $conditionGuardEnd = "#endif // ${conditionalString}"; } # Properties my $implContent = ""; my @readableProperties = grep { IsPropertyReadable($_) } @{$interface->attributes}; my @writeableProperties = grep { IsPropertyWriteable($_) } @{$interface->attributes}; my $numProperties = scalar @readableProperties; # Properties if ($numProperties > 0) { $implContent = << "EOF"; enum { PROP_0, EOF push(@cBodyProperties, $implContent); push(@txtGetProps, "static void ${lowerCaseIfaceName}_get_property(GObject* object, guint propertyId, GValue* value, GParamSpec* pspec)\n"); push(@txtGetProps, "{\n"); push(@txtGetProps, " ${className}* self = WEBKIT_DOM_${clsCaps}(object);\n"); push(@txtGetProps, "\n"); push(@txtGetProps, " switch (propertyId) {\n"); if (scalar @writeableProperties > 0) { push(@txtSetProps, "static void ${lowerCaseIfaceName}_set_property(GObject* object, guint propertyId, const GValue* value, GParamSpec* pspec)\n"); push(@txtSetProps, "{\n"); push(@txtSetProps, " ${className}* self = WEBKIT_DOM_${clsCaps}(object);\n"); push(@txtSetProps, "\n"); push(@txtSetProps, " switch (propertyId) {\n"); } foreach my $attribute (@readableProperties) { GenerateProperty($attribute, $interfaceName, \@writeableProperties, $interface); } push(@cBodyProperties, "};\n\n"); $txtGetProp = << "EOF"; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec); break; } } EOF push(@txtGetProps, $txtGetProp); if (scalar @writeableProperties > 0) { $txtSetProps = << "EOF"; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec); break; } } EOF push(@txtSetProps, $txtSetProps); } } # Do not insert extra spaces when interpolating array variables $" = ""; if ($parentImplClassName eq "Object") { $implContent = << "EOF"; static void ${lowerCaseIfaceName}_finalize(GObject* object) { ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(object); $conditionGuardStart WebKit::DOMObjectCache::forget(priv->coreObject.get()); $conditionGuardEnd priv->~${className}Private(); G_OBJECT_CLASS(${lowerCaseIfaceName}_parent_class)->finalize(object); } EOF push(@cBodyProperties, $implContent); } if ($numProperties > 0) { if (scalar @writeableProperties > 0) { push(@cBodyProperties, @txtSetProps); push(@cBodyProperties, "\n"); } push(@cBodyProperties, @txtGetProps); push(@cBodyProperties, "\n"); } # Add a constructor implementation only for direct subclasses of Object to make sure # that the WebCore wrapped object is added only once to the DOM cache. The DOM garbage # collector works because Node is a direct subclass of Object and the version of # DOMObjectCache::put() that receives a Node (which is the one setting the frame) is # always called for DOM objects derived from Node. if ($parentImplClassName eq "Object") { $implContent = << "EOF"; static GObject* ${lowerCaseIfaceName}_constructor(GType type, guint constructPropertiesCount, GObjectConstructParam* constructProperties) { GObject* object = G_OBJECT_CLASS(${lowerCaseIfaceName}_parent_class)->constructor(type, constructPropertiesCount, constructProperties); $conditionGuardStart ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(object); priv->coreObject = static_cast(WEBKIT_DOM_OBJECT(object)->coreObject); WebKit::DOMObjectCache::put(priv->coreObject.get(), object); $conditionGuardEnd return object; } EOF push(@cBodyProperties, $implContent); } $implContent = << "EOF"; static void ${lowerCaseIfaceName}_class_init(${className}Class* requestClass) { EOF push(@cBodyProperties, $implContent); if ($parentImplClassName eq "Object" || $numProperties > 0) { push(@cBodyProperties, " GObjectClass* gobjectClass = G_OBJECT_CLASS(requestClass);\n"); if ($parentImplClassName eq "Object") { push(@cBodyProperties, " g_type_class_add_private(gobjectClass, sizeof(${className}Private));\n"); push(@cBodyProperties, " gobjectClass->constructor = ${lowerCaseIfaceName}_constructor;\n"); push(@cBodyProperties, " gobjectClass->finalize = ${lowerCaseIfaceName}_finalize;\n"); } if ($numProperties > 0) { if (scalar @writeableProperties > 0) { push(@cBodyProperties, " gobjectClass->set_property = ${lowerCaseIfaceName}_set_property;\n"); } push(@cBodyProperties, " gobjectClass->get_property = ${lowerCaseIfaceName}_get_property;\n"); push(@cBodyProperties, "\n"); push(@cBodyProperties, @txtInstallProps); } } else { push(@cBodyProperties, " UNUSED_PARAM(requestClass);\n"); } $implContent = << "EOF"; } static void ${lowerCaseIfaceName}_init(${className}* request) { EOF push(@cBodyProperties, $implContent); if ($parentImplClassName eq "Object") { $implContent = << "EOF"; ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(request); new (priv) ${className}Private(); EOF push(@cBodyProperties, $implContent); } else { push(@cBodyProperties, " UNUSED_PARAM(request);\n"); } $implContent = << "EOF"; } EOF push(@cBodyProperties, $implContent); } sub GenerateConstants { my ($interface, $prefix) = @_; my $isStableClass = scalar(@stableSymbols); if (@{$interface->constants}) { my @constants = @{$interface->constants}; foreach my $constant (@constants) { my $conditionalString = $codeGenerator->GenerateConditionalString($constant); my $constantName = $prefix . $constant->name; my $constantValue = $constant->value; my $stableSymbol = grep {$_ =~ /^\Q$constantName/} @stableSymbols; my $stableSymbolVersion; if ($stableSymbol) { ($dummy, $stableSymbolVersion) = split('@', $stableSymbol, 2); push(@symbols, "$constantName\n"); } my @constantHeader = (); push(@constantHeader, "#if ${conditionalString}") if $conditionalString; push(@constantHeader, "/**"); push(@constantHeader, " * ${constantName}:"); if ($stableSymbolVersion) { push(@constantHeader, " * Since: ${stableSymbolVersion}"); } push(@constantHeader, " */"); push(@constantHeader, "#define $constantName $constantValue"); push(@constantHeader, "#endif /* ${conditionalString} */") if $conditionalString; push(@constantHeader, "\n"); if ($stableSymbol or !$isStableClass) { push(@hBody, join("\n", @constantHeader)); } else { push(@hBodyUnstable, join("\n", @constantHeader)); } } } } sub GenerateHeader { my ($object, $interfaceName, $parentClassName, $interface) = @_; my $implContent = ""; # Add the default header template @hPrefix = split("\r", $licenceTemplate); push(@hPrefix, "\n"); my $isStableClass = scalar(@stableSymbols); if ($isStableClass) { # Force single header include. my $headerCheck = << "EOF"; #if !defined(__WEBKITDOM_H_INSIDE__) && !defined(BUILDING_WEBKIT) #error "Only can be included directly." #endif EOF push(@hPrefix, $headerCheck); } # Header guard my $guard = $className . "_h"; @hPrefixGuard = << "EOF"; #ifndef $guard #define $guard EOF if (!$isStableClass) { push(@hPrefixGuard, "#ifdef WEBKIT_DOM_USE_UNSTABLE_API\n\n"); } $implContent = << "EOF"; G_BEGIN_DECLS EOF push(@hBodyPre, $implContent); my $decamelize = decamelize($interfaceName); my $clsCaps = uc($decamelize); my $lowerCaseIfaceName = "webkit_dom_$decamelize"; $implContent = << "EOF"; #define WEBKIT_DOM_TYPE_${clsCaps} (${lowerCaseIfaceName}_get_type()) #define WEBKIT_DOM_${clsCaps}(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_DOM_TYPE_${clsCaps}, ${className})) #define WEBKIT_DOM_${clsCaps}_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), WEBKIT_DOM_TYPE_${clsCaps}, ${className}Class) #define WEBKIT_DOM_IS_${clsCaps}(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_DOM_TYPE_${clsCaps})) #define WEBKIT_DOM_IS_${clsCaps}_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), WEBKIT_DOM_TYPE_${clsCaps})) #define WEBKIT_DOM_${clsCaps}_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), WEBKIT_DOM_TYPE_${clsCaps}, ${className}Class)) EOF push(@hBody, $implContent); if ($isStableClass) { push(@symbols, "GType ${lowerCaseIfaceName}_get_type(void)\n"); } GenerateConstants($interface, "WEBKIT_DOM_${clsCaps}_"); $implContent = << "EOF"; struct _${className} { ${parentClassName} parent_instance; }; struct _${className}Class { ${parentClassName}Class parent_class; }; EOF push(@hBody, $implContent); push(@hBody, "WEBKIT_API GType\n${lowerCaseIfaceName}_get_type(void);\n"); push(@hBody, "\n"); } sub GetGReturnMacro { my ($paramName, $paramIDLType, $returnType, $functionName) = @_; my $condition; if ($paramIDLType eq "GError") { $condition = "!$paramName || !*$paramName"; } elsif (IsGDOMClassType($paramIDLType)) { my $paramTypeCaps = uc(decamelize($paramIDLType)); $condition = "WEBKIT_DOM_IS_${paramTypeCaps}($paramName)"; if (ParamCanBeNull($functionName, $paramName)) { $condition = "!$paramName || $condition"; } } else { if (ParamCanBeNull($functionName, $paramName)) { return ""; } $condition = "$paramName"; } my $macro; if ($returnType ne "void") { $defaultReturn = $returnType eq "gboolean" ? "FALSE" : 0; $macro = " g_return_val_if_fail($condition, $defaultReturn);\n"; } else { $macro = " g_return_if_fail($condition);\n"; } return $macro; } sub ParamCanBeNull { my($functionName, $paramName) = @_; if (defined($functionName)) { return scalar(grep {$_ eq $paramName} @{$canBeNullParams->{$functionName}}); } return 0; } sub GetFunctionSignatureName { my ($interfaceName, $function) = @_; my $signatureName = decamelize($function->signature->name); return $signatureName if $signatureName ne "type"; # For HTML type attribute use type_attr. # Example: webkit_dom_html_link_element_get_type_attr() my $contentAttributeName = $codeGenerator->ContentAttributeName(\%implIncludes, $interfaceName, $function); if ($contentAttributeName) { return "type_attr" if $contentAttributeName eq "WebCore::HTMLNames::typeAttr"; } # For methods returning a MIME type use content_type. # Examples: webkit_dom_style_sheet_get_content_type(), webkit_dom_dom_mime_type_get_content_type() if ($interfaceName eq "StyleSheet" || $interfaceName eq "DOMMimeType") { return "content_type"; } # For HTMLFieldSet use field_set_type. # Example: webkit_dom_html_field_set_element_get_field_set_type() if ($interfaceName eq "HTMLFieldSet") { return "field_set_type"; } # For any other cases use the last word of the interface name. # Examples: webkit_dom_blob_get_blob_type(), webkit_dom_event_get_event_type() my @nameTokens = split('_', decamelize($interfaceName)); my $name = $nameTokens[-1]; # If the last word is element and there are more words, use the previous one. # Example: webkit_dom_html_button_element_get_button_type() if (scalar(@nameTokens) > 1 && $name eq "element") { $name = $nameTokens[-2]; } return "${name}_type"; } sub GetTransferTypeForReturnType { my $returnType = shift; # Node is always transfer none. return "none" if $returnType eq "Node"; # Any base class but Node is transfer full. return "full" if IsBaseType($returnType); # Any other class not derived from Node is transfer full. return "full" if $transferFullTypeHash{$returnType}; return "none"; } sub GetEffectiveFunctionName { my $functionName = shift; # Rename webkit_dom_[document|element]_get_elements_by_tag_name* and webkit_dom_[document|element]_get_elements_by_class_name # functions since they were changed to return a WebKitDOMHTMLCollection instead of a WebKitDOMNodeList in # r188809 and r188735. The old methods are now manually added as deprecated. if ($functionName eq "webkit_dom_document_get_elements_by_tag_name" || $functionName eq "webkit_dom_document_get_elements_by_tag_name_ns" || $functionName eq "webkit_dom_document_get_elements_by_class_name" || $functionName eq "webkit_dom_element_get_elements_by_tag_name" || $functionName eq "webkit_dom_element_get_elements_by_tag_name_ns" || $functionName eq "webkit_dom_element_get_elements_by_class_name") { return $functionName . "_as_html_collection"; } return $functionName; } sub FunctionUsedToRaiseException { my $functionName = shift; return $functionName eq "webkit_dom_attr_set_value" || $functionName eq "webkit_dom_character_data_append_data" || $functionName eq "webkit_dom_character_data_set_data" || $functionName eq "webkit_dom_document_create_node_iterator" || $functionName eq "webkit_dom_document_create_tree_walker" || $functionName eq "webkit_dom_node_iterator_next_node" || $functionName eq "webkit_dom_node_iterator_previous_node" || $functionName eq "webkit_dom_range_clone_range" || $functionName eq "webkit_dom_range_collapse" || $functionName eq "webkit_dom_range_detach" || $functionName eq "webkit_dom_range_get_common_ancestor_container" || $functionName eq "webkit_dom_range_get_end_container" || $functionName eq "webkit_dom_range_get_start_container" || $functionName eq "webkit_dom_range_get_collapsed" || $functionName eq "webkit_dom_range_get_end_offset" || $functionName eq "webkit_dom_range_get_start_offset" || $functionName eq "webkit_dom_range_to_string" || $functionName eq "webkit_dom_tree_walker_set_current_node"; } sub FunctionUsedToNotRaiseException { my $functionName = shift; return $functionName eq "webkit_dom_document_set_title" || $functionName eq "webkit_dom_html_title_element_set_text" || $functionName eq "webkit_dom_node_clone_node"; } sub GenerateFunction { my ($object, $interfaceName, $function, $prefix, $parentNode) = @_; my $decamelize = decamelize($interfaceName); if (SkipFunction($object, $function, $parentNode, $decamelize, $prefix)) { return; } my $functionSigType = $prefix eq "set_" ? "void" : $function->signature->type; my $functionSigName = GetFunctionSignatureName($interfaceName, $function); my $functionName = GetEffectiveFunctionName("webkit_dom_" . $decamelize . "_" . $prefix . $functionSigName); my $returnType = GetGlibTypeName($functionSigType); my $returnValueIsGDOMType = IsGDOMClassType($functionSigType); my $raisesException = $function->signature->extendedAttributes->{"RaisesException"}; # If a method used to raise an exception, but was changed to not raise it anymore, the # API changes because we use a explicit GError parameter to handle the exceptions. # In this case, it's better to keep the GError parameter even if it's unused to keep # the API compatibility. my $usedToRaiseException = FunctionUsedToRaiseException($functionName); # If a method didn't raise an exception but was changed to raise exceptions, the API # changes because we use a explicit GError parameter to handle the exceptions. # In this case, we add _with_error suffix and the previous version simply ignores the error. if (FunctionUsedToNotRaiseException($functionName)) { $functionName = $functionName . "_with_error"; } my $conditionalString = $codeGenerator->GenerateConditionalString($function->signature); my $parentConditionalString = $codeGenerator->GenerateConditionalString($parentNode); my @conditionalWarn = GenerateConditionalWarning($function->signature); my @parentConditionalWarn = GenerateConditionalWarning($parentNode); my $functionSig = "${className}* self"; my $symbolSig = "${className}*"; my $hasVariadic = 0; my @callImplParams; foreach my $param (@{$function->parameters}) { my $paramIDLType = $param->type; my $arrayOrSequenceType = $codeGenerator->GetArrayOrSequenceType($paramIDLType); $paramIDLType = $arrayOrSequenceType if $arrayOrSequenceType ne ""; my $paramType = GetGlibTypeName($paramIDLType); my $const = $paramType eq "gchar*" ? "const " : ""; my $paramName = $param->name; if ($param->isVariadic) { $hasVariadic = 1; } else { $functionSig .= ", ${const}$paramType $paramName"; $symbolSig .= ", ${const}$paramType"; } my $paramIsGDOMType = IsGDOMClassType($paramIDLType); if ($paramIsGDOMType) { if ($paramIDLType ne "any") { $implIncludes{"WebKitDOM${paramIDLType}Private.h"} = 1; } } if ($paramIsGDOMType || ($paramIDLType eq "DOMString") || $param->isVariadic) { $paramName = "converted" . $codeGenerator->WK_ucfirst($paramName); $paramName = "*$paramName" if $codeGenerator->ShouldPassWrapperByReference($param, $parentNode); $paramName = "WTFMove($paramName)" if $param->isVariadic; } if ($paramIDLType eq "NodeFilter" || $paramIDLType eq "XPathNSResolver") { $paramName = "WTF::getPtr(" . $paramName . ")"; } if ($paramIDLType eq "SerializedScriptValue") { $implIncludes{"SerializedScriptValue.h"} = 1; $paramName = "WebCore::SerializedScriptValue::create(WTF::String::fromUTF8(" . $paramName . "))"; } push(@callImplParams, $paramName); } if ($returnType ne "void" && $returnValueIsGDOMType && $functionSigType ne "any") { $implIncludes{"WebKitDOM${functionSigType}Private.h"} = 1; } $functionSig .= ", GError** error" if $raisesException || $usedToRaiseException; $symbolSig .= ", GError**" if $raisesException || $usedToRaiseException; if ($hasVariadic) { my $param = @{$function->parameters}[-1]; if ($codeGenerator->IsNonPointerType($param->type)) { my $paramName = $param->name; $functionSig .= ", guint n_$paramName"; $symbolSig .= ", guint"; } $functionSig .= ", ..."; $symbolSig .= ", ..."; } my $symbol = "$returnType $functionName($symbolSig)"; my $isStableClass = scalar(@stableSymbols); my ($stableSymbol) = grep {$_ =~ /^\Q$symbol/} @stableSymbols; my $stableSymbolVersion; if ($stableSymbol and $isStableClass) { ($dummy, $stableSymbolVersion) = split('@', $stableSymbol, 2); push(@symbols, "$symbol\n"); } my @functionHeader = (); # Insert introspection annotations push(@functionHeader, "/**"); push(@functionHeader, " * ${functionName}:"); push(@functionHeader, " * \@self: A #${className}"); foreach my $param (@{$function->parameters}) { if ($param->isVariadic) { last; } my $paramIDLType = $param->type; my $arrayOrSequenceType = $codeGenerator->GetArrayOrSequenceType($paramIDLType); $paramIDLType = $arrayOrSequenceType if $arrayOrSequenceType ne ""; my $paramType = GetGlibTypeName($paramIDLType); # $paramType can have a trailing * in some cases $paramType =~ s/\*$//; my $paramName = $param->name; my $paramAnnotations = ""; if (ParamCanBeNull($functionName, $paramName)) { $paramAnnotations = " (allow-none):"; } push(@functionHeader, " * \@${paramName}:${paramAnnotations} A #${paramType}"); } push(@functionHeader, " * \@error: #GError") if $raisesException || $usedToRaiseException; if ($hasVariadic) { my $param = @{$function->parameters}[-1]; my $paramName = $param->name; my $paramType = GetGlibTypeName($param->type); $paramType =~ s/\*$//; if ($codeGenerator->IsNonPointerType($param->type)) { push(@functionHeader, " * \@n_${paramName}: number of ${paramName} that will be passed"); push(@functionHeader, " * \@...: list of #${paramType}"); } else { push(@functionHeader, " * \@...: list of #${paramType} ended by %NULL."); } } push(@functionHeader, " *"); my $returnTypeName = $returnType; my $hasReturnTag = 0; $returnTypeName =~ s/\*$//; if ($returnValueIsGDOMType) { my $transferType = GetTransferTypeForReturnType($functionSigType); push(@functionHeader, " * Returns: (transfer $transferType): A #${returnTypeName}"); $hasReturnTag = 1; } elsif ($returnType ne "void") { push(@functionHeader, " * Returns: A #${returnTypeName}"); $hasReturnTag = 1; } if (!$stableSymbol) { if ($hasReturnTag) { push(@functionHeader, " *"); } push(@functionHeader, " * Stability: Unstable"); } elsif ($stableSymbolVersion) { if ($hasReturnTag) { push(@functionHeader, " *"); } push(@functionHeader, " * Since: ${stableSymbolVersion}"); } push(@functionHeader, "**/"); push(@functionHeader, "WEBKIT_API $returnType\n$functionName($functionSig);"); push(@functionHeader, "\n"); if ($stableSymbol or !$isStableClass) { push(@hBody, join("\n", @functionHeader)); } else { push(@hBodyUnstable, join("\n", @functionHeader)); } push(@cBody, "$returnType $functionName($functionSig)\n{\n"); push(@cBody, "#if ${parentConditionalString}\n") if $parentConditionalString; push(@cBody, "#if ${conditionalString}\n") if $conditionalString; push(@cBody, " WebCore::JSMainThreadNullState state;\n"); # g_return macros to check parameters of public methods. $gReturnMacro = GetGReturnMacro("self", $interfaceName, $returnType); push(@cBody, $gReturnMacro); foreach my $param (@{$function->parameters}) { my $paramName = $param->name; my $paramIDLType = $param->type; my $paramTypeIsPointer = !$codeGenerator->IsNonPointerType($paramIDLType); if ($paramTypeIsPointer && !$param->isVariadic) { $gReturnMacro = GetGReturnMacro($paramName, $paramIDLType, $returnType, $functionName); push(@cBody, $gReturnMacro); } } if ($raisesException) { $gReturnMacro = GetGReturnMacro("error", "GError", $returnType); push(@cBody, $gReturnMacro); } elsif ($usedToRaiseException) { push(@cBody, " UNUSED_PARAM(error);\n"); } # The WebKit::core implementations check for null already; no need to duplicate effort. push(@cBody, " WebCore::${interfaceName}* item = WebKit::core(self);\n"); $returnParamName = ""; my $currentParameterIndex = 0; foreach my $param (@{$function->parameters}) { my $paramIDLType = $param->type; my $paramName = $param->name; my $paramType = GetGlibTypeName($paramIDLType); my $paramIsGDOMType = IsGDOMClassType($paramIDLType); my $paramTypeIsPointer = !$codeGenerator->IsNonPointerType($paramIDLType); my $convertedParamName = "converted" . $codeGenerator->WK_ucfirst($paramName); my $paramCoreType = $paramType; my $paramConversionFunction = ""; if ($paramIDLType eq "DOMString") { $paramCoreType = "WTF::String"; $paramConversionFunction = "WTF::String::fromUTF8"; } elsif ($paramIDLType eq "NodeFilter" || $paramIDLType eq "XPathNSResolver") { $paramCoreType = "RefPtr"; $paramConversionFunction = "WebKit::core" } elsif ($paramIsGDOMType) { $paramCoreType = "WebCore::${paramIDLType}*"; $paramConversionFunction = "WebKit::core" } if ($param->isVariadic) { my $previousParamName; if ($raisesException) { $previousParamName = "error"; } elsif ($currentParameterIndex == 0) { $previousParamName = "self"; } else { $previousParamName = @{$function->parameters}[$currentParameterIndex - 1]->name; } push(@cBody, " va_list variadicParameterList;\n"); push(@cBody, " Vector<$paramCoreType> $convertedParamName;\n"); push(@cBody, " va_start(variadicParameterList, $previousParamName);\n"); if ($paramTypeIsPointer) { push(@cBody, " while ($paramType variadicParameter = va_arg(variadicParameterList, $paramType))\n"); push(@cBody, " ${convertedParamName}.append(${paramConversionFunction}(variadicParameter));\n"); } else { push(@cBody, " ${convertedParamName}.reserveInitialCapacity(n_$paramName);\n"); push(@cBody, " for (unsigned i = 0; i < n_$paramName; ++i) {\n"); push(@cBody, " ${convertedParamName}.uncheckedAppend(va_arg(variadicParameterList, $paramType));\n"); } push(@cBody, " va_end(variadicParameterList);\n"); } elsif ($paramCoreType ne $paramType) { push(@cBody, " $paramCoreType $convertedParamName = ${paramConversionFunction}($paramName);\n"); } $returnParamName = $convertedParamName if $param->extendedAttributes->{"CustomReturn"}; $currentParameterIndex++; } my $assign = ""; my $assignPre = ""; my $assignPost = ""; # We need to special-case these Node methods because their C++ # signature is different from what we'd expect given their IDL # description; see Node.h. my $functionHasCustomReturn = $functionName eq "webkit_dom_node_append_child" || $functionName eq "webkit_dom_node_insert_before" || $functionName eq "webkit_dom_node_replace_child" || $functionName eq "webkit_dom_node_remove_child"; if ($returnType ne "void" && !$functionHasCustomReturn) { if ($returnValueIsGDOMType) { $assign = "RefPtr gobjectResult = "; $assignPre = "WTF::getPtr("; $assignPost = ")"; } else { $assign = "${returnType} result = "; if ($function->signature->isNullable) { # FIXME: Returning 0 is probably not right for all nullable attribute values. # We may want to handle this the way we do in the Objective-C bindings: not # handle it at all, and not expose any nullables. $assignPost = ".valueOr(0)"; } } if ($functionSigType eq "SerializedScriptValue") { $assignPre = "convertToUTF8String("; $assignPost = "->toString())"; } } if ($raisesException) { push(@cBody, " WebCore::ExceptionCode ec = 0;\n"); push(@callImplParams, "ec"); } my $functionImplementationName = $function->signature->extendedAttributes->{"ImplementedAs"} || $function->signature->name; if ($functionHasCustomReturn) { push(@cBody, " bool ok = item->${functionImplementationName}(" . join(", ", @callImplParams) . ");\n"); my $customNodeAppendChild = << "EOF"; if (ok) return WebKit::kit($returnParamName); EOF push(@cBody, $customNodeAppendChild); if($raisesException) { my $exceptionHandling = << "EOF"; WebCore::ExceptionCodeDescription ecdesc(ec); g_set_error_literal(error, g_quark_from_string("WEBKIT_DOM"), ecdesc.code, ecdesc.name); EOF push(@cBody, $exceptionHandling); } push(@cBody, " return 0;\n"); push(@cBody, "}\n\n"); return; } elsif ($functionSigType eq "DOMString") { my $getterContentHead; if ($prefix) { my ($functionName, @arguments) = $codeGenerator->GetterExpression(\%implIncludes, $interfaceName, $function); push(@arguments, @callImplParams); if ($function->signature->extendedAttributes->{"ImplementedBy"}) { my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"}; $implIncludes{"${implementedBy}.h"} = 1; unshift(@arguments, "item"); $functionName = "WebCore::${implementedBy}::${functionName}"; } else { $functionName = "item->${functionName}"; } $getterContentHead = "${assign}convertToUTF8String(${functionName}(" . join(", ", @arguments) . "));\n"; } else { my @arguments = @callImplParams; if ($function->signature->extendedAttributes->{"ImplementedBy"}) { my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"}; $implIncludes{"${implementedBy}.h"} = 1; unshift(@arguments, "item"); $getterContentHead = "${assign}convertToUTF8String(WebCore::${implementedBy}::${functionImplementationName}(" . join(", ", @arguments) . "));\n"; } else { $getterContentHead = "${assign}convertToUTF8String(item->${functionImplementationName}(" . join(", ", @arguments) . "));\n"; } } push(@cBody, " ${getterContentHead}"); } else { my $contentHead; if ($prefix eq "get_") { my ($functionName, @arguments) = $codeGenerator->GetterExpression(\%implIncludes, $interfaceName, $function); push(@arguments, @callImplParams); if ($function->signature->extendedAttributes->{"ImplementedBy"}) { my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"}; $implIncludes{"${implementedBy}.h"} = 1; unshift(@arguments, "*item"); $functionName = "WebCore::${implementedBy}::${functionName}"; } else { $functionName = "item->${functionName}"; } $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . ")${assignPost};\n"; } elsif ($prefix eq "set_") { my ($functionName, @arguments) = $codeGenerator->SetterExpression(\%implIncludes, $interfaceName, $function); push(@arguments, @callImplParams); if ($function->signature->extendedAttributes->{"ImplementedBy"}) { my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"}; $implIncludes{"${implementedBy}.h"} = 1; unshift(@arguments, "*item"); $functionName = "WebCore::${implementedBy}::${functionName}"; $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . ")${assignPost};\n"; } else { $functionName = "item->${functionName}"; $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . ")${assignPost};\n"; } } else { my @arguments = @callImplParams; if ($function->signature->extendedAttributes->{"ImplementedBy"}) { my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"}; $implIncludes{"${implementedBy}.h"} = 1; unshift(@arguments, "*item"); $contentHead = "${assign}${assignPre}WebCore::${implementedBy}::${functionImplementationName}(" . join(", ", @arguments) . ")${assignPost};\n"; } else { $contentHead = "${assign}${assignPre}item->${functionImplementationName}(" . join(", ", @arguments) . ")${assignPost};\n"; } } push(@cBody, " ${contentHead}"); if($raisesException) { my $exceptionHandling = << "EOF"; if (ec) { WebCore::ExceptionCodeDescription ecdesc(ec); g_set_error_literal(error, g_quark_from_string("WEBKIT_DOM"), ecdesc.code, ecdesc.name); } EOF push(@cBody, $exceptionHandling); } } if ($returnType ne "void" && !$functionHasCustomReturn) { if ($functionSigType ne "any") { if ($returnValueIsGDOMType) { push(@cBody, " return WebKit::kit(gobjectResult.get());\n"); } else { push(@cBody, " return result;\n"); } } else { push(@cBody, " return 0; // TODO: return canvas object\n"); } } if ($conditionalString) { push(@cBody, "#else\n"); push(@cBody, " UNUSED_PARAM(self);\n"); foreach my $param (@{$function->parameters}) { push(@cBody, " UNUSED_PARAM(" . $param->name . ");\n"); } push(@cBody, " UNUSED_PARAM(error);\n") if $raisesException; push(@cBody, @conditionalWarn) if scalar(@conditionalWarn); if ($returnType ne "void") { if ($codeGenerator->IsNonPointerType($functionSigType)) { push(@cBody, " return static_cast<${returnType}>(0);\n"); } else { push(@cBody, " return 0;\n"); } } push(@cBody, "#endif /* ${conditionalString} */\n"); } if ($parentConditionalString) { push(@cBody, "#else\n"); push(@cBody, " UNUSED_PARAM(self);\n"); foreach my $param (@{$function->parameters}) { push(@cBody, " UNUSED_PARAM(" . $param->name . ");\n"); } push(@cBody, " UNUSED_PARAM(error);\n") if $raisesException; push(@cBody, @parentConditionalWarn) if scalar(@parentConditionalWarn); if ($returnType ne "void") { if ($codeGenerator->IsNonPointerType($functionSigType)) { push(@cBody, " return static_cast<${returnType}>(0);\n"); } else { push(@cBody, " return 0;\n"); } } push(@cBody, "#endif /* ${parentConditionalString} */\n"); } push(@cBody, "}\n\n"); } sub ClassHasFunction { my ($class, $name) = @_; foreach my $function (@{$class->functions}) { if ($function->signature->name eq $name) { return 1; } } return 0; } sub GenerateFunctions { my ($object, $interfaceName, $interface) = @_; foreach my $function (@{$interface->functions}) { $object->GenerateFunction($interfaceName, $function, "", $interface); } TOP: foreach my $attribute (@{$interface->attributes}) { if (SkipAttribute($attribute)) { next TOP; } my $attrNameUpper = $codeGenerator->WK_ucfirst($attribute->signature->name); my $getname = "get${attrNameUpper}"; my $setname = "set${attrNameUpper}"; if (ClassHasFunction($interface, $getname) || ClassHasFunction($interface, $setname)) { # Very occasionally an IDL file defines getter/setter functions for one of its # attributes; in this case we don't need to autogenerate the getter/setter. next TOP; } # Generate an attribute getter. For an attribute "foo", this is a function named # "get_foo" which calls a DOM class method named foo(). my $function = new domFunction(); $function->signature($attribute->signature); $function->signature->extendedAttributes({%{$attribute->signature->extendedAttributes}}); if ($attribute->signature->extendedAttributes->{"GetterRaisesException"}) { $function->signature->extendedAttributes->{"RaisesException"} = "VALUE_IS_MISSING"; } $object->GenerateFunction($interfaceName, $function, "get_", $interface); # FIXME: We are not generating setters for 'Replaceable' # attributes now, but we should somehow. my $custom = $attribute->signature->extendedAttributes->{"CustomSetter"}; if ($attribute->isReadOnly || $attribute->signature->extendedAttributes->{"Replaceable"} || $attribute->signature->extendedAttributes->{"CallWith"} || $attribute->signature->extendedAttributes->{"SetterCallWith"} || $custom) { next TOP; } # Generate an attribute setter. For an attribute, "foo", this is a function named # "set_foo" which calls a DOM class method named setFoo(). $function = new domFunction(); $function->signature(new domSignature()); $function->signature->name($attribute->signature->name); $function->signature->type($attribute->signature->type); $function->signature->extendedAttributes({%{$attribute->signature->extendedAttributes}}); my $param = new domSignature(); $param->name("value"); $param->type($attribute->signature->type); $param->isNullable($attribute->signature->isNullable); my %attributes = (); $param->extendedAttributes(\%attributes); my $arrayRef = $function->parameters; push(@$arrayRef, $param); if ($attribute->signature->extendedAttributes->{"SetterRaisesException"}) { $function->signature->extendedAttributes->{"RaisesException"} = "VALUE_IS_MISSING"; } else { delete $function->signature->extendedAttributes->{"RaisesException"}; } $object->GenerateFunction($interfaceName, $function, "set_", $interface); } } sub ImplementsInterface { my $interface = shift; my $implementInterface = shift; return $codeGenerator->InheritsInterface($interface, $implementInterface); } sub GenerateCFile { my ($object, $interfaceName, $parentClassName, $parentGObjType, $interface) = @_; if (ImplementsInterface($interface, "EventTarget")) { $object->GenerateEventTargetIface($interface); } my $implContent = ""; my $decamelize = decamelize($interfaceName); my $clsCaps = uc($decamelize); my $lowerCaseIfaceName = "webkit_dom_$decamelize"; my $parentImplClassName = GetParentImplClassName($interface); my $baseClassName = GetBaseClass($parentImplClassName, $interface); # Add a private struct only for direct subclasses of Object so that we can use RefPtr # for the WebCore wrapped object and make sure we only increment the reference counter once. if ($parentImplClassName eq "Object") { my $conditionalString = $codeGenerator->GenerateConditionalString($interface); push(@cStructPriv, "#define WEBKIT_DOM_${clsCaps}_GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE(obj, WEBKIT_DOM_TYPE_${clsCaps}, ${className}Private)\n\n"); push(@cStructPriv, "typedef struct _${className}Private {\n"); push(@cStructPriv, "#if ${conditionalString}\n") if $conditionalString; push(@cStructPriv, " RefPtr coreObject;\n"); push(@cStructPriv, "#endif // ${conditionalString}\n") if $conditionalString; push(@cStructPriv, "} ${className}Private;\n\n"); } $implContent = << "EOF"; ${defineTypeMacro}(${className}, ${lowerCaseIfaceName}, ${parentGObjType}${defineTypeInterfaceImplementation} EOF push(@cBodyProperties, $implContent); if ($parentImplClassName eq "Object") { push(@cBodyPriv, "${className}* kit(WebCore::$interfaceName* obj)\n"); push(@cBodyPriv, "{\n"); push(@cBodyPriv, " if (!obj)\n"); push(@cBodyPriv, " return 0;\n\n"); push(@cBodyPriv, " if (gpointer ret = DOMObjectCache::get(obj))\n"); push(@cBodyPriv, " return WEBKIT_DOM_${clsCaps}(ret);\n\n"); if (IsPolymorphic($interfaceName)) { push(@cBodyPriv, " return wrap(obj);\n"); } else { push(@cBodyPriv, " return wrap${interfaceName}(obj);\n"); } push(@cBodyPriv, "}\n\n"); } else { push(@cBodyPriv, "${className}* kit(WebCore::$interfaceName* obj)\n"); push(@cBodyPriv, "{\n"); if (!IsPolymorphic($baseClassName)) { push(@cBodyPriv, " if (!obj)\n"); push(@cBodyPriv, " return 0;\n\n"); push(@cBodyPriv, " if (gpointer ret = DOMObjectCache::get(obj))\n"); push(@cBodyPriv, " return WEBKIT_DOM_${clsCaps}(ret);\n\n"); push(@cBodyPriv, " return wrap${interfaceName}(obj);\n"); } else { push(@cBodyPriv, " return WEBKIT_DOM_${clsCaps}(kit(static_cast(obj)));\n"); } push(@cBodyPriv, "}\n\n"); } $implContent = << "EOF"; WebCore::${interfaceName}* core(${className}* request) { return request ? static_cast(WEBKIT_DOM_OBJECT(request)->coreObject) : 0; } ${className}* wrap${interfaceName}(WebCore::${interfaceName}* coreObject) { ASSERT(coreObject); return WEBKIT_DOM_${clsCaps}(g_object_new(WEBKIT_DOM_TYPE_${clsCaps}, "core-object", coreObject, nullptr)); } EOF push(@cBodyPriv, $implContent); $object->GenerateProperties($interfaceName, $interface); $object->GenerateFunctions($interfaceName, $interface); } sub GenerateEndHeader { my ($object) = @_; my $isStableClass = scalar(@stableSymbols); if (!$isStableClass) { push(@hPrefixGuardEnd, "#endif /* WEBKIT_DOM_USE_UNSTABLE_API */\n"); } #Header guard my $guard = $className . "_h"; push(@hBody, "G_END_DECLS\n\n"); push(@hPrefixGuardEnd, "#endif /* $guard */\n"); } sub IsPolymorphic { my $type = shift; return scalar(grep {$_ eq $type} qw(Blob Event HTMLCollection Node StyleSheet TextTrackCue)); } sub GenerateEventTargetIface { my $object = shift; my $interface = shift; my $interfaceName = $interface->name; my $decamelize = decamelize($interfaceName); my $conditionalString = $codeGenerator->GenerateConditionalString($interface); my @conditionalWarn = GenerateConditionalWarning($interface); $implIncludes{"GObjectEventListener.h"} = 1; $implIncludes{"WebKitDOMEventTarget.h"} = 1; $implIncludes{"WebKitDOMEventPrivate.h"} = 1; push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_dispatch_event(WebKitDOMEventTarget* target, WebKitDOMEvent* event, GError** error)\n{\n"); push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString; push(@cBodyProperties, " WebCore::Event* coreEvent = WebKit::core(event);\n"); push(@cBodyProperties, " WebCore::${interfaceName}* coreTarget = static_cast(WEBKIT_DOM_OBJECT(target)->coreObject);\n\n"); push(@cBodyProperties, " WebCore::ExceptionCode ec = 0;\n"); push(@cBodyProperties, " gboolean result = coreTarget->dispatchEventForBindings(coreEvent, ec);\n"); push(@cBodyProperties, " if (ec) {\n WebCore::ExceptionCodeDescription description(ec);\n"); push(@cBodyProperties, " g_set_error_literal(error, g_quark_from_string(\"WEBKIT_DOM\"), description.code, description.name);\n }\n"); push(@cBodyProperties, " return result;\n"); if ($conditionalString) { push(@cBodyProperties, "#else\n"); push(@cBodyProperties, " UNUSED_PARAM(target);\n"); push(@cBodyProperties, " UNUSED_PARAM(event);\n"); push(@cBodyProperties, " UNUSED_PARAM(error);\n"); push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn); push(@cBodyProperties, " return false;\n#endif // ${conditionalString}\n"); } push(@cBodyProperties, "}\n\n"); push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_add_event_listener(WebKitDOMEventTarget* target, const char* eventName, GClosure* handler, gboolean useCapture)\n{\n"); push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString; push(@cBodyProperties, " WebCore::${interfaceName}* coreTarget = static_cast(WEBKIT_DOM_OBJECT(target)->coreObject);\n"); push(@cBodyProperties, " return WebCore::GObjectEventListener::addEventListener(G_OBJECT(target), coreTarget, eventName, handler, useCapture);\n"); if ($conditionalString) { push(@cBodyProperties, "#else\n"); push(@cBodyProperties, " UNUSED_PARAM(target);\n"); push(@cBodyProperties, " UNUSED_PARAM(eventName);\n"); push(@cBodyProperties, " UNUSED_PARAM(handler);\n"); push(@cBodyProperties, " UNUSED_PARAM(useCapture);\n"); push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn); push(@cBodyProperties, " return false;\n#endif // ${conditionalString}\n"); } push(@cBodyProperties, "}\n\n"); push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_remove_event_listener(WebKitDOMEventTarget* target, const char* eventName, GClosure* handler, gboolean useCapture)\n{\n"); push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString; push(@cBodyProperties, " WebCore::${interfaceName}* coreTarget = static_cast(WEBKIT_DOM_OBJECT(target)->coreObject);\n"); push(@cBodyProperties, " return WebCore::GObjectEventListener::removeEventListener(G_OBJECT(target), coreTarget, eventName, handler, useCapture);\n"); if ($conditionalString) { push(@cBodyProperties, "#else\n"); push(@cBodyProperties, " UNUSED_PARAM(target);\n"); push(@cBodyProperties, " UNUSED_PARAM(eventName);\n"); push(@cBodyProperties, " UNUSED_PARAM(handler);\n"); push(@cBodyProperties, " UNUSED_PARAM(useCapture);\n"); push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn); push(@cBodyProperties, " return false;\n#endif // ${conditionalString}\n"); } push(@cBodyProperties, "}\n\n"); push(@cBodyProperties, "static void webkit_dom_event_target_init(WebKitDOMEventTargetIface* iface)\n{\n"); push(@cBodyProperties, " iface->dispatch_event = webkit_dom_${decamelize}_dispatch_event;\n"); push(@cBodyProperties, " iface->add_event_listener = webkit_dom_${decamelize}_add_event_listener;\n"); push(@cBodyProperties, " iface->remove_event_listener = webkit_dom_${decamelize}_remove_event_listener;\n}\n\n"); $defineTypeMacro = "G_DEFINE_TYPE_WITH_CODE"; $defineTypeInterfaceImplementation = ", G_IMPLEMENT_INTERFACE(WEBKIT_DOM_TYPE_EVENT_TARGET, webkit_dom_event_target_init))"; } sub Generate { my ($object, $interface) = @_; my $parentClassName = GetParentClassName($interface); my $parentGObjType = GetParentGObjType($interface); my $interfaceName = $interface->name; my $parentImplClassName = GetParentImplClassName($interface); my $baseClassName = GetBaseClass($parentImplClassName, $interface); # Add the default impl header template @cPrefix = split("\r", $licenceTemplate); push(@cPrefix, "\n"); $implIncludes{"DOMObjectCache.h"} = 1; $implIncludes{"WebKitDOMPrivate.h"} = 1; $implIncludes{"gobject/ConvertToUTF8String.h"} = 1; $implIncludes{"${className}Private.h"} = 1; $implIncludes{"Document.h"} = 1; $implIncludes{"JSMainThreadExecState.h"} = 1; $implIncludes{"ExceptionCode.h"} = 1; $implIncludes{"ExceptionCodeDescription.h"} = 1; $implIncludes{"CSSImportRule.h"} = 1; if ($parentImplClassName ne "Object" and IsPolymorphic($baseClassName)) { $implIncludes{"WebKitDOM${baseClassName}Private.h"} = 1; } $hdrIncludes{"webkitdom/${parentClassName}.h"} = 1; $object->GenerateHeader($interfaceName, $parentClassName, $interface); $object->GenerateCFile($interfaceName, $parentClassName, $parentGObjType, $interface); $object->GenerateEndHeader(); } sub HasUnstableCustomAPI { my $domClassName = shift; return scalar(grep {$_ eq $domClassName} qw(WebKitDOMDOMWindow WebKitDOMUserMessageHandlersNamespace WebKitDOMHTMLLinkElement)); } sub WriteData { my $object = shift; my $interface = shift; my $outputDir = shift; mkdir $outputDir; my $isStableClass = scalar(@stableSymbols); # Write a private header. my $interfaceName = $interface->name; my $filename = "$outputDir/" . $className . "Private.h"; my $guard = "${className}Private_h"; # Add the guard if the 'Conditional' extended attribute exists my $conditionalString = $codeGenerator->GenerateConditionalString($interface); open(PRIVHEADER, ">$filename") or die "Couldn't open file $filename for writing"; print PRIVHEADER split("\r", $licenceTemplate); print PRIVHEADER "\n"; my $text = << "EOF"; #ifndef $guard #define $guard #include "${interfaceName}.h" #include EOF print PRIVHEADER $text; print PRIVHEADER "#if ${conditionalString}\n" if $conditionalString; print PRIVHEADER "\n"; $text = << "EOF"; namespace WebKit { ${className}* wrap${interfaceName}(WebCore::${interfaceName}*); ${className}* kit(WebCore::${interfaceName}*); WebCore::${interfaceName}* core(${className}*); EOF print PRIVHEADER $text; $text = << "EOF"; } // namespace WebKit EOF print PRIVHEADER $text; print PRIVHEADER "#endif /* ${conditionalString} */\n\n" if $conditionalString; print PRIVHEADER "#endif /* ${guard} */\n"; close(PRIVHEADER); my $basename = FileNamePrefix . $interfaceName; $basename =~ s/_//g; # Write public header. my $fullHeaderFilename = "$outputDir/" . $basename . ".h"; my $installedHeaderFilename = "${basename}.h"; open(HEADER, ">$fullHeaderFilename") or die "Couldn't open file $fullHeaderFilename"; print HEADER @hPrefix; print HEADER @hPrefixGuard; print HEADER "#include \n"; print HEADER map { "#include <$_>\n" } sort keys(%hdrIncludes); if ($isStableClass) { print HEADER "#include \n\n"; } else { if (HasUnstableCustomAPI($className)) { print HEADER "#include \n"; } print HEADER "#include \n\n"; } print HEADER @hBodyPre; print HEADER @hBody; print HEADER @hPrefixGuardEnd; close(HEADER); # Write the unstable header if needed. if ($isStableClass and scalar(@hBodyUnstable)) { my $fullUnstableHeaderFilename = "$outputDir/" . $className . "Unstable.h"; open(UNSTABLE, ">$fullUnstableHeaderFilename") or die "Couldn't open file $fullUnstableHeaderFilename"; print UNSTABLE split("\r", $licenceTemplate); print UNSTABLE "\n"; $guard = "${className}Unstable_h"; $text = << "EOF"; #ifndef $guard #define $guard #ifdef WEBKIT_DOM_USE_UNSTABLE_API EOF print UNSTABLE $text; if (HasUnstableCustomAPI($className)) { print UNSTABLE "#include \n"; } print UNSTABLE "#include \n\n"; print UNSTABLE "#if ${conditionalString}\n\n" if $conditionalString; print UNSTABLE "G_BEGIN_DECLS\n"; print UNSTABLE "\n"; print UNSTABLE @hBodyUnstable; print UNSTABLE "\n"; print UNSTABLE "G_END_DECLS\n"; print UNSTABLE "\n"; print UNSTABLE "#endif /* ${conditionalString} */\n\n" if $conditionalString; print UNSTABLE "#endif /* WEBKIT_DOM_USE_UNSTABLE_API */\n"; print UNSTABLE "#endif /* ${guard} */\n"; close(UNSTABLE); } # Write the implementation sources my $implFileName = "$outputDir/" . $basename . ".cpp"; open(IMPL, ">$implFileName") or die "Couldn't open file $implFileName"; print IMPL @cPrefix; print IMPL "#include \"config.h\"\n"; print IMPL "#include \"$installedHeaderFilename\"\n\n"; # Remove the implementation header from the list of included files. %includesCopy = %implIncludes; print IMPL map { "#include \"$_\"\n" } sort keys(%includesCopy); if ($isStableClass and scalar(@hBodyUnstable)) { print IMPL "#include \"${className}Unstable.h\"\n"; } print IMPL "#include \n"; print IMPL "#include \n\n"; print IMPL @cStructPriv; print IMPL "#if ${conditionalString}\n\n" if $conditionalString; print IMPL "namespace WebKit {\n\n"; print IMPL @cBodyPriv; print IMPL "} // namespace WebKit\n\n"; print IMPL "#endif // ${conditionalString}\n\n" if $conditionalString; print IMPL @cBodyProperties; print IMPL @cBody; close(IMPL); # Write a symbols file. if ($isStableClass) { my $symbolsFileName = "$outputDir/" . $basename . ".symbols"; open(SYM, ">$symbolsFileName") or die "Couldn't open file $symbolsFileName"; print SYM @symbols; close(SYM); } %implIncludes = (); %hdrIncludes = (); @hPrefix = (); @hBody = (); @hBodyUnstable = (); @cPrefix = (); @cBody = (); @cBodyPriv = (); @cBodyProperties = (); @cStructPriv = (); @symbols = (); @stableSymbols = (); } sub IsInterfaceSymbol { my ($line, $lowerCaseIfaceName) = @_; # Function. return 1 if $line =~ /^[a-zA-Z0-9\*]+\s${lowerCaseIfaceName}_.+$/; # Constant. my $prefix = uc($lowerCaseIfaceName); return 1 if $line =~ /^${prefix}_[A-Z_]+$/; return 0; } sub ReadStableSymbols { my $interfaceName = shift; @stableSymbols = (); my $bindingsDir = dirname($FindBin::Bin); my $fileName = "$bindingsDir/gobject/webkitdom.symbols"; open FILE, "<", $fileName or die "Could not open $fileName"; my @lines = ; close FILE; my $decamelize = decamelize($interfaceName); my $lowerCaseIfaceName = "webkit_dom_$decamelize"; foreach $line (@lines) { $line =~ s/\n$//; my ($symbol) = split('@', $line, 2); if ($symbol eq "GType ${lowerCaseIfaceName}_get_type(void)") { push(@stableSymbols, $line); next; } if (scalar(@stableSymbols) and IsInterfaceSymbol($symbol, $lowerCaseIfaceName) and $symbol !~ /^GType/) { push(@stableSymbols, $line); next; } if (scalar(@stableSymbols) and $symbol !~ /^GType/) { warn "Symbol %line found, but a get_type was expected"; } last if scalar(@stableSymbols); } } sub GenerateInterface { my ($object, $interface, $defines) = @_; # Set up some global variables $className = GetClassName($interface->name); ReadStableSymbols($interface->name); $object->Generate($interface); } 1;