File indexing completed on 2024-05-12 15:43:30
0001 // krazy:excludeall=doublequote_chars (UStrings aren't QStrings) 0002 /* 0003 * This file is part of the KDE libraries 0004 * Copyright (C) 1999-2000,2003 Harri Porten (porten@kde.org) 0005 * Copyright (C) 2007 Apple Inc. All rights reserved. 0006 * 0007 * This library is free software; you can redistribute it and/or 0008 * modify it under the terms of the GNU Lesser General Public 0009 * License as published by the Free Software Foundation; either 0010 * version 2 of the License, or (at your option) any later version. 0011 * 0012 * This library is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 * Lesser General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU Lesser General Public 0018 * License along with this library; if not, write to the Free Software 0019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 0020 * USA 0021 * 0022 */ 0023 0024 #include "number_object.h" 0025 #include "number_object.lut.h" 0026 0027 #include "dtoa.h" 0028 #include "error_object.h" 0029 #include "operations.h" 0030 #include <wtf/Assertions.h> 0031 #include <wtf/MathExtras.h> 0032 #include <wtf/Vector.h> 0033 0034 namespace KJS 0035 { 0036 0037 // GCC cstring uses these automatically, but not all implementations do. 0038 using std::strlen; 0039 using std::strcpy; 0040 using std::strncpy; 0041 using std::memset; 0042 using std::memcpy; 0043 0044 static const double MAX_SAFE_INTEGER = 9007199254740991.0; //(2^53)-1 0045 static const double MIN_SAFE_INTEGER = -9007199254740991.0; //-((2^53)-1) 0046 0047 // ------------------------------ NumberInstance ---------------------------- 0048 0049 const ClassInfo NumberInstance::info = {"Number", nullptr, nullptr, nullptr}; 0050 0051 NumberInstance::NumberInstance(JSObject *proto) 0052 : JSWrapperObject(proto) 0053 { 0054 } 0055 0056 JSObject *NumberInstance::valueClone(Interpreter *targetCtx) const 0057 { 0058 NumberInstance *copy = new NumberInstance(targetCtx->builtinNumberPrototype()); 0059 copy->setInternalValue(internalValue()); 0060 return copy; 0061 } 0062 0063 // ------------------------------ NumberPrototype --------------------------- 0064 0065 // ECMA 15.7.4 0066 0067 NumberPrototype::NumberPrototype(ExecState *exec, ObjectPrototype *objProto, FunctionPrototype *funcProto) 0068 : NumberInstance(objProto) 0069 { 0070 setInternalValue(jsNumber(0)); 0071 0072 // The constructor will be added later, after NumberObjectImp has been constructed 0073 0074 putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToString, 1, exec->propertyNames().toString), DontEnum); 0075 putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToLocaleString, 0, exec->propertyNames().toLocaleString), DontEnum); 0076 putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ValueOf, 0, exec->propertyNames().valueOf), DontEnum); 0077 putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToFixed, 1, exec->propertyNames().toFixed), DontEnum); 0078 putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToExponential, 1, exec->propertyNames().toExponential), DontEnum); 0079 putDirectFunction(new NumberProtoFunc(exec, funcProto, NumberProtoFunc::ToPrecision, 1, exec->propertyNames().toPrecision), DontEnum); 0080 } 0081 0082 // ------------------------------ NumberProtoFunc --------------------------- 0083 0084 NumberProtoFunc::NumberProtoFunc(ExecState *exec, FunctionPrototype *funcProto, int i, int len, const Identifier &name) 0085 : InternalFunctionImp(funcProto, name) 0086 , id(i) 0087 { 0088 putDirect(exec->propertyNames().length, len, DontDelete | ReadOnly | DontEnum); 0089 } 0090 0091 static UString integer_part_noexp(double d) 0092 { 0093 int decimalPoint; 0094 int sign; 0095 char *result = kjs_dtoa(d, 0, 0, &decimalPoint, &sign, nullptr); 0096 bool resultIsInfOrNan = (decimalPoint == 9999); 0097 size_t length = strlen(result); 0098 0099 UString str = sign ? "-" : ""; 0100 if (resultIsInfOrNan) { 0101 str += result; 0102 } else if (decimalPoint <= 0) { 0103 str += "0"; 0104 } else { 0105 Vector<char, 1024> buf(decimalPoint + 1); 0106 0107 if (static_cast<int>(length) <= decimalPoint) { 0108 strcpy(buf.data(), result); 0109 memset(buf.data() + length, '0', decimalPoint - length); 0110 } else { 0111 strncpy(buf.data(), result, decimalPoint); 0112 } 0113 0114 buf[decimalPoint] = '\0'; 0115 str += UString(buf.data()); 0116 } 0117 0118 kjs_freedtoa(result); 0119 0120 return str; 0121 } 0122 0123 static UString char_sequence(char c, int count) 0124 { 0125 Vector<char, 2048> buf(count + 1, c); 0126 buf[count] = '\0'; 0127 0128 return UString(buf.data()); 0129 } 0130 0131 static double intPow10(int e) 0132 { 0133 // This function uses the "exponentiation by squaring" algorithm and 0134 // long double to quickly and precisely calculate integer powers of 10.0. 0135 0136 // This is a handy workaround for <rdar://problem/4494756> 0137 0138 if (e == 0) { 0139 return 1.0; 0140 } 0141 0142 bool negative = e < 0; 0143 unsigned exp = negative ? -e : e; 0144 0145 long double result = 10.0; 0146 bool foundOne = false; 0147 for (int bit = 31; bit >= 0; bit--) { 0148 if (!foundOne) { 0149 if ((exp >> bit) & 1) { 0150 foundOne = true; 0151 } 0152 } else { 0153 result = result * result; 0154 if ((exp >> bit) & 1) { 0155 result = result * 10.0; 0156 } 0157 } 0158 } 0159 0160 if (negative) { 0161 return static_cast<double>(1.0 / result); 0162 } 0163 return static_cast<double>(result); 0164 } 0165 0166 static JSValue *numberToString(ExecState *exec, JSValue *v, const List &args) 0167 { 0168 double radixAsDouble = JSValue::toInteger(args[0], exec); // nan -> 0 0169 if (radixAsDouble == 10 || JSValue::isUndefined(args[0])) { 0170 return jsString(JSValue::toString(v, exec)); 0171 } 0172 0173 if (radixAsDouble < 2 || radixAsDouble > 36) { 0174 return throwError(exec, RangeError, "toString() radix argument must be between 2 and 36"); 0175 } 0176 0177 int radix = static_cast<int>(radixAsDouble); 0178 const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 0179 // INT_MAX results in 1024 characters left of the dot with radix 2 0180 // give the same space on the right side. safety checks are in place 0181 // unless someone finds a precise rule. 0182 char s[2048 + 3]; 0183 const char *lastCharInString = s + sizeof(s) - 1; 0184 double x = JSValue::toNumber(v, exec); 0185 if (isNaN(x) || isInf(x)) { 0186 return jsString(UString::from(x)); 0187 } 0188 0189 bool isNegative = x < 0.0; 0190 if (isNegative) { 0191 x = -x; 0192 } 0193 0194 double integerPart = floor(x); 0195 char *decimalPoint = s + sizeof(s) / 2; 0196 0197 // convert integer portion 0198 char *p = decimalPoint; 0199 double d = integerPart; 0200 do { 0201 int remainderDigit = static_cast<int>(fmod(d, radix)); 0202 *--p = digits[remainderDigit]; 0203 d /= radix; 0204 } while ((d <= -1.0 || d >= 1.0) && s < p); 0205 0206 if (isNegative) { 0207 *--p = '-'; 0208 } 0209 char *startOfResultString = p; 0210 ASSERT(s <= startOfResultString); 0211 0212 d = x - integerPart; 0213 p = decimalPoint; 0214 const double epsilon = 0.001; // TODO: guessed. base on radix ? 0215 bool hasFractionalPart = (d < -epsilon || d > epsilon); 0216 if (hasFractionalPart) { 0217 *p++ = '.'; 0218 do { 0219 d *= radix; 0220 const int digit = static_cast<int>(d); 0221 *p++ = digits[digit]; 0222 d -= digit; 0223 } while ((d < -epsilon || d > epsilon) && p < lastCharInString); 0224 } 0225 *p = '\0'; 0226 ASSERT(p < s + sizeof(s)); 0227 0228 return jsString(startOfResultString); 0229 } 0230 0231 static JSValue *numberToFixed(ExecState *exec, JSValue *v, const List &args) 0232 { 0233 JSValue *fractionDigits = args[0]; 0234 double df = JSValue::toInteger(fractionDigits, exec); 0235 if (!(df >= 0 && df <= 20)) { 0236 return throwError(exec, RangeError, "toFixed() digits argument must be between 0 and 20"); 0237 } 0238 int f = (int)df; 0239 0240 double x = JSValue::toNumber(v, exec); 0241 if (isNaN(x)) { 0242 return jsString("NaN"); 0243 } 0244 0245 UString s; 0246 if (x < 0) { 0247 s.append('-'); 0248 x = -x; 0249 } else if (x == -0.0) { 0250 x = 0; 0251 } 0252 0253 if (x >= pow(10.0, 21.0)) { 0254 return jsString(s + UString::from(x)); 0255 } 0256 0257 const double tenToTheF = pow(10.0, f); 0258 double n = floor(x * tenToTheF); 0259 if (fabs(n / tenToTheF - x) >= fabs((n + 1) / tenToTheF - x)) { 0260 n++; 0261 } 0262 0263 UString m = integer_part_noexp(n); 0264 0265 int k = m.size(); 0266 if (k <= f) { 0267 UString z; 0268 for (int i = 0; i < f + 1 - k; i++) { 0269 z.append('0'); 0270 } 0271 m = z + m; 0272 k = f + 1; 0273 ASSERT(k == m.size()); 0274 } 0275 int kMinusf = k - f; 0276 if (kMinusf < m.size()) { 0277 return jsString(s + m.substr(0, kMinusf) + "." + m.substr(kMinusf)); 0278 } 0279 return jsString(s + m.substr(0, kMinusf)); 0280 } 0281 0282 void fractionalPartToString(char *buf, int &i, const char *result, int resultLength, int fractionalDigits) 0283 { 0284 if (fractionalDigits <= 0) { 0285 return; 0286 } 0287 0288 int fDigitsInResult = static_cast<int>(resultLength) - 1; 0289 buf[i++] = '.'; 0290 if (fDigitsInResult > 0) { 0291 if (fractionalDigits < fDigitsInResult) { 0292 strncpy(buf + i, result + 1, fractionalDigits); 0293 i += fractionalDigits; 0294 } else { 0295 strcpy(buf + i, result + 1); 0296 i += static_cast<int>(resultLength) - 1; 0297 } 0298 } 0299 0300 for (int j = 0; j < fractionalDigits - fDigitsInResult; j++) { 0301 buf[i++] = '0'; 0302 } 0303 } 0304 0305 void exponentialPartToString(char *buf, int &i, int decimalPoint) 0306 { 0307 buf[i++] = 'e'; 0308 buf[i++] = (decimalPoint >= 0) ? '+' : '-'; 0309 // decimalPoint can't be more than 3 digits decimal given the 0310 // nature of float representation 0311 int exponential = decimalPoint - 1; 0312 if (exponential < 0) { 0313 exponential *= -1; 0314 } 0315 if (exponential >= 100) { 0316 buf[i++] = static_cast<char>('0' + exponential / 100); 0317 } 0318 if (exponential >= 10) { 0319 buf[i++] = static_cast<char>('0' + (exponential % 100) / 10); 0320 } 0321 buf[i++] = static_cast<char>('0' + exponential % 10); 0322 } 0323 0324 static JSValue *numberToExponential(ExecState *exec, JSValue *v, const List &args) 0325 { 0326 double x = JSValue::toNumber(v, exec); 0327 0328 if (isNaN(x) || isInf(x)) { 0329 return jsString(UString::from(x)); 0330 } 0331 0332 JSValue *fractionalDigitsValue = args[0]; 0333 double df = JSValue::toInteger(fractionalDigitsValue, exec); 0334 if (!(df >= 0 && df <= 20)) { 0335 return throwError(exec, RangeError, "toExponential() argument must between 0 and 20"); 0336 } 0337 int fractionalDigits = (int)df; 0338 bool includeAllDigits = JSValue::isUndefined(fractionalDigitsValue); 0339 0340 int decimalAdjust = 0; 0341 if (x && !includeAllDigits) { 0342 double logx = floor(log10(fabs(x))); 0343 x /= pow(10.0, logx); 0344 const double tenToTheF = pow(10.0, fractionalDigits); 0345 double fx = floor(x * tenToTheF) / tenToTheF; 0346 double cx = ceil(x * tenToTheF) / tenToTheF; 0347 0348 if (fabs(fx - x) < fabs(cx - x)) { 0349 x = fx; 0350 } else { 0351 x = cx; 0352 } 0353 0354 decimalAdjust = static_cast<int>(logx); 0355 } 0356 0357 if (isNaN(x)) { 0358 return jsString("NaN"); 0359 } 0360 0361 if (x == -0.0) { // (-0.0).toExponential() should print as 0 instead of -0 0362 x = 0; 0363 } 0364 0365 int decimalPoint; 0366 int sign; 0367 char *result = kjs_dtoa(x, 0, 0, &decimalPoint, &sign, nullptr); 0368 size_t resultLength = strlen(result); 0369 decimalPoint += decimalAdjust; 0370 0371 int i = 0; 0372 char buf[80]; // digit + '.' + fractionDigits (max 20) + 'e' + sign + exponent (max?) 0373 if (sign) { 0374 buf[i++] = '-'; 0375 } 0376 0377 if (decimalPoint == 999) { // ? 9999 is the magical "result is Inf or NaN" value. what's 999?? 0378 strcpy(buf + i, result); 0379 } else { 0380 buf[i++] = result[0]; 0381 0382 if (includeAllDigits) { 0383 fractionalDigits = static_cast<int>(resultLength) - 1; 0384 } 0385 0386 fractionalPartToString(buf, i, result, resultLength, fractionalDigits); 0387 exponentialPartToString(buf, i, decimalPoint); 0388 buf[i++] = '\0'; 0389 } 0390 ASSERT(i <= 80); 0391 0392 kjs_freedtoa(result); 0393 0394 return jsString(buf); 0395 } 0396 0397 static JSValue *numberToPrecision(ExecState *exec, JSValue *v, const List &args) 0398 { 0399 double doublePrecision = JSValue::toIntegerPreserveNaN(args[0], exec); 0400 double x = JSValue::toNumber(v, exec); 0401 if (JSValue::isUndefined(args[0]) || isNaN(x) || isInf(x)) { 0402 return jsString(JSValue::toString(v, exec)); 0403 } 0404 0405 UString s; 0406 if (x < 0) { 0407 s = "-"; 0408 x = -x; 0409 } 0410 0411 if (!(doublePrecision >= 1 && doublePrecision <= 21)) { // true for NaN 0412 return throwError(exec, RangeError, "toPrecision() argument must be between 1 and 21"); 0413 } 0414 int precision = (int)doublePrecision; 0415 0416 int e = 0; 0417 UString m; 0418 if (x) { 0419 e = static_cast<int>(log10(x)); 0420 double tens = intPow10(e - precision + 1); 0421 double n = floor(x / tens); 0422 if (n < intPow10(precision - 1)) { 0423 e = e - 1; 0424 tens = intPow10(e - precision + 1); 0425 n = floor(x / tens); 0426 } 0427 0428 if (fabs((n + 1.0) * tens - x) <= fabs(n * tens - x)) { 0429 ++n; 0430 } 0431 // maintain n < 10^(precision) 0432 if (n >= intPow10(precision)) { 0433 n /= 10.0; 0434 e += 1; 0435 } 0436 ASSERT(intPow10(precision - 1) <= n); 0437 ASSERT(n < intPow10(precision)); 0438 0439 m = integer_part_noexp(n); 0440 if (e < -6 || e >= precision) { 0441 if (m.size() > 1) { 0442 m = m.substr(0, 1) + "." + m.substr(1); 0443 } 0444 if (e >= 0) { 0445 return jsString(s + m + "e+" + UString::from(e)); 0446 } 0447 return jsString(s + m + "e-" + UString::from(-e)); 0448 } 0449 } else { 0450 m = char_sequence('0', precision); 0451 e = 0; 0452 } 0453 0454 if (e == precision - 1) { 0455 return jsString(s + m); 0456 } else if (e >= 0) { 0457 if (e + 1 < m.size()) { 0458 return jsString(s + m.substr(0, e + 1) + "." + m.substr(e + 1)); 0459 } 0460 return jsString(s + m); 0461 } 0462 return jsString(s + "0." + char_sequence('0', -(e + 1)) + m); 0463 } 0464 0465 // ECMA 15.7.4.2 - 15.7.4.7 0466 JSValue *NumberProtoFunc::callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) 0467 { 0468 // no generic function. "this" has to be a Number object 0469 if (!thisObj->inherits(&NumberInstance::info)) { 0470 return throwError(exec, TypeError); 0471 } 0472 0473 JSValue *v = static_cast<NumberInstance *>(thisObj)->internalValue(); 0474 switch (id) { 0475 case ToString: 0476 return numberToString(exec, v, args); 0477 case ToLocaleString: /* TODO */ 0478 return jsString(JSValue::toString(v, exec)); 0479 case ValueOf: 0480 return jsNumber(JSValue::toNumber(v, exec)); 0481 case ToFixed: 0482 return numberToFixed(exec, v, args); 0483 case ToExponential: 0484 return numberToExponential(exec, v, args); 0485 case ToPrecision: 0486 return numberToPrecision(exec, v, args); 0487 } 0488 return nullptr; 0489 } 0490 0491 // ------------------------------ NumberObjectImp ------------------------------ 0492 0493 const ClassInfo NumberObjectImp::info = {"Function", &InternalFunctionImp::info, &numberTable, nullptr}; 0494 0495 /* Source for number_object.lut.h 0496 @begin numberTable 5 0497 NaN NumberObjectImp::NaNValue DontEnum|DontDelete|ReadOnly 0498 NEGATIVE_INFINITY NumberObjectImp::NegInfinity DontEnum|DontDelete|ReadOnly 0499 POSITIVE_INFINITY NumberObjectImp::PosInfinity DontEnum|DontDelete|ReadOnly 0500 MAX_VALUE NumberObjectImp::MaxValue DontEnum|DontDelete|ReadOnly 0501 MIN_VALUE NumberObjectImp::MinValue DontEnum|DontDelete|ReadOnly 0502 0503 MAX_SAFE_INTEGER NumberObjectImp::MaxSafeInteger DontEnum|DontDelete|ReadOnly 0504 MIN_SAFE_INTEGER NumberObjectImp::MinSafeInteger DontEnum|DontDelete|ReadOnly 0505 isFinite NumberObjectImp::IsFinite DontEnum|Function 1 0506 isInteger NumberObjectImp::IsInteger DontEnum|Function 1 0507 isNaN NumberObjectImp::IsNaN DontEnum|Function 1 0508 isSafeInteger NumberObjectImp::IsSafeInteger DontEnum|Function 1 0509 parseInt NumberObjectImp::ParseInt DontEnum|Function 2 0510 parseFloat NumberObjectImp::ParseFloat DontEnum|Function 1 0511 @end 0512 */ 0513 NumberObjectImp::NumberObjectImp(ExecState *exec, FunctionPrototype *funcProto, NumberPrototype *numberProto) 0514 : InternalFunctionImp(funcProto) 0515 { 0516 // Number.Prototype 0517 putDirect(exec->propertyNames().prototype, numberProto, DontEnum | DontDelete | ReadOnly); 0518 0519 // no. of arguments for constructor 0520 putDirect(exec->propertyNames().length, jsNumber(1), ReadOnly | DontDelete | DontEnum); 0521 } 0522 0523 bool NumberObjectImp::getOwnPropertySlot(ExecState *exec, const Identifier &propertyName, PropertySlot &slot) 0524 { 0525 return getStaticPropertySlot<NumberFuncImp, NumberObjectImp, InternalFunctionImp>(exec, &numberTable, this, propertyName, slot); 0526 } 0527 0528 JSValue *NumberObjectImp::getValueProperty(ExecState *, int token) const 0529 { 0530 // ECMA 15.7.3 0531 switch (token) { 0532 case NaNValue: 0533 return jsNaN(); 0534 case NegInfinity: 0535 return jsNumberCell(-Inf); 0536 case PosInfinity: 0537 return jsNumberCell(Inf); 0538 case MaxValue: 0539 return jsNumberCell(1.7976931348623157E+308); 0540 case MinValue: 0541 return jsNumberCell(5E-324); 0542 case MaxSafeInteger: 0543 return jsNumber(MAX_SAFE_INTEGER); 0544 case MinSafeInteger: 0545 return jsNumber(MIN_SAFE_INTEGER); 0546 } 0547 return jsNull(); 0548 } 0549 0550 bool NumberObjectImp::implementsConstruct() const 0551 { 0552 return true; 0553 } 0554 0555 // ECMA 15.7.1 0556 JSObject *NumberObjectImp::construct(ExecState *exec, const List &args) 0557 { 0558 JSObject *proto = exec->lexicalInterpreter()->builtinNumberPrototype(); 0559 NumberInstance *obj = new NumberInstance(proto); 0560 0561 double n = args.isEmpty() ? 0 : JSValue::toNumber(args[0], exec); 0562 obj->setInternalValue(jsNumber(n)); 0563 return obj; 0564 } 0565 0566 // ECMA 15.7.2 0567 JSValue *NumberObjectImp::callAsFunction(ExecState *exec, JSObject *, const List &args) 0568 { 0569 double n = args.isEmpty() ? 0 : JSValue::toNumber(args[0], exec); 0570 return jsNumber(n); 0571 } 0572 0573 NumberFuncImp::NumberFuncImp(ExecState* exec, int i, int l, const Identifier& name) 0574 : InternalFunctionImp(static_cast<FunctionPrototype*>(exec->lexicalInterpreter()->builtinFunctionPrototype()), name) 0575 , id(i) 0576 { 0577 putDirect(exec->propertyNames().length, l, DontDelete|ReadOnly|DontEnum); 0578 } 0579 0580 JSValue* NumberFuncImp::callAsFunction(ExecState* exec, JSObject* /*thisObj*/, const List& args) 0581 { 0582 double arg = JSValue::toNumber(args[0], exec); 0583 0584 switch (id) { 0585 case NumberObjectImp::IsFinite: 0586 if (JSValue::type(args[0]) != NumberType) 0587 return jsBoolean(false); 0588 return jsBoolean(!isNaN(arg) && !isInf(arg)); 0589 0590 case NumberObjectImp::IsInteger: 0591 { 0592 if (JSValue::type(args[0]) != NumberType) 0593 return jsBoolean(false); 0594 if (isNaN(arg) || isInf(arg)) 0595 return jsBoolean(false); 0596 double num = JSValue::toInteger(args[0], exec); 0597 return jsBoolean(num == arg); 0598 } 0599 case NumberObjectImp::IsNaN: 0600 if (JSValue::type(args[0]) != NumberType) 0601 return jsBoolean(false); 0602 return jsBoolean(isNaN(arg)); 0603 0604 case NumberObjectImp::IsSafeInteger: 0605 { 0606 if (JSValue::type(args[0]) != NumberType) 0607 return jsBoolean(false); 0608 if (isNaN(arg) || isInf(arg)) 0609 return jsBoolean(false); 0610 double num = JSValue::toInteger(args[0], exec); 0611 if (num != arg) 0612 return jsBoolean(false); 0613 return jsBoolean(fabs(num) <= MAX_SAFE_INTEGER); 0614 } 0615 case NumberObjectImp::ParseInt: 0616 return jsNumber(KJS::parseInt(JSValue::toString(args[0], exec), JSValue::toInt32(args[1], exec))); 0617 case NumberObjectImp::ParseFloat: 0618 return jsNumber(KJS::parseFloat(JSValue::toString(args[0], exec))); 0619 } 0620 return jsUndefined(); 0621 } 0622 0623 } // namespace KJS