source: webkit/trunk/JavaScriptCore/kjs/function.cpp@ 7507

Last change on this file since 7507 was 7507, checked in by mjs, 21 years ago

Reviewed by Richard.

<rdar://problem/3493140> REGRESSION (85-100): cedille displays %-escaped in JavaScript message at hotmail.com

  • kjs/function.cpp: (KJS::GlobalFuncImp::call): Replace our escape() and unescape() implementations with ones from KDE KJS, which have the proper latin-1 behavior to match Win IE.
  • kjs/lexer.cpp: (Lexer::isHexDigit): Made static and non-const.
  • kjs/lexer.h:
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.6 KB
Line 
1// -*- c-basic-offset: 2 -*-
2/*
3 * This file is part of the KDE libraries
4 * Copyright (C) 1999-2002 Harri Porten ([email protected])
5 * Copyright (C) 2001 Peter Kelly ([email protected])
6 * Copyright (C) 2003 Apple Computer, Inc.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, USA.
22 *
23 */
24
25#include "function.h"
26
27#include "internal.h"
28#include "function_object.h"
29#include "lexer.h"
30#include "nodes.h"
31#include "operations.h"
32#include "debugger.h"
33#include "context.h"
34
35#include <stdio.h>
36#include <errno.h>
37#include <stdlib.h>
38#include <assert.h>
39#include <string.h>
40
41#if APPLE_CHANGES
42#include <unicode/uchar.h>
43#endif
44
45namespace KJS {
46
47// ----------------------------- FunctionImp ----------------------------------
48
49const ClassInfo FunctionImp::info = {"Function", &InternalFunctionImp::info, 0, 0};
50
51 class Parameter {
52 public:
53 Parameter(const Identifier &n) : name(n), next(0L) { }
54 ~Parameter() { delete next; }
55 Identifier name;
56 Parameter *next;
57 };
58
59FunctionImp::FunctionImp(ExecState *exec, const Identifier &n)
60 : InternalFunctionImp(
61 static_cast<FunctionPrototypeImp*>(exec->lexicalInterpreter()->builtinFunctionPrototype().imp())
62 ), param(0L), ident(n)
63{
64}
65
66FunctionImp::~FunctionImp()
67{
68 delete param;
69}
70
71bool FunctionImp::implementsCall() const
72{
73 return true;
74}
75
76Value FunctionImp::call(ExecState *exec, Object &thisObj, const List &args)
77{
78 Object &globalObj = exec->dynamicInterpreter()->globalObject();
79
80 Debugger *dbg = exec->dynamicInterpreter()->imp()->debugger();
81 int sid = -1;
82 int lineno = -1;
83 if (dbg) {
84 if (inherits(&DeclaredFunctionImp::info)) {
85 sid = static_cast<DeclaredFunctionImp*>(this)->body->sourceId();
86 lineno = static_cast<DeclaredFunctionImp*>(this)->body->firstLine();
87 }
88
89 Object func(this);
90 bool cont = dbg->callEvent(exec,sid,lineno,func,args);
91 if (!cont) {
92 dbg->imp()->abort();
93 return Undefined();
94 }
95 }
96
97 // enter a new execution context
98 ContextImp ctx(globalObj, exec->dynamicInterpreter()->imp(), thisObj, codeType(),
99 exec->context().imp(), this, &args);
100 ExecState newExec(exec->dynamicInterpreter(), &ctx);
101 newExec.setException(exec->exception()); // could be null
102
103 // assign user supplied arguments to parameters
104 processParameters(&newExec, args);
105 // add variable declarations (initialized to undefined)
106 processVarDecls(&newExec);
107
108 Completion comp = execute(&newExec);
109
110 // if an exception occured, propogate it back to the previous execution object
111 if (newExec.hadException())
112 exec->setException(newExec.exception());
113
114#ifdef KJS_VERBOSE
115 if (comp.complType() == Throw)
116 printInfo(exec,"throwing", comp.value());
117 else if (comp.complType() == ReturnValue)
118 printInfo(exec,"returning", comp.value());
119 else
120 fprintf(stderr, "returning: undefined\n");
121#endif
122
123 if (dbg) {
124 Object func(this);
125 int cont = dbg->returnEvent(exec,sid,lineno,func);
126 if (!cont) {
127 dbg->imp()->abort();
128 return Undefined();
129 }
130 }
131
132 if (comp.complType() == Throw) {
133 exec->setException(comp.value());
134 return comp.value();
135 }
136 else if (comp.complType() == ReturnValue)
137 return comp.value();
138 else
139 return Undefined();
140}
141
142void FunctionImp::addParameter(const Identifier &n)
143{
144 Parameter **p = &param;
145 while (*p)
146 p = &(*p)->next;
147
148 *p = new Parameter(n);
149}
150
151UString FunctionImp::parameterString() const
152{
153 UString s;
154 const Parameter *p = param;
155 while (p) {
156 if (!s.isEmpty())
157 s += ", ";
158 s += p->name.ustring();
159 p = p->next;
160 }
161
162 return s;
163}
164
165
166// ECMA 10.1.3q
167void FunctionImp::processParameters(ExecState *exec, const List &args)
168{
169 Object variable = exec->context().imp()->variableObject();
170
171#ifdef KJS_VERBOSE
172 fprintf(stderr, "---------------------------------------------------\n"
173 "processing parameters for %s call\n",
174 name().isEmpty() ? "(internal)" : name().ascii());
175#endif
176
177 if (param) {
178 ListIterator it = args.begin();
179 Parameter *p = param;
180 while (p) {
181 if (it != args.end()) {
182#ifdef KJS_VERBOSE
183 fprintf(stderr, "setting parameter %s ", p->name.ascii());
184 printInfo(exec,"to", *it);
185#endif
186 variable.put(exec, p->name, *it);
187 it++;
188 } else
189 variable.put(exec, p->name, Undefined());
190 p = p->next;
191 }
192 }
193#ifdef KJS_VERBOSE
194 else {
195 for (int i = 0; i < args.size(); i++)
196 printInfo(exec,"setting argument", args[i]);
197 }
198#endif
199}
200
201void FunctionImp::processVarDecls(ExecState */*exec*/)
202{
203}
204
205Value FunctionImp::get(ExecState *exec, const Identifier &propertyName) const
206{
207 // Find the arguments from the closest context.
208 if (propertyName == argumentsPropertyName) {
209 ContextImp *context = exec->_context;
210 while (context) {
211 if (context->function() == this)
212 return static_cast<ActivationImp *>
213 (context->activationObject())->get(exec, propertyName);
214 context = context->callingContext();
215 }
216 return Null();
217 }
218
219 // Compute length of parameters.
220 if (propertyName == lengthPropertyName) {
221 const Parameter * p = param;
222 int count = 0;
223 while (p) {
224 ++count;
225 p = p->next;
226 }
227 return Number(count);
228 }
229
230 return InternalFunctionImp::get(exec, propertyName);
231}
232
233void FunctionImp::put(ExecState *exec, const Identifier &propertyName, const Value &value, int attr)
234{
235 if (propertyName == argumentsPropertyName || propertyName == lengthPropertyName)
236 return;
237 InternalFunctionImp::put(exec, propertyName, value, attr);
238}
239
240bool FunctionImp::hasProperty(ExecState *exec, const Identifier &propertyName) const
241{
242 if (propertyName == argumentsPropertyName || propertyName == lengthPropertyName)
243 return true;
244 return InternalFunctionImp::hasProperty(exec, propertyName);
245}
246
247bool FunctionImp::deleteProperty(ExecState *exec, const Identifier &propertyName)
248{
249 if (propertyName == argumentsPropertyName || propertyName == lengthPropertyName)
250 return false;
251 return InternalFunctionImp::deleteProperty(exec, propertyName);
252}
253
254// ------------------------------ DeclaredFunctionImp --------------------------
255
256// ### is "Function" correct here?
257const ClassInfo DeclaredFunctionImp::info = {"Function", &FunctionImp::info, 0, 0};
258
259DeclaredFunctionImp::DeclaredFunctionImp(ExecState *exec, const Identifier &n,
260 FunctionBodyNode *b, const ScopeChain &sc)
261 : FunctionImp(exec,n), body(b)
262{
263 Value protect(this);
264 body->ref();
265 setScope(sc);
266}
267
268DeclaredFunctionImp::~DeclaredFunctionImp()
269{
270 if ( body->deref() )
271 delete body;
272}
273
274bool DeclaredFunctionImp::implementsConstruct() const
275{
276 return true;
277}
278
279// ECMA 13.2.2 [[Construct]]
280Object DeclaredFunctionImp::construct(ExecState *exec, const List &args)
281{
282 Object proto;
283 Value p = get(exec,prototypePropertyName);
284 if (p.type() == ObjectType)
285 proto = Object(static_cast<ObjectImp*>(p.imp()));
286 else
287 proto = exec->lexicalInterpreter()->builtinObjectPrototype();
288
289 Object obj(new ObjectImp(proto));
290
291 Value res = call(exec,obj,args);
292
293 if (res.type() == ObjectType)
294 return Object::dynamicCast(res);
295 else
296 return obj;
297}
298
299Completion DeclaredFunctionImp::execute(ExecState *exec)
300{
301 Completion result = body->execute(exec);
302
303 if (result.complType() == Throw || result.complType() == ReturnValue)
304 return result;
305 return Completion(Normal, Undefined()); // TODO: or ReturnValue ?
306}
307
308void DeclaredFunctionImp::processVarDecls(ExecState *exec)
309{
310 body->processVarDecls(exec);
311}
312
313// ------------------------------ ArgumentsImp ---------------------------------
314
315const ClassInfo ArgumentsImp::info = {"Arguments", 0, 0, 0};
316
317// ECMA 10.1.8
318ArgumentsImp::ArgumentsImp(ExecState *exec, FunctionImp *func)
319 : ArrayInstanceImp(exec->lexicalInterpreter()->builtinObjectPrototype().imp(), 0)
320{
321 Value protect(this);
322 putDirect(calleePropertyName, func, DontEnum);
323}
324
325ArgumentsImp::ArgumentsImp(ExecState *exec, FunctionImp *func, const List &args)
326 : ArrayInstanceImp(exec->lexicalInterpreter()->builtinObjectPrototype().imp(), args)
327{
328 Value protect(this);
329 putDirect(calleePropertyName, func, DontEnum);
330}
331
332// ------------------------------ ActivationImp --------------------------------
333
334const ClassInfo ActivationImp::info = {"Activation", 0, 0, 0};
335
336// ECMA 10.1.6
337ActivationImp::ActivationImp(FunctionImp *function, const List &arguments)
338 : _function(function), _arguments(true), _argumentsObject(0)
339{
340 _arguments = arguments.copy();
341 // FIXME: Do we need to support enumerating the arguments property?
342}
343
344Value ActivationImp::get(ExecState *exec, const Identifier &propertyName) const
345{
346 if (propertyName == argumentsPropertyName) {
347 if (!_argumentsObject)
348 createArgumentsObject(exec);
349 return Value(_argumentsObject);
350 }
351 return ObjectImp::get(exec, propertyName);
352}
353
354void ActivationImp::put(ExecState *exec, const Identifier &propertyName, const Value &value, int attr)
355{
356 if (propertyName == argumentsPropertyName) {
357 // FIXME: Do we need to allow overwriting this?
358 return;
359 }
360 ObjectImp::put(exec, propertyName, value, attr);
361}
362
363bool ActivationImp::hasProperty(ExecState *exec, const Identifier &propertyName) const
364{
365 if (propertyName == argumentsPropertyName)
366 return true;
367 return ObjectImp::hasProperty(exec, propertyName);
368}
369
370bool ActivationImp::deleteProperty(ExecState *exec, const Identifier &propertyName)
371{
372 if (propertyName == argumentsPropertyName)
373 return false;
374 return ObjectImp::deleteProperty(exec, propertyName);
375}
376
377void ActivationImp::mark()
378{
379 if (_function && !_function->marked())
380 _function->mark();
381 _arguments.mark();
382 if (_argumentsObject && !_argumentsObject->marked())
383 _argumentsObject->mark();
384 ObjectImp::mark();
385}
386
387void ActivationImp::createArgumentsObject(ExecState *exec) const
388{
389 _argumentsObject = new ArgumentsImp(exec, _function, _arguments);
390}
391
392// ------------------------------ GlobalFunc -----------------------------------
393
394
395GlobalFuncImp::GlobalFuncImp(ExecState *exec, FunctionPrototypeImp *funcProto, int i, int len)
396 : InternalFunctionImp(funcProto), id(i)
397{
398 Value protect(this);
399 putDirect(lengthPropertyName, len, DontDelete|ReadOnly|DontEnum);
400}
401
402CodeType GlobalFuncImp::codeType() const
403{
404 return id == Eval ? EvalCode : codeType();
405}
406
407bool GlobalFuncImp::implementsCall() const
408{
409 return true;
410}
411
412static Value encode(ExecState *exec, const List &args, const char *do_not_escape)
413{
414 UString r = "", s, str = args[0].toString(exec);
415 CString cstr = str.UTF8String();
416 const char *p = cstr.c_str();
417 for (int k = 0; k < cstr.size(); k++, p++) {
418 char c = *p;
419 if (c && strchr(do_not_escape, c)) {
420 r.append(c);
421 } else {
422 char tmp[4];
423 sprintf(tmp, "%%%02X", (unsigned char)c);
424 r += tmp;
425 }
426 }
427 return String(r);
428}
429
430static Value decode(ExecState *exec, const List &args, const char *do_not_unescape, bool strict)
431{
432 UString s = "", str = args[0].toString(exec);
433 int k = 0, len = str.size();
434 const UChar *d = str.data();
435 UChar u;
436 while (k < len) {
437 const UChar *p = d + k;
438 UChar c = *p;
439 if (c == '%') {
440 int charLen = 0;
441 if (k <= len - 3 && isxdigit(p[1].uc) && isxdigit(p[2].uc)) {
442 const char b0 = Lexer::convertHex(p[1].uc, p[2].uc);
443 const int sequenceLen = UTF8SequenceLength(b0);
444 if (sequenceLen != 0 && k <= len - sequenceLen * 3) {
445 charLen = sequenceLen * 3;
446 char sequence[5];
447 sequence[0] = b0;
448 for (int i = 1; i < sequenceLen; ++i) {
449 const UChar *q = p + i * 3;
450 if (q[0] == '%' && isxdigit(q[1].uc) && isxdigit(q[2].uc))
451 sequence[i] = Lexer::convertHex(q[1].uc, q[2].uc);
452 else {
453 charLen = 0;
454 break;
455 }
456 }
457 if (charLen != 0) {
458 sequence[sequenceLen] = 0;
459 const int character = decodeUTF8Sequence(sequence);
460 if (character < 0 || character >= 0x110000) {
461 charLen = 0;
462 } else if (character >= 0x10000) {
463 // Convert to surrogate pair.
464 s.append(static_cast<unsigned short>(0xD800 | ((character - 0x10000) >> 10)));
465 u = static_cast<unsigned short>(0xDC00 | ((character - 0x10000) & 0x3FF));
466 } else {
467 u = static_cast<unsigned short>(character);
468 }
469 }
470 }
471 }
472 if (charLen == 0) {
473 if (strict) {
474 Object error = Error::create(exec, URIError);
475 exec->setException(error);
476 return error;
477 }
478 // The only case where we don't use "strict" mode is the "unescape" function.
479 // For that, it's good to support the wonky "%u" syntax for compatibility with WinIE.
480 if (k <= len - 6 && p[1] == 'u'
481 && isxdigit(p[2].uc) && isxdigit(p[3].uc)
482 && isxdigit(p[4].uc) && isxdigit(p[5].uc)) {
483 charLen = 6;
484 u = Lexer::convertUnicode(p[2].uc, p[3].uc, p[4].uc, p[5].uc);
485 }
486 }
487 if (charLen && (u.uc == 0 || u.uc >= 128 || !strchr(do_not_unescape, u.low()))) {
488 c = u;
489 k += charLen - 1;
490 }
491 }
492 k++;
493 s.append(c);
494 }
495 return String(s);
496}
497
498static bool isStrWhiteSpace(unsigned short c)
499{
500 switch (c) {
501 case 0x0009:
502 case 0x000A:
503 case 0x000B:
504 case 0x000C:
505 case 0x000D:
506 case 0x0020:
507 case 0x00A0:
508 case 0x2028:
509 case 0x2029:
510 return true;
511 default:
512#if APPLE_CHANGES
513 return u_charType(c) == U_SPACE_SEPARATOR;
514#else
515 // ### properly support other Unicode Zs characters
516 return false;
517#endif
518 }
519}
520
521static int parseDigit(unsigned short c, int radix)
522{
523 int digit = -1;
524
525 if (c >= '0' && c <= '9') {
526 digit = c - '0';
527 } else if (c >= 'A' && c <= 'Z') {
528 digit = c - 'A' + 10;
529 } else if (c >= 'a' && c <= 'z') {
530 digit = c - 'a' + 10;
531 }
532
533 if (digit >= radix)
534 return -1;
535 return digit;
536}
537
538static double parseInt(const UString &s, int radix)
539{
540 int length = s.size();
541 int p = 0;
542
543 while (p < length && isStrWhiteSpace(s[p].uc)) {
544 ++p;
545 }
546
547 double sign = 1;
548 if (p < length) {
549 if (s[p] == '+') {
550 ++p;
551 } else if (s[p] == '-') {
552 sign = -1;
553 ++p;
554 }
555 }
556
557 if ((radix == 0 || radix == 16) && length - p >= 2 && s[p] == '0' && (s[p + 1] == 'x' || s[p + 1] == 'X')) {
558 radix = 16;
559 p += 2;
560 } else if (radix == 0) {
561 if (p < length && s[p] == '0')
562 radix = 8;
563 else
564 radix = 10;
565 }
566
567 if (radix < 2 || radix > 36)
568 return NaN;
569
570 bool sawDigit = false;
571 double number = 0;
572 while (p < length) {
573 int digit = parseDigit(s[p].uc, radix);
574 if (digit == -1)
575 break;
576 sawDigit = true;
577 number *= radix;
578 number += digit;
579 ++p;
580 }
581
582 if (!sawDigit)
583 return NaN;
584
585 return sign * number;
586}
587
588static double parseFloat(const UString &s)
589{
590 // Check for 0x prefix here, because toDouble allows it, but we must treat it as 0.
591 // Need to skip any whitespace and then one + or - sign.
592 int length = s.size();
593 int p = 0;
594 while (p < length && isStrWhiteSpace(s[p].uc)) {
595 ++p;
596 }
597 if (p < length && (s[p] == '+' || s[p] == '-')) {
598 ++p;
599 }
600 if (length - p >= 2 && s[p] == '0' && (s[p + 1] == 'x' || s[p + 1] == 'X')) {
601 return 0;
602 }
603
604 return s.toDouble( true /*tolerant*/, false /* NaN for empty string */ );
605}
606
607Value GlobalFuncImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
608{
609 Value res;
610
611 static const char do_not_escape[] =
612 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
613 "abcdefghijklmnopqrstuvwxyz"
614 "0123456789"
615 "*+-./@_";
616
617 static const char do_not_escape_when_encoding_URI_component[] =
618 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
619 "abcdefghijklmnopqrstuvwxyz"
620 "0123456789"
621 "!'()*-._~";
622 static const char do_not_escape_when_encoding_URI[] =
623 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
624 "abcdefghijklmnopqrstuvwxyz"
625 "0123456789"
626 "!#$&'()*+,-./:;=?@_~";
627 static const char do_not_unescape_when_decoding_URI[] =
628 "#$&+,/:;=?@";
629
630 switch (id) {
631 case Eval: { // eval()
632 Value x = args[0];
633 if (x.type() != StringType)
634 return x;
635 else {
636 UString s = x.toString(exec);
637
638 int sid;
639 int errLine;
640 UString errMsg;
641 ProgramNode *progNode = Parser::parse(UString(), 0, s.data(),s.size(),&sid,&errLine,&errMsg);
642
643 // no program node means a syntax occurred
644 if (!progNode) {
645 Object err = Error::create(exec,SyntaxError,errMsg.ascii(),errLine);
646 err.put(exec,"sid",Number(sid));
647 exec->setException(err);
648 return err;
649 }
650
651 progNode->ref();
652
653 // enter a new execution context
654 Object thisVal(Object::dynamicCast(exec->context().thisValue()));
655 ContextImp ctx(exec->dynamicInterpreter()->globalObject(),
656 exec->dynamicInterpreter()->imp(),
657 thisVal,
658 EvalCode,
659 exec->context().imp());
660
661 ExecState newExec(exec->dynamicInterpreter(), &ctx);
662 newExec.setException(exec->exception()); // could be null
663
664 // execute the code
665 Completion c = progNode->execute(&newExec);
666
667 // if an exception occured, propogate it back to the previous execution object
668 if (newExec.hadException())
669 exec->setException(newExec.exception());
670
671 if ( progNode->deref() )
672 delete progNode;
673 if (c.complType() == ReturnValue)
674 return c.value();
675 // ### setException() on throw?
676 else if (c.complType() == Normal) {
677 if (c.isValueCompletion())
678 return c.value();
679 else
680 return Undefined();
681 } else {
682 return Undefined();
683 }
684 }
685 break;
686 }
687 case ParseInt:
688 res = Number(parseInt(args[0].toString(exec), args[1].toInt32(exec)));
689 break;
690 case ParseFloat:
691 res = Number(parseFloat(args[0].toString(exec)));
692 break;
693 case IsNaN:
694 res = Boolean(isNaN(args[0].toNumber(exec)));
695 break;
696 case IsFinite: {
697 double n = args[0].toNumber(exec);
698 res = Boolean(!isNaN(n) && !isInf(n));
699 break;
700 }
701 case DecodeURI:
702 res = decode(exec, args, do_not_unescape_when_decoding_URI, true);
703 break;
704 case DecodeURIComponent:
705 res = decode(exec, args, "", true);
706 break;
707 case EncodeURI:
708 res = encode(exec, args, do_not_escape_when_encoding_URI);
709 break;
710 case EncodeURIComponent:
711 res = encode(exec, args, do_not_escape_when_encoding_URI_component);
712 break;
713 case Escape:
714 {
715 UString r = "", s, str = args[0].toString(exec);
716 const UChar *c = str.data();
717 for (int k = 0; k < str.size(); k++, c++) {
718 int u = c->uc;
719 if (u > 255) {
720 char tmp[7];
721 sprintf(tmp, "%%u%04X", u);
722 s = UString(tmp);
723 } else if (strchr(do_not_escape, (char)u)) {
724 s = UString(c, 1);
725 } else {
726 char tmp[4];
727 sprintf(tmp, "%%%02X", u);
728 s = UString(tmp);
729 }
730 r += s;
731 }
732 res = String(r);
733 break;
734 }
735 case UnEscape:
736 {
737 UString s, str = args[0].toString(exec);
738 int k = 0, len = str.size();
739 while (k < len) {
740 const UChar *c = str.data() + k;
741 UChar u;
742 if (*c == UChar('%') && k <= len - 6 && *(c+1) == UChar('u')) {
743 if (Lexer::isHexDigit((c+2)->uc) && Lexer::isHexDigit((c+3)->uc) &&
744 Lexer::isHexDigit((c+4)->uc) && Lexer::isHexDigit((c+5)->uc)) {
745 u = Lexer::convertUnicode((c+2)->uc, (c+3)->uc,
746 (c+4)->uc, (c+5)->uc);
747 c = &u;
748 k += 5;
749 }
750 } else if (*c == UChar('%') && k <= len - 3 &&
751 Lexer::isHexDigit((c+1)->uc) && Lexer::isHexDigit((c+2)->uc)) {
752 u = UChar(Lexer::convertHex((c+1)->uc, (c+2)->uc));
753 c = &u;
754 k += 2;
755 }
756 k++;
757 s += UString(c, 1);
758 }
759 res = String(s);
760 break;
761 }
762#ifndef NDEBUG
763 case KJSPrint:
764 puts(args[0].toString(exec).ascii());
765 break;
766#endif
767 }
768
769 return res;
770}
771
772} // namespace
Note: See TracBrowser for help on using the repository browser.