Diff
Modified: trunk/Source/_javascript_Core/CMakeLists.txt (113354 => 113355)
--- trunk/Source/_javascript_Core/CMakeLists.txt 2012-04-05 19:25:36 UTC (rev 113354)
+++ trunk/Source/_javascript_Core/CMakeLists.txt 2012-04-05 19:30:13 UTC (rev 113355)
@@ -182,6 +182,7 @@
runtime/JSPropertyNameIterator.cpp
runtime/JSStaticScopeObject.cpp
runtime/JSString.cpp
+ runtime/JSStringJoiner.cpp
runtime/JSValue.cpp
runtime/JSVariableObject.cpp
runtime/JSWrapperObject.cpp
Modified: trunk/Source/_javascript_Core/ChangeLog (113354 => 113355)
--- trunk/Source/_javascript_Core/ChangeLog 2012-04-05 19:25:36 UTC (rev 113354)
+++ trunk/Source/_javascript_Core/ChangeLog 2012-04-05 19:30:13 UTC (rev 113355)
@@ -1,3 +1,42 @@
+2012-04-05 Benjamin Poulain <[email protected]>
+
+ Make something faster than JSStringBuilder for joining an array of JSValue
+ https://bugs.webkit.org/show_bug.cgi?id=83180
+
+ Reviewed by Geoffrey Garen.
+
+ This patch add the class JSStringJoiner optimized for join() operations.
+
+ This class makes stricter constraints than JSStringBuilder in order avoid
+ memory allocations.
+
+ In the best case, the class allocate memory only twice:
+ -Allocate an array to keep a list of UString to join.
+ -Allocate the final string.
+
+ We also avoid the conversion from 8bits strings to 16bits strings since
+ they are costly and unlikly to help for subsequent calls.
+
+ * CMakeLists.txt:
+ * GNUmakefile.list.am:
+ * _javascript_Core.gypi:
+ * _javascript_Core.vcproj/_javascript_Core/_javascript_Core.vcproj:
+ * _javascript_Core.xcodeproj/project.pbxproj:
+ * Target.pri:
+ * runtime/ArrayPrototype.cpp:
+ (JSC::arrayProtoFuncToLocaleString):
+ (JSC::arrayProtoFuncJoin):
+ * runtime/JSStringJoiner.cpp: Added.
+ (JSC):
+ (JSC::appendStringToData):
+ (JSC::joinStrings):
+ (JSC::JSStringJoiner::build):
+ * runtime/JSStringJoiner.h: Added.
+ (JSC):
+ (JSStringJoiner):
+ (JSC::JSStringJoiner::JSStringJoiner):
+ (JSC::JSStringJoiner::append):
+
2012-04-05 Gavin Barraclough <[email protected]>
https://bugs.webkit.org/show_bug.cgi?id=77293
Modified: trunk/Source/_javascript_Core/GNUmakefile.list.am (113354 => 113355)
--- trunk/Source/_javascript_Core/GNUmakefile.list.am 2012-04-05 19:25:36 UTC (rev 113354)
+++ trunk/Source/_javascript_Core/GNUmakefile.list.am 2012-04-05 19:30:13 UTC (rev 113355)
@@ -477,6 +477,8 @@
Source/_javascript_Core/runtime/JSStaticScopeObject.cpp \
Source/_javascript_Core/runtime/JSStaticScopeObject.h \
Source/_javascript_Core/runtime/JSStringBuilder.h \
+ Source/_javascript_Core/runtime/JSStringJoiner.cpp \
+ Source/_javascript_Core/runtime/JSStringJoiner.h \
Source/_javascript_Core/runtime/JSString.cpp \
Source/_javascript_Core/runtime/JSString.h \
Source/_javascript_Core/runtime/JSType.h \
Modified: trunk/Source/_javascript_Core/_javascript_Core.gypi (113354 => 113355)
--- trunk/Source/_javascript_Core/_javascript_Core.gypi 2012-04-05 19:25:36 UTC (rev 113354)
+++ trunk/Source/_javascript_Core/_javascript_Core.gypi 2012-04-05 19:30:13 UTC (rev 113355)
@@ -388,6 +388,8 @@
'runtime/JSStaticScopeObject.h',
'runtime/JSString.cpp',
'runtime/JSStringBuilder.h',
+ 'runtime/JSStringJoiner.cpp',
+ 'runtime/JSStringJoiner.h',
'runtime/JSValue.cpp',
'runtime/JSVariableObject.cpp',
'runtime/JSWrapperObject.cpp',
Modified: trunk/Source/_javascript_Core/_javascript_Core.vcproj/_javascript_Core/_javascript_Core.vcproj (113354 => 113355)
--- trunk/Source/_javascript_Core/_javascript_Core.vcproj/_javascript_Core/_javascript_Core.vcproj 2012-04-05 19:25:36 UTC (rev 113354)
+++ trunk/Source/_javascript_Core/_javascript_Core.vcproj/_javascript_Core/_javascript_Core.vcproj 2012-04-05 19:30:13 UTC (rev 113355)
@@ -897,6 +897,14 @@
RelativePath="..\..\runtime\JSString.h"
>
</File>
+ <File
+ RelativePath="..\..\runtime\JSStringJoiner.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\runtime\JSStringJoiner.h"
+ >
+ </File>
<File
RelativePath="..\..\runtime\JSType.h"
>
Modified: trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj (113354 => 113355)
--- trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj 2012-04-05 19:25:36 UTC (rev 113354)
+++ trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj 2012-04-05 19:30:13 UTC (rev 113355)
@@ -332,6 +332,8 @@
14F7256514EE265E00B1652B /* WeakHandleOwner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 14F7256314EE265E00B1652B /* WeakHandleOwner.cpp */; };
14F7256614EE265E00B1652B /* WeakHandleOwner.h in Headers */ = {isa = PBXBuildFile; fileRef = 14F7256414EE265E00B1652B /* WeakHandleOwner.h */; settings = {ATTRIBUTES = (Private, ); }; };
14F97447138C853E00DA1C67 /* HeapRootVisitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 14F97446138C853E00DA1C67 /* HeapRootVisitor.h */; settings = {ATTRIBUTES = (Private, ); }; };
+ 2600B5A6152BAAA70091EE5F /* JSStringJoiner.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2600B5A4152BAAA70091EE5F /* JSStringJoiner.cpp */; };
+ 2600B5A7152BAAA70091EE5F /* JSStringJoiner.h in Headers */ = {isa = PBXBuildFile; fileRef = 2600B5A5152BAAA70091EE5F /* JSStringJoiner.h */; };
41359CF30FDD89AD00206180 /* DateConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = D21202290AD4310C00ED79B6 /* DateConversion.h */; };
451539B912DC994500EF7AC4 /* Yarr.h in Headers */ = {isa = PBXBuildFile; fileRef = 451539B812DC994500EF7AC4 /* Yarr.h */; settings = {ATTRIBUTES = (Private, ); }; };
5D53726F0E1C54880021E549 /* Tracing.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D53726E0E1C54880021E549 /* Tracing.h */; };
@@ -965,6 +967,8 @@
1C9051450BA9E8A70081E9D0 /* Base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = "<group>"; };
1CAA8B4A0D32C39A0041BCFF /* _javascript_.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _javascript_.h; sourceTree = "<group>"; };
1CAA8B4B0D32C39A0041BCFF /* _javascript_Core.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _javascript_Core.h; sourceTree = "<group>"; };
+ 2600B5A4152BAAA70091EE5F /* JSStringJoiner.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSStringJoiner.cpp; sourceTree = "<group>"; };
+ 2600B5A5152BAAA70091EE5F /* JSStringJoiner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSStringJoiner.h; sourceTree = "<group>"; };
449097EE0F8F81B50076A327 /* FeatureDefines.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = FeatureDefines.xcconfig; sourceTree = "<group>"; };
451539B812DC994500EF7AC4 /* Yarr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Yarr.h; path = yarr/Yarr.h; sourceTree = "<group>"; };
45E12D8806A49B0F00E9DF84 /* jsc.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = jsc.cpp; sourceTree = "<group>"; tabWidth = 4; };
@@ -1879,6 +1883,8 @@
BC02E9B60E1842FA000F9297 /* JSString.cpp */,
F692A8620255597D01FF60F7 /* JSString.h */,
86E85538111B9968001AF51E /* JSStringBuilder.h */,
+ 2600B5A4152BAAA70091EE5F /* JSStringJoiner.cpp */,
+ 2600B5A5152BAAA70091EE5F /* JSStringJoiner.h */,
14ABB454099C2A0F00E2A24F /* JSType.h */,
6507D2970E871E4A00D7D896 /* JSTypeInfo.h */,
F692A8870255597D01FF60F7 /* JSValue.cpp */,
@@ -2363,6 +2369,7 @@
BC18C4260E16F5CD00B34460 /* JSRetainPtr.h in Headers */,
BC18C4270E16F5CD00B34460 /* JSString.h in Headers */,
86E85539111B9968001AF51E /* JSStringBuilder.h in Headers */,
+ 2600B5A7152BAAA70091EE5F /* JSStringJoiner.h in Headers */,
BC18C4280E16F5CD00B34460 /* JSStringRef.h in Headers */,
BC18C4290E16F5CD00B34460 /* JSStringRefCF.h in Headers */,
BC18C42A0E16F5CD00B34460 /* JSType.h in Headers */,
@@ -3015,6 +3022,7 @@
A727FF6B0DA3092200E548D7 /* JSPropertyNameIterator.cpp in Sources */,
140566D1107EC267005DBC8D /* JSStaticScopeObject.cpp in Sources */,
147F39D5107EC37600427A48 /* JSString.cpp in Sources */,
+ 2600B5A6152BAAA70091EE5F /* JSStringJoiner.cpp in Sources */,
1482B74E0A43032800517CFC /* JSStringRef.cpp in Sources */,
146AAB380B66A94400E55F16 /* JSStringRefCF.cpp in Sources */,
147F39D6107EC37600427A48 /* JSValue.cpp in Sources */,
Modified: trunk/Source/_javascript_Core/Target.pri (113354 => 113355)
--- trunk/Source/_javascript_Core/Target.pri 2012-04-05 19:25:36 UTC (rev 113354)
+++ trunk/Source/_javascript_Core/Target.pri 2012-04-05 19:30:13 UTC (rev 113355)
@@ -190,6 +190,7 @@
runtime/JSPropertyNameIterator.cpp \
runtime/JSStaticScopeObject.cpp \
runtime/JSString.cpp \
+ runtime/JSStringJoiner.cpp \
runtime/JSValue.cpp \
runtime/JSVariableObject.cpp \
runtime/JSWrapperObject.cpp \
Modified: trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp (113354 => 113355)
--- trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp 2012-04-05 19:25:36 UTC (rev 113354)
+++ trunk/Source/_javascript_Core/runtime/ArrayPrototype.cpp 2012-04-05 19:30:13 UTC (rev 113355)
@@ -28,7 +28,7 @@
#include "CodeBlock.h"
#include "Interpreter.h"
#include "JIT.h"
-#include "JSStringBuilder.h"
+#include "JSStringJoiner.h"
#include "Lookup.h"
#include "ObjectPrototype.h"
#include "Operations.h"
@@ -343,11 +343,9 @@
if (JSValue earlyReturnValue = checker.earlyReturnValue())
return JSValue::encode(earlyReturnValue);
- JSStringBuilder strBuffer;
+ UString separator(",");
+ JSStringJoiner stringJoiner(separator, length);
for (unsigned k = 0; k < length; k++) {
- if (k >= 1)
- strBuffer.append(',');
-
JSValue element = thisObj->get(exec, k);
if (exec->hadException())
return JSValue::encode(jsUndefined());
@@ -365,11 +363,11 @@
str = element.toString(exec)->value(exec);
if (exec->hadException())
return JSValue::encode(jsUndefined());
- strBuffer.append(str);
+ stringJoiner.append(str);
}
}
- return JSValue::encode(strBuffer.build(exec));
+ return JSValue::encode(stringJoiner.build(exec));
}
EncodedJSValue JSC_HOST_CALL arrayProtoFuncJoin(ExecState* exec)
@@ -383,60 +381,39 @@
if (JSValue earlyReturnValue = checker.earlyReturnValue())
return JSValue::encode(earlyReturnValue);
- JSStringBuilder strBuffer;
-
UString separator;
if (!exec->argument(0).isUndefined())
separator = exec->argument(0).toString(exec)->value(exec);
+ if (separator.isNull())
+ separator = UString(",");
+ JSStringJoiner stringJoiner(separator, length);
+
unsigned k = 0;
if (isJSArray(thisObj)) {
JSArray* array = asArray(thisObj);
- if (length) {
- if (!array->canGetIndex(k))
- goto skipFirstLoop;
+ for (; k < length; k++) {
+ if (!array->canGetIndex(k))
+ break;
+
JSValue element = array->getIndex(k);
if (!element.isUndefinedOrNull())
- strBuffer.append(element.toString(exec)->value(exec));
- k++;
- }
-
- if (separator.isNull()) {
- for (; k < length; k++) {
- if (!array->canGetIndex(k))
- break;
- strBuffer.append(',');
- JSValue element = array->getIndex(k);
- if (!element.isUndefinedOrNull())
- strBuffer.append(element.toString(exec)->value(exec));
- }
- } else {
- for (; k < length; k++) {
- if (!array->canGetIndex(k))
- break;
- strBuffer.append(separator);
- JSValue element = array->getIndex(k);
- if (!element.isUndefinedOrNull())
- strBuffer.append(element.toString(exec)->value(exec));
- }
- }
- }
- skipFirstLoop:
- for (; k < length; k++) {
- if (k >= 1) {
- if (separator.isNull())
- strBuffer.append(',');
+ stringJoiner.append(element.toString(exec)->value(exec));
else
- strBuffer.append(separator);
+ stringJoiner.append(UString());
}
+ }
+ for (; k < length; k++) {
JSValue element = thisObj->get(exec, k);
if (!element.isUndefinedOrNull())
- strBuffer.append(element.toString(exec)->value(exec));
+ stringJoiner.append(element.toString(exec)->value(exec));
+ else
+ stringJoiner.append(UString());
}
- return JSValue::encode(strBuffer.build(exec));
+ return JSValue::encode(stringJoiner.build(exec));
}
EncodedJSValue JSC_HOST_CALL arrayProtoFuncConcat(ExecState* exec)
Added: trunk/Source/_javascript_Core/runtime/JSStringJoiner.cpp (0 => 113355)
--- trunk/Source/_javascript_Core/runtime/JSStringJoiner.cpp (rev 0)
+++ trunk/Source/_javascript_Core/runtime/JSStringJoiner.cpp 2012-04-05 19:30:13 UTC (rev 113355)
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012 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. ``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
+ * 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.
+ */
+
+#include "config.h"
+#include "JSStringJoiner.h"
+
+#include "ExceptionHelpers.h"
+#include "JSString.h"
+#include "ScopeChain.h"
+#include <wtf/text/StringImpl.h>
+
+
+namespace JSC {
+
+// The destination is 16bits, at least one string is 16 bits.
+static inline void appendStringToData(UChar*& data, const UString& string)
+{
+ if (string.isNull())
+ return;
+
+ unsigned length = string.length();
+ const StringImpl* stringImpl = string.impl();
+
+ if (stringImpl->is8Bit()) {
+ for (unsigned i = 0; i < length; ++i) {
+ *data = ""
+ ++data;
+ }
+ } else {
+ for (unsigned i = 0; i < length; ++i) {
+ *data = ""
+ ++data;
+ }
+ }
+}
+
+// If the destination is 8bits, we know every string has to be 8bit.
+static inline void appendStringToData(LChar*& data, const UString& string)
+{
+ if (string.isNull())
+ return;
+ ASSERT(string.is8Bit());
+
+ unsigned length = string.length();
+ const StringImpl* stringImpl = string.impl();
+
+ for (unsigned i = 0; i < length; ++i) {
+ *data = ""
+ ++data;
+ }
+}
+
+template<typename CharacterType>
+static inline PassRefPtr<StringImpl> joinStrings(const Vector<UString>& strings, const UString& separator, unsigned outputLength)
+{
+ ASSERT(outputLength);
+
+ CharacterType* data;
+ RefPtr<StringImpl> outputStringImpl = StringImpl::tryCreateUninitialized(outputLength, data);
+ if (!outputStringImpl)
+ return PassRefPtr<StringImpl>();
+
+ const UString firstString = strings.first();
+ appendStringToData(data, firstString);
+
+ for (size_t i = 1; i < strings.size(); ++i) {
+ appendStringToData(data, separator);
+ appendStringToData(data, strings[i]);
+ }
+
+ ASSERT(data == (outputStringImpl->getCharacters<CharacterType>() + outputStringImpl->length()));
+ return outputStringImpl.release();
+}
+
+JSValue JSStringJoiner::build(ExecState* exec)
+{
+ if (!m_isValid)
+ return throwOutOfMemoryError(exec);
+
+ if (!m_strings.size())
+ return jsEmptyString(exec);
+
+ size_t separatorLength = m_separator.length();
+ // FIXME: add special cases of joinStrings() for (separatorLength == 0) and (separatorLength == 1).
+ ASSERT(m_strings.size() > 0);
+ size_t totalSeparactorsLength = separatorLength * (m_strings.size() - 1);
+ size_t outputStringSize = totalSeparactorsLength + m_cumulatedStringsLength;
+
+ if (!outputStringSize)
+ return jsEmptyString(exec);
+
+ RefPtr<StringImpl> outputStringImpl;
+ if (m_is8Bits)
+ outputStringImpl = joinStrings<LChar>(m_strings, m_separator, outputStringSize);
+ else
+ outputStringImpl = joinStrings<UChar>(m_strings, m_separator, outputStringSize);
+
+ if (!outputStringImpl)
+ return throwOutOfMemoryError(exec);
+
+ return JSString::create(exec->globalData(), outputStringImpl.release());
+}
+
+}
Added: trunk/Source/_javascript_Core/runtime/JSStringJoiner.h (0 => 113355)
--- trunk/Source/_javascript_Core/runtime/JSStringJoiner.h (rev 0)
+++ trunk/Source/_javascript_Core/runtime/JSStringJoiner.h 2012-04-05 19:30:13 UTC (rev 113355)
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2012 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. ``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
+ * 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.
+ */
+
+#ifndef JSStringJoiner_h
+#define JSStringJoiner_h
+
+#include "JSValue.h"
+#include "UString.h"
+#include <wtf/Vector.h>
+
+namespace JSC {
+
+class ExecState;
+
+
+class JSStringJoiner {
+public:
+ JSStringJoiner(const UString& separator, size_t stringCount);
+
+ void append(const UString&);
+ JSValue build(ExecState*);
+
+private:
+ UString m_separator;
+ Vector<UString> m_strings;
+
+ unsigned m_cumulatedStringsLength;
+ bool m_isValid;
+ bool m_is8Bits;
+};
+
+inline JSStringJoiner::JSStringJoiner(const UString& separator, size_t stringCount)
+ : m_separator(separator)
+ , m_cumulatedStringsLength(0)
+ , m_isValid(true)
+ , m_is8Bits(m_separator.is8Bit())
+{
+ ASSERT(!m_separator.isNull());
+ m_isValid = m_strings.tryReserveCapacity(stringCount);
+}
+
+inline void JSStringJoiner::append(const UString& str)
+{
+ if (!m_isValid)
+ return;
+
+ m_strings.uncheckedAppend(str);
+ if (!str.isNull()) {
+ m_cumulatedStringsLength += str.length();
+ m_is8Bits = m_is8Bits && str.is8Bit();
+ }
+}
+
+}
+
+#endif