1 | // -*- c-basic-offset: 2 -*- |
2 | /* |
3 | * This file is part of the KDE libraries |
4 | * Copyright (C) 1999-2002 Harri Porten (porten@kde.org) |
5 | * Copyright (C) 2001 Peter Kelly (pmk@post.com) |
6 | * Copyright (C) 2003 Apple Computer, Inc. |
7 | * Copyright (C) 2007 Cameron Zwarich (cwzwarich@uwaterloo.ca) |
8 | * Copyright (C) 2007 Maksim Orlovich (maksim@kde.org) |
9 | * |
10 | * This library is free software; you can redistribute it and/or |
11 | * modify it under the terms of the GNU Library General Public |
12 | * License as published by the Free Software Foundation; either |
13 | * version 2 of the License, or (at your option) any later version. |
14 | * |
15 | * This library is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 | * Library General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU Library General Public License |
21 | * along with this library; see the file COPYING.LIB. If not, write to |
22 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
23 | * Boston, MA 02110-1301, USA. |
24 | * |
25 | */ |
26 | |
27 | #include "function.h" |
28 | #include <config-kjs.h> |
29 | #include "scriptfunction.h" |
30 | #include "dtoa.h" |
31 | #include "internal.h" |
32 | #include "function_object.h" |
33 | #include "lexer.h" |
34 | #include "nodes.h" |
35 | #include "operations.h" |
36 | #include "debugger.h" |
37 | #include "PropertyNameArray.h" |
38 | #include "commonunicode.h" |
39 | |
40 | #include <stdio.h> |
41 | #include <errno.h> |
42 | #include <stdlib.h> |
43 | #include <assert.h> |
44 | #include <string.h> |
45 | #include <string> |
46 | #include "wtf/DisallowCType.h" |
47 | #include "wtf/ASCIICType.h" |
48 | #include "bytecode/machine.h" |
49 | |
50 | using namespace WTF; |
51 | |
52 | //#define KJS_VERBOSE |
53 | |
54 | namespace KJS { |
55 | |
56 | // ----------------------------- FunctionImp ---------------------------------- |
57 | |
58 | const ClassInfo FunctionImp::info = {"Function" , &InternalFunctionImp::info, 0, 0}; |
59 | |
60 | FunctionImp::FunctionImp(ExecState* exec, const Identifier& n, FunctionBodyNode* b, const ScopeChain& sc) |
61 | : InternalFunctionImp(static_cast<FunctionPrototype*> |
62 | (exec->lexicalInterpreter()->builtinFunctionPrototype()), n) |
63 | , body(b) |
64 | , _scope(sc) |
65 | { |
66 | } |
67 | |
68 | void FunctionImp::mark() |
69 | { |
70 | InternalFunctionImp::mark(); |
71 | _scope.mark(); |
72 | } |
73 | |
74 | FunctionImp::~FunctionImp() |
75 | { |
76 | } |
77 | |
78 | void FunctionImp::initialCompile(ExecState* newExec) |
79 | { |
80 | FunctionBodyNode* body = this->body.get(); |
81 | |
82 | // Reserve various slots needed for the activation object. We do it only once, |
83 | // --- isCompiled() would return true even if debugging state changed |
84 | body->reserveSlot(ActivationImp::LengthSlot, false); |
85 | body->reserveSlot(ActivationImp::TearOffNeeded, false); |
86 | body->reserveSlot(ActivationImp::ScopeLink, false /* will mark via ScopeChain::mark() */); |
87 | body->reserveSlot(ActivationImp::FunctionSlot, true); |
88 | body->reserveSlot(ActivationImp::ArgumentsObjectSlot, true); |
89 | |
90 | // Create declarations for parameters, and allocate the symbols. |
91 | // We always just give them sequential positions, to make passInParameters |
92 | // simple (though perhaps wasting memory in the trivial case) |
93 | for (size_t i = 0; i < body->numParams(); ++i) |
94 | body->addSymbolOverwriteID(i + ActivationImp::NumReservedSlots, body->paramName(i), DontDelete); |
95 | |
96 | body->processDecls(newExec); |
97 | body->compile(FunctionCode, newExec->dynamicInterpreter()->debugger() ? Debug : Release); |
98 | } |
99 | |
100 | |
101 | #ifdef KJS_VERBOSE |
102 | static int callDepth; |
103 | static std::string callIndent; |
104 | |
105 | static const char* ind() |
106 | { |
107 | callIndent = "" ; |
108 | for (int i = 0; i < callDepth; ++i) |
109 | callIndent += " " ; |
110 | return callIndent.c_str(); |
111 | } |
112 | |
113 | // Multiline print adding indentation |
114 | static void printInd(const char* str) |
115 | { |
116 | fprintf(stderr, "%s" , ind()); |
117 | for (const char* c = str; *c; ++c) { |
118 | if (*c != '\n') |
119 | fprintf(stderr, "%c" , *c); |
120 | else |
121 | fprintf(stderr, "\n%s" , ind()); |
122 | } |
123 | } |
124 | |
125 | #endif |
126 | |
127 | JSValue* FunctionImp::callAsFunction(ExecState* exec, JSObject* thisObj, const List& args) |
128 | { |
129 | assert(thisObj); |
130 | |
131 | #ifdef KJS_VERBOSE |
132 | ++callDepth; |
133 | #endif |
134 | |
135 | Debugger* dbg = exec->dynamicInterpreter()->debugger(); |
136 | |
137 | // enter a new execution context |
138 | FunctionExecState newExec(exec->dynamicInterpreter(), thisObj, body.get(), exec, this); |
139 | if (exec->hadException()) |
140 | newExec.setException(exec->exception()); |
141 | |
142 | FunctionBodyNode* body = this->body.get(); |
143 | |
144 | // The first time we're called, compute the set of local variables, |
145 | // and compile the body. (note that parameters have been collected |
146 | // during the AST build) |
147 | CompileType currentState = body->compileState(); |
148 | if (currentState == NotCompiled) { |
149 | initialCompile(&newExec); |
150 | } else { |
151 | // Otherwise, we may still need to recompile due to debug... |
152 | CompileType desiredState = dbg ? Debug : Release; |
153 | if (desiredState != currentState) |
154 | body->compile(FunctionCode, desiredState); |
155 | } |
156 | |
157 | size_t stackSize = 0; |
158 | LocalStorageEntry* stackSpace = 0; |
159 | |
160 | // We always allocate on stack initially, and tearoff only after we're done. |
161 | int regs = body->numLocalsAndRegisters(); |
162 | stackSize = sizeof(LocalStorageEntry) * regs; |
163 | stackSpace = (LocalStorageEntry*)exec->dynamicInterpreter()->stackAlloc(stackSize); |
164 | |
165 | ActivationImp* activation = static_cast<ActivationImp*>(newExec.activationObject()); |
166 | activation->setup(&newExec, this, &args, stackSpace); |
167 | activation->tearOffNeededSlot() = body->tearOffAtEnd(); |
168 | |
169 | newExec.initLocalStorage(stackSpace, regs); |
170 | |
171 | JSValue* result = Machine::runBlock(&newExec, body->code(), exec); |
172 | |
173 | // If we need to tear off now --- either due to static flag above, or |
174 | // if execution requested it dynamically --- do so now. |
175 | if (activation->tearOffNeededSlot()) { |
176 | activation->performTearOff(); |
177 | } else { |
178 | // Otherwise, we recycle the activation object; we must clear its |
179 | // data pointer, though, since that may become dead. |
180 | // (we also unlink it from the scope chain at this time) |
181 | activation->scopeLink().deref(); |
182 | activation->localStorage = 0; |
183 | exec->dynamicInterpreter()->recycleActivation(activation); |
184 | } |
185 | |
186 | // Now free the stack space.. |
187 | exec->dynamicInterpreter()->stackFree(stackSize); |
188 | |
189 | #ifdef KJS_VERBOSE |
190 | fprintf(stderr, "%s" , ind()); |
191 | if (exec->exception()) |
192 | printInfo(exec,"throwing" , exec->exception()); |
193 | else |
194 | printInfo(exec,"returning" , result); |
195 | |
196 | --callDepth; |
197 | #endif |
198 | |
199 | return result; |
200 | } |
201 | |
202 | JSValue *FunctionImp::argumentsGetter(ExecState* exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot) |
203 | { |
204 | FunctionImp *thisObj = static_cast<FunctionImp *>(slot.slotBase()); |
205 | ExecState *context = exec; |
206 | while (context) { |
207 | if (context->function() == thisObj) { |
208 | return static_cast<ActivationImp *>(context->activationObject())->get(exec, propertyName); |
209 | } |
210 | context = context->callingExecState(); |
211 | } |
212 | return jsNull(); |
213 | } |
214 | |
215 | JSValue *FunctionImp::callerGetter(ExecState* exec, JSObject*, const Identifier&, const PropertySlot& slot) |
216 | { |
217 | FunctionImp* thisObj = static_cast<FunctionImp*>(slot.slotBase()); |
218 | ExecState* context = exec; |
219 | while (context) { |
220 | if (context->function() == thisObj) |
221 | break; |
222 | context = context->callingExecState(); |
223 | } |
224 | |
225 | if (!context) |
226 | return jsNull(); |
227 | |
228 | ExecState* callingContext = context->callingExecState(); |
229 | if (!callingContext) |
230 | return jsNull(); |
231 | |
232 | FunctionImp* callingFunction = callingContext->function(); |
233 | if (!callingFunction) |
234 | return jsNull(); |
235 | |
236 | return callingFunction; |
237 | } |
238 | |
239 | JSValue *FunctionImp::lengthGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot& slot) |
240 | { |
241 | FunctionImp *thisObj = static_cast<FunctionImp *>(slot.slotBase()); |
242 | return jsNumber(thisObj->body->numParams()); |
243 | } |
244 | |
245 | JSValue* FunctionImp::nameGetter(ExecState*, JSObject*, const Identifier&, const PropertySlot& slot) |
246 | { |
247 | FunctionImp* thisObj = static_cast<FunctionImp*>(slot.slotBase()); |
248 | return jsString(thisObj->functionName().ustring()); |
249 | } |
250 | |
251 | bool FunctionImp::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) |
252 | { |
253 | // Find the arguments from the closest context. |
254 | if (propertyName == exec->propertyNames().arguments) { |
255 | slot.setCustom(this, argumentsGetter); |
256 | return true; |
257 | } |
258 | |
259 | // Compute length of parameters. |
260 | if (propertyName == exec->propertyNames().length) { |
261 | slot.setCustom(this, lengthGetter); |
262 | return true; |
263 | } |
264 | |
265 | // Calling function (Mozilla-extension) |
266 | if (propertyName == exec->propertyNames().caller) { |
267 | slot.setCustom(this, callerGetter); |
268 | return true; |
269 | } |
270 | |
271 | // Function name (Mozilla-extension) |
272 | if (propertyName == exec->propertyNames().name) { |
273 | slot.setCustom(this, nameGetter); |
274 | return true; |
275 | } |
276 | |
277 | return InternalFunctionImp::getOwnPropertySlot(exec, propertyName, slot); |
278 | } |
279 | |
280 | bool FunctionImp::getOwnPropertyDescriptor(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& desc) |
281 | { |
282 | if (propertyName == exec->propertyNames().length) { |
283 | desc.setPropertyDescriptorValues(exec, jsNumber(body->numParams()), ReadOnly|DontDelete|DontEnum); |
284 | return true; |
285 | } |
286 | |
287 | return KJS::JSObject::getOwnPropertyDescriptor(exec, propertyName, desc); |
288 | } |
289 | |
290 | void FunctionImp::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr) |
291 | { |
292 | if (propertyName == exec->propertyNames().arguments || |
293 | propertyName == exec->propertyNames().length || |
294 | propertyName == exec->propertyNames().name) |
295 | return; |
296 | InternalFunctionImp::put(exec, propertyName, value, attr); |
297 | } |
298 | |
299 | bool FunctionImp::deleteProperty(ExecState *exec, const Identifier &propertyName) |
300 | { |
301 | if (propertyName == exec->propertyNames().arguments || |
302 | propertyName == exec->propertyNames().length || |
303 | propertyName == exec->propertyNames().name) |
304 | return false; |
305 | return InternalFunctionImp::deleteProperty(exec, propertyName); |
306 | } |
307 | |
308 | /* Returns the parameter name corresponding to the given index. eg: |
309 | * function f1(x, y, z): getParameterName(0) --> x |
310 | * |
311 | * If a name appears more than once, only the last index at which |
312 | * it appears associates with it. eg: |
313 | * function f2(x, x): getParameterName(0) --> null |
314 | */ |
315 | Identifier FunctionImp::getParameterName(size_t index) |
316 | { |
317 | if (index >= body->numParams()) |
318 | return CommonIdentifiers::shared()->nullIdentifier; |
319 | |
320 | Identifier name = body->paramName(index); |
321 | |
322 | // Are there any subsequent parameters with the same name? |
323 | for (size_t pos = index + 1; pos < body->numParams(); ++pos) |
324 | if (body->paramName(pos) == name) |
325 | return CommonIdentifiers::shared()->nullIdentifier; |
326 | |
327 | return name; |
328 | } |
329 | |
330 | bool FunctionImp::implementsConstruct() const |
331 | { |
332 | return true; |
333 | } |
334 | |
335 | // ECMA 13.2.2 [[Construct]] |
336 | JSObject *FunctionImp::construct(ExecState *exec, const List &args) |
337 | { |
338 | JSObject *proto; |
339 | JSValue *p = get(exec, exec->propertyNames().prototype); |
340 | if (p->isObject()) |
341 | proto = static_cast<JSObject*>(p); |
342 | else |
343 | proto = exec->lexicalInterpreter()->builtinObjectPrototype(); |
344 | |
345 | JSObject *obj(new JSObject(proto)); |
346 | |
347 | JSValue *res = call(exec,obj,args); |
348 | |
349 | if (res->isObject()) |
350 | return static_cast<JSObject *>(res); |
351 | else |
352 | return obj; |
353 | } |
354 | |
355 | // ------------------------------ Thrower --------------------------------- |
356 | |
357 | Thrower::Thrower(ErrorType type) |
358 | : JSObject(), |
359 | m_type(type) |
360 | { |
361 | } |
362 | |
363 | JSValue* Thrower::callAsFunction(ExecState* exec, JSObject* /*thisObj*/, const List& /*args*/) |
364 | { |
365 | return throwError(exec, m_type); |
366 | } |
367 | |
368 | |
369 | // ------------------------------ BoundFunction --------------------------------- |
370 | |
371 | BoundFunction::BoundFunction(ExecState* exec, JSObject* targetFunction, JSObject* boundThis, KJS::List boundArgs) |
372 | : InternalFunctionImp(static_cast<FunctionPrototype*>(exec->lexicalInterpreter()->builtinFunctionPrototype())), |
373 | m_targetFunction(targetFunction), |
374 | m_boundThis(boundThis), |
375 | m_boundArgs(boundArgs) |
376 | { |
377 | } |
378 | |
379 | // ECMAScript Edition 5.1r6 - 15.3.4.5.2 |
380 | JSObject* BoundFunction::construct(ExecState* exec, const List& ) |
381 | { |
382 | JSObject* target = m_targetFunction; |
383 | if (!target->implementsConstruct()) |
384 | return throwError(exec, TypeError); |
385 | List boundArgs = m_boundArgs; |
386 | |
387 | List args; |
388 | for (int i = 0; i < boundArgs.size(); ++i) |
389 | args.append(boundArgs.at(i)); |
390 | for (int i = 0; i < extraArgs.size(); ++i) |
391 | args.append(extraArgs.at(i)); |
392 | |
393 | return target->construct(exec, args); |
394 | } |
395 | |
396 | // ECMAScript Edition 5.1r6 - 15.3.4.5.1 |
397 | JSValue* BoundFunction::callAsFunction(ExecState* exec, JSObject* /*thisObj*/, const List& ) |
398 | { |
399 | List boundArgs = m_boundArgs; |
400 | JSObject* boundThis = m_boundThis; |
401 | JSObject* target = m_targetFunction; |
402 | |
403 | List args; |
404 | for (int i = 0; i < boundArgs.size(); ++i) |
405 | args.append(boundArgs.at(i)); |
406 | for (int i = 0; i < extraArgs.size(); ++i) |
407 | args.append(extraArgs.at(i)); |
408 | |
409 | return target->callAsFunction(exec, boundThis, args); |
410 | } |
411 | |
412 | // ECMAScript Edition 5.1r6 - 15.3.4.5.3 |
413 | bool BoundFunction::hasInstance(ExecState* exec, JSValue* value) |
414 | { |
415 | JSObject* target = m_targetFunction; |
416 | if (!target->implementsHasInstance()) |
417 | return throwError(exec, TypeError); |
418 | |
419 | return target->hasInstance(exec, value); |
420 | } |
421 | |
422 | void BoundFunction::setTargetFunction(JSObject* targetFunction) |
423 | { |
424 | m_targetFunction = targetFunction; |
425 | } |
426 | |
427 | void BoundFunction::setBoundArgs(const List& boundArgs) |
428 | { |
429 | m_boundArgs = boundArgs; |
430 | } |
431 | |
432 | void BoundFunction::setBoundThis(JSObject* boundThis) |
433 | { |
434 | m_boundThis = boundThis; |
435 | } |
436 | |
437 | |
438 | // ------------------------------ IndexToNameMap --------------------------------- |
439 | |
440 | // We map indexes in the arguments array to their corresponding argument names. |
441 | // Example: function f(x, y, z): arguments[0] = x, so we map 0 to Identifier("x"). |
442 | |
443 | // Once we have an argument name, we can get and set the argument's value in the |
444 | // activation object. |
445 | |
446 | // We use Identifier::null to indicate that a given argument's value |
447 | // isn't stored in the activation object. |
448 | |
449 | IndexToNameMap::IndexToNameMap(FunctionImp *func, const List &args) |
450 | { |
451 | _map = new Identifier[args.size()]; |
452 | this->_size = args.size(); |
453 | |
454 | size_t i = 0; |
455 | ListIterator iterator = args.begin(); |
456 | for (; iterator != args.end(); i++, iterator++) |
457 | _map[i] = func->getParameterName(i); // null if there is no corresponding parameter |
458 | } |
459 | |
460 | IndexToNameMap::~IndexToNameMap() { |
461 | delete [] _map; |
462 | } |
463 | |
464 | bool IndexToNameMap::isMapped(const Identifier &index) const |
465 | { |
466 | bool indexIsNumber; |
467 | int indexAsNumber = index.toStrictUInt32(&indexIsNumber); |
468 | |
469 | if (!indexIsNumber) |
470 | return false; |
471 | |
472 | if (indexAsNumber >= _size) |
473 | return false; |
474 | |
475 | if (_map[indexAsNumber].isNull()) |
476 | return false; |
477 | |
478 | return true; |
479 | } |
480 | |
481 | void IndexToNameMap::unMap(const Identifier &index) |
482 | { |
483 | bool indexIsNumber; |
484 | int indexAsNumber = index.toStrictUInt32(&indexIsNumber); |
485 | |
486 | assert(indexIsNumber && indexAsNumber < _size); |
487 | |
488 | _map[indexAsNumber] = CommonIdentifiers::shared()->nullIdentifier;; |
489 | } |
490 | |
491 | int IndexToNameMap::size() const |
492 | { |
493 | return _size; |
494 | } |
495 | |
496 | Identifier& IndexToNameMap::operator[](int index) |
497 | { |
498 | return _map[index]; |
499 | } |
500 | |
501 | Identifier& IndexToNameMap::operator[](const Identifier &index) |
502 | { |
503 | bool indexIsNumber; |
504 | int indexAsNumber = index.toStrictUInt32(&indexIsNumber); |
505 | |
506 | assert(indexIsNumber && indexAsNumber < _size); |
507 | |
508 | return (*this)[indexAsNumber]; |
509 | } |
510 | |
511 | // ------------------------------ Arguments --------------------------------- |
512 | |
513 | const ClassInfo Arguments::info = {"Arguments" , 0, 0, 0}; |
514 | |
515 | // ECMA 10.1.8 |
516 | Arguments::Arguments(ExecState *exec, FunctionImp *func, const List &args, ActivationImp *act) |
517 | : JSObject(exec->lexicalInterpreter()->builtinObjectPrototype()), |
518 | _activationObject(act), |
519 | indexToNameMap(func, args) |
520 | { |
521 | putDirect(exec->propertyNames().callee, func, DontEnum); |
522 | putDirect(exec->propertyNames().length, args.size(), DontEnum); |
523 | |
524 | int i = 0; |
525 | ListIterator iterator = args.begin(); |
526 | for (; iterator != args.end(); i++, iterator++) { |
527 | if (!indexToNameMap.isMapped(Identifier::from(i))) { |
528 | //ECMAScript Edition 5.1r6 - 10.6.11.b, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true |
529 | JSObject::put(exec, Identifier::from(i), *iterator, None); |
530 | } |
531 | } |
532 | } |
533 | |
534 | void Arguments::mark() |
535 | { |
536 | JSObject::mark(); |
537 | if (_activationObject && !_activationObject->marked()) |
538 | _activationObject->mark(); |
539 | } |
540 | |
541 | JSValue *Arguments::mappedIndexGetter(ExecState* exec, JSObject*, const Identifier& propertyName, const PropertySlot& slot) |
542 | { |
543 | Arguments *thisObj = static_cast<Arguments *>(slot.slotBase()); |
544 | return thisObj->_activationObject->get(exec, thisObj->indexToNameMap[propertyName]); |
545 | } |
546 | |
547 | bool Arguments::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot) |
548 | { |
549 | if (indexToNameMap.isMapped(propertyName)) { |
550 | slot.setCustom(this, mappedIndexGetter); |
551 | return true; |
552 | } |
553 | |
554 | return JSObject::getOwnPropertySlot(exec, propertyName, slot); |
555 | } |
556 | |
557 | void Arguments::put(ExecState *exec, const Identifier &propertyName, JSValue *value, int attr) |
558 | { |
559 | if (indexToNameMap.isMapped(propertyName)) { |
560 | unsigned attr = 0; |
561 | JSObject::getPropertyAttributes(propertyName, attr); |
562 | if (attr & ReadOnly) |
563 | return; |
564 | |
565 | _activationObject->put(exec, indexToNameMap[propertyName], value, attr); |
566 | } else { |
567 | JSObject::put(exec, propertyName, value, attr); |
568 | } |
569 | } |
570 | |
571 | bool Arguments::deleteProperty(ExecState *exec, const Identifier &propertyName) |
572 | { |
573 | if (indexToNameMap.isMapped(propertyName)) { |
574 | bool result = JSObject::deleteProperty(exec, propertyName); |
575 | if (result) { |
576 | _activationObject->deleteProperty(exec, indexToNameMap[propertyName]); |
577 | indexToNameMap.unMap(propertyName); |
578 | } |
579 | return true; |
580 | } else { |
581 | return JSObject::deleteProperty(exec, propertyName); |
582 | } |
583 | } |
584 | |
585 | void Arguments::getOwnPropertyNames(ExecState* exec, PropertyNameArray& propertyNames, PropertyMap::PropertyMode mode) |
586 | { |
587 | unsigned int length = indexToNameMap.size(); |
588 | unsigned attr; |
589 | for (unsigned int i = 0; i < length; ++i) { |
590 | attr = 0; |
591 | Identifier ident = Identifier::from(i); |
592 | |
593 | if (indexToNameMap.isMapped(ident) && |
594 | _activationObject->getPropertyAttributes(indexToNameMap[ident], attr)) { |
595 | if (PropertyMap::checkEnumerable(attr, mode)) { |
596 | propertyNames.add(ident); |
597 | } |
598 | } |
599 | } |
600 | |
601 | JSObject::getOwnPropertyNames(exec, propertyNames, mode); |
602 | } |
603 | |
604 | bool Arguments::defineOwnProperty(ExecState* exec, const Identifier& propertyName, PropertyDescriptor& desc, bool shouldThrow) |
605 | { |
606 | bool isMapped = indexToNameMap.isMapped(propertyName); |
607 | |
608 | Identifier mappedName; |
609 | if (isMapped) |
610 | mappedName = indexToNameMap[propertyName]; |
611 | else |
612 | mappedName = propertyName; |
613 | |
614 | bool allowed = JSObject::defineOwnProperty(exec, propertyName, desc, false); |
615 | |
616 | if (!allowed) { |
617 | if (shouldThrow) |
618 | throwError(exec, TypeError); |
619 | return false; |
620 | } |
621 | if (isMapped) { |
622 | if (desc.isAccessorDescriptor()) { |
623 | indexToNameMap.unMap(propertyName); |
624 | } else { |
625 | if (desc.value()) { |
626 | _activationObject->putDirect(mappedName, desc.value(), desc.attributes()); |
627 | } |
628 | if (desc.writableSet() && desc.writable() == false) { |
629 | indexToNameMap.unMap(propertyName); |
630 | } |
631 | } |
632 | } |
633 | |
634 | return true; |
635 | } |
636 | |
637 | // ------------------------------ ActivationImp -------------------------------- |
638 | |
639 | const ClassInfo ActivationImp::info = {"Activation" , 0, 0, 0}; |
640 | |
641 | // ECMA 10.1.6 |
642 | void ActivationImp::setup(ExecState* exec, FunctionImp *function, |
643 | const List* arguments, LocalStorageEntry* entries) |
644 | { |
645 | FunctionBodyNode* body = function->body.get(); |
646 | |
647 | size_t total = body->numLocalsAndRegisters(); |
648 | localStorage = entries; |
649 | lengthSlot() = total; |
650 | |
651 | // we can now link ourselves into the scope, which will also fix up our scopeLink(). |
652 | exec->pushVariableObjectScope(this); |
653 | |
654 | const FunctionBodyNode::SymbolInfo* symInfo = body->getLocalInfo(); |
655 | |
656 | // Setup our fields |
657 | this->arguments = arguments; |
658 | functionSlot() = function; |
659 | argumentsObjectSlot() = jsUndefined(); |
660 | symbolTable = &body->symbolTable(); |
661 | |
662 | // Set the mark/don't mark flags and attributes for everything |
663 | for (size_t p = 0; p < total; ++p) |
664 | entries[p].attributes = symInfo[p].attr; |
665 | |
666 | // Pass in the parameters (ECMA 10.1.3q) |
667 | #ifdef KJS_VERBOSE |
668 | fprintf(stderr, "%s---------------------------------------------------\n" |
669 | "%sprocessing parameters for %s call\n" , ind(), ind(), |
670 | function->functionName().isEmpty() ? "(internal)" : function->functionName().ascii()); |
671 | #endif |
672 | size_t numParams = body->numParams(); |
673 | size_t numPassedIn = min(numParams, static_cast<size_t>(arguments->size())); |
674 | |
675 | size_t pos = 0; |
676 | for (; pos < numPassedIn; ++pos) { |
677 | size_t symNum = pos + ActivationImp::NumReservedSlots; |
678 | JSValue* v = arguments->atUnchecked(pos); |
679 | |
680 | entries[symNum].val.valueVal = v; |
681 | |
682 | #ifdef KJS_VERBOSE |
683 | fprintf(stderr, "%s setting parameter %s" , ind(), body->paramName(pos).ascii()); |
684 | printInfo(exec, "to" , v); |
685 | #endif |
686 | } |
687 | |
688 | for (; pos < numParams; ++pos) { |
689 | size_t symNum = pos + ActivationImp::NumReservedSlots; |
690 | entries[symNum].val.valueVal = jsUndefined(); |
691 | |
692 | #ifdef KJS_VERBOSE |
693 | fprintf(stderr, "%s setting parameter %s to undefined (not passed in)" , ind(), body->paramName(pos).ascii()); |
694 | #endif |
695 | } |
696 | |
697 | #ifdef KJS_VERBOSE |
698 | fprintf(stderr, "\n%s---------------------------------\n" , ind()); |
699 | fprintf(stderr, "%sBody:\n" , ind()); |
700 | fprintf(stderr, "%s---------------------------------\n" , ind()); |
701 | printInd(body->toString().ascii()); |
702 | fprintf(stderr, "\n%s---------------------------------\n\n" , ind()); |
703 | #endif |
704 | |
705 | // Initialize the rest of the locals to 'undefined' |
706 | for (size_t pos = numParams + ActivationImp::NumReservedSlots; pos < total; ++pos) |
707 | entries[pos].val.valueVal = jsUndefined(); |
708 | |
709 | // Finally, put in the functions. Note that this relies on above |
710 | // steps to have completed, since it can trigger a GC. |
711 | size_t numFuns = body->numFunctionLocals(); |
712 | size_t* funsData = body->getFunctionLocalInfo(); |
713 | for (size_t fun = 0; fun < numFuns; ++fun) { |
714 | size_t id = funsData[fun]; |
715 | entries[id].val.valueVal = symInfo[id].funcDecl->makeFunctionObject(exec); |
716 | } |
717 | } |
718 | |
719 | void ActivationImp::performTearOff() |
720 | { |
721 | // Create a new local array, copy stuff over |
722 | size_t total = lengthSlot(); |
723 | LocalStorageEntry* entries = new LocalStorageEntry[total]; |
724 | std::memcpy(entries, localStorage, total*sizeof(LocalStorageEntry)); |
725 | localStorage = entries; |
726 | } |
727 | |
728 | void ActivationImp::requestTearOff() |
729 | { |
730 | tearOffNeededSlot() = true; |
731 | } |
732 | |
733 | JSValue *ActivationImp::argumentsGetter(ExecState* exec, JSObject*, const Identifier&, const PropertySlot& slot) |
734 | { |
735 | ActivationImp* thisObj = static_cast<ActivationImp*>(slot.slotBase()); |
736 | |
737 | if (thisObj->argumentsObjectSlot() == jsUndefined()) |
738 | thisObj->createArgumentsObject(exec); |
739 | |
740 | return thisObj->argumentsObjectSlot(); |
741 | } |
742 | |
743 | |
744 | PropertySlot::GetValueFunc ActivationImp::getArgumentsGetter() |
745 | { |
746 | return ActivationImp::argumentsGetter; |
747 | } |
748 | |
749 | bool ActivationImp::getOwnPropertySlot(ExecState *exec, const Identifier& propertyName, PropertySlot& slot) |
750 | { |
751 | if (symbolTableGet(propertyName, slot)) |
752 | return true; |
753 | |
754 | if (JSValue** location = getDirectLocation(propertyName)) { |
755 | slot.setValueSlot(this, location); |
756 | return true; |
757 | } |
758 | |
759 | // Only return the built-in arguments object if it wasn't overridden above. |
760 | if (propertyName == exec->propertyNames().arguments) { |
761 | slot.setCustom(this, getArgumentsGetter()); |
762 | return true; |
763 | } |
764 | |
765 | // We don't call through to JSObject because there's no way to give an |
766 | // activation object getter properties or a prototype. |
767 | ASSERT(!_prop.hasGetterSetterProperties()); |
768 | ASSERT(prototype() == jsNull()); |
769 | return false; |
770 | } |
771 | |
772 | bool ActivationImp::deleteProperty(ExecState *exec, const Identifier &propertyName) |
773 | { |
774 | if (propertyName == exec->propertyNames().arguments) |
775 | return false; |
776 | |
777 | return JSVariableObject::deleteProperty(exec, propertyName); |
778 | } |
779 | |
780 | void ActivationImp::putDirect(const Identifier& propertyName, JSValue* value, int attr) |
781 | { |
782 | size_t index = symbolTable->get(propertyName.ustring().rep()); |
783 | if (index != missingSymbolMarker()) { |
784 | LocalStorageEntry& entry = localStorage[index]; |
785 | entry.val.valueVal = value; |
786 | entry.attributes = attr; |
787 | return; |
788 | } |
789 | |
790 | JSVariableObject::putDirect(propertyName, value, attr); |
791 | } |
792 | |
793 | JSValue* ActivationImp::getDirect(const Identifier& propertyName) const |
794 | { |
795 | size_t index = symbolTable->get(propertyName.ustring().rep()); |
796 | if (index != missingSymbolMarker()) { |
797 | LocalStorageEntry& entry = localStorage[index]; |
798 | return entry.val.valueVal; |
799 | } |
800 | |
801 | return JSVariableObject::getDirect(propertyName); |
802 | } |
803 | |
804 | bool ActivationImp::getPropertyAttributes(const Identifier& propertyName, unsigned int& attributes) const |
805 | { |
806 | size_t index = symbolTable->get(propertyName.ustring().rep()); |
807 | if (index != missingSymbolMarker()) { |
808 | LocalStorageEntry& entry = localStorage[index]; |
809 | attributes = entry.attributes; |
810 | return true; |
811 | } |
812 | |
813 | return JSVariableObject::getPropertyAttributes(propertyName, attributes); |
814 | } |
815 | |
816 | void ActivationImp::put(ExecState*, const Identifier& propertyName, JSValue* value, int attr) |
817 | { |
818 | // If any bits other than DontDelete are set, then we bypass the read-only check. |
819 | bool checkReadOnly = !(attr & ~DontDelete); |
820 | if (symbolTablePut(propertyName, value, checkReadOnly)) |
821 | return; |
822 | |
823 | // We don't call through to JSObject because __proto__ and getter/setter |
824 | // properties are non-standard extensions that other implementations do not |
825 | // expose in the activation object. |
826 | ASSERT(!_prop.hasGetterSetterProperties()); |
827 | _prop.put(propertyName, value, attr, checkReadOnly); |
828 | } |
829 | |
830 | void ActivationImp::createArgumentsObject(ExecState *exec) |
831 | { |
832 | requestTearOff(); |
833 | argumentsObjectSlot() = new Arguments(exec, static_cast<FunctionImp*>(functionSlot()), |
834 | *arguments, const_cast<ActivationImp*>(this)); |
835 | } |
836 | |
837 | // ------------------------------ GlobalFunc ----------------------------------- |
838 | |
839 | |
840 | GlobalFuncImp::GlobalFuncImp(ExecState* exec, FunctionPrototype* funcProto, int i, int len, const Identifier& name) |
841 | : InternalFunctionImp(funcProto, name) |
842 | , id(i) |
843 | { |
844 | putDirect(exec->propertyNames().length, len, DontDelete|ReadOnly|DontEnum); |
845 | } |
846 | |
847 | static JSValue *encode(ExecState *exec, const List &args, const char *do_not_escape) |
848 | { |
849 | UString r = "" , s, str = args[0]->toString(exec); |
850 | CString cstr = str.UTF8String(); |
851 | const char *p = cstr.c_str(); |
852 | for (size_t k = 0; k < cstr.size(); k++, p++) { |
853 | char c = *p; |
854 | if (c && strchr(do_not_escape, c)) { |
855 | r.append(c); |
856 | } else { |
857 | char tmp[4]; |
858 | sprintf(tmp, "%%%02X" , (unsigned char)c); |
859 | r += tmp; |
860 | } |
861 | } |
862 | return jsString(r); |
863 | } |
864 | |
865 | static JSValue *decode(ExecState *exec, const List &args, const char *do_not_unescape) |
866 | { |
867 | UString s = "" , str = args[0]->toString(exec); |
868 | int k = 0, len = str.size(); |
869 | const UChar *d = str.data(); |
870 | UChar u; |
871 | while (k < len) { |
872 | const UChar *p = d + k; |
873 | UChar c = *p; |
874 | if (c == '%') { |
875 | int charLen = 0; |
876 | if (k <= len - 3 && isASCIIHexDigit(p[1].uc) && isASCIIHexDigit(p[2].uc)) { |
877 | const char b0 = Lexer::convertHex(p[1].uc, p[2].uc); |
878 | const int sequenceLen = UTF8SequenceLength(b0); |
879 | if (sequenceLen != 0 && k <= len - sequenceLen * 3) { |
880 | charLen = sequenceLen * 3; |
881 | char sequence[5]; |
882 | sequence[0] = b0; |
883 | for (int i = 1; i < sequenceLen; ++i) { |
884 | const UChar *q = p + i * 3; |
885 | if (q[0] == '%' && isASCIIHexDigit(q[1].uc) && isASCIIHexDigit(q[2].uc)) |
886 | sequence[i] = Lexer::convertHex(q[1].uc, q[2].uc); |
887 | else { |
888 | charLen = 0; |
889 | break; |
890 | } |
891 | } |
892 | if (charLen != 0) { |
893 | sequence[sequenceLen] = 0; |
894 | const int character = decodeUTF8Sequence(sequence); |
895 | if (character < 0 || character >= 0x110000) { |
896 | charLen = 0; |
897 | } else if (character >= 0x10000) { |
898 | // Convert to surrogate pair. |
899 | s.append(static_cast<unsigned short>(0xD800 | ((character - 0x10000) >> 10))); |
900 | u = static_cast<unsigned short>(0xDC00 | ((character - 0x10000) & 0x3FF)); |
901 | } else { |
902 | u = static_cast<unsigned short>(character); |
903 | } |
904 | } |
905 | } |
906 | } |
907 | if (charLen == 0) |
908 | return throwError(exec, URIError); |
909 | if (u.uc == 0 || u.uc >= 128 || !strchr(do_not_unescape, u.low())) { |
910 | c = u; |
911 | k += charLen - 1; |
912 | } |
913 | } |
914 | k++; |
915 | s.append(c); |
916 | } |
917 | return jsString(s); |
918 | } |
919 | |
920 | static int parseDigit(unsigned short c, int radix) |
921 | { |
922 | int digit = -1; |
923 | |
924 | if (c >= '0' && c <= '9') { |
925 | digit = c - '0'; |
926 | } else if (c >= 'A' && c <= 'Z') { |
927 | digit = c - 'A' + 10; |
928 | } else if (c >= 'a' && c <= 'z') { |
929 | digit = c - 'a' + 10; |
930 | } |
931 | |
932 | if (digit >= radix) |
933 | return -1; |
934 | return digit; |
935 | } |
936 | |
937 | double parseIntOverflow(const char* s, int length, int radix) |
938 | { |
939 | double number = 0.0; |
940 | double radixMultiplier = 1.0; |
941 | |
942 | for (const char* p = s + length - 1; p >= s; p--) { |
943 | if (radixMultiplier == Inf) { |
944 | if (*p != '0') { |
945 | number = Inf; |
946 | break; |
947 | } |
948 | } else { |
949 | int digit = parseDigit(*p, radix); |
950 | number += digit * radixMultiplier; |
951 | } |
952 | |
953 | radixMultiplier *= radix; |
954 | } |
955 | |
956 | return number; |
957 | } |
958 | |
959 | double parseInt(const UString &s, int radix) |
960 | { |
961 | int length = s.size(); |
962 | int p = 0; |
963 | |
964 | while (p < length && CommonUnicode::isStrWhiteSpace(s[p].uc)) { |
965 | ++p; |
966 | } |
967 | |
968 | double sign = 1; |
969 | if (p < length) { |
970 | if (s[p] == '+') { |
971 | ++p; |
972 | } else if (s[p] == '-') { |
973 | sign = -1; |
974 | ++p; |
975 | } |
976 | } |
977 | |
978 | if ((radix == 0 || radix == 16) && length - p >= 2 && s[p] == '0' && (s[p + 1] == 'x' || s[p + 1] == 'X')) { |
979 | radix = 16; |
980 | p += 2; |
981 | } else if (radix == 0) { |
982 | // ECMAscript test262 S15.1.2.2_A5.1_T1 says we should no longer accept octal. To fix remove next 3 lines. |
983 | if (p < length && s[p] == '0') |
984 | radix = 8; |
985 | else |
986 | radix = 10; |
987 | } |
988 | |
989 | if (radix < 2 || radix > 36) |
990 | return NaN; |
991 | |
992 | int firstDigitPosition = p; |
993 | bool sawDigit = false; |
994 | double number = 0; |
995 | while (p < length) { |
996 | int digit = parseDigit(s[p].uc, radix); |
997 | if (digit == -1) |
998 | break; |
999 | sawDigit = true; |
1000 | number *= radix; |
1001 | number += digit; |
1002 | ++p; |
1003 | } |
1004 | |
1005 | if (number >= mantissaOverflowLowerBound) { |
1006 | if (radix == 10) |
1007 | number = kjs_strtod(s.substr(firstDigitPosition, p - firstDigitPosition).ascii(), 0); |
1008 | else if (radix == 2 || radix == 4 || radix == 8 || radix == 16 || radix == 32) |
1009 | number = parseIntOverflow(s.substr(firstDigitPosition, p - firstDigitPosition).ascii(), p - firstDigitPosition, radix); |
1010 | } |
1011 | |
1012 | if (!sawDigit) |
1013 | return NaN; |
1014 | |
1015 | return sign * number; |
1016 | } |
1017 | |
1018 | double parseFloat(const UString &s) |
1019 | { |
1020 | // Check for 0x prefix here, because toDouble allows it, but we must treat it as 0. |
1021 | // Need to skip any whitespace and then one + or - sign. |
1022 | int length = s.size(); |
1023 | int p = 0; |
1024 | while (p < length && CommonUnicode::isStrWhiteSpace(s[p].uc)) { |
1025 | ++p; |
1026 | } |
1027 | if (p < length && (s[p] == '+' || s[p] == '-')) { |
1028 | ++p; |
1029 | } |
1030 | if (length - p >= 2 && s[p] == '0' && (s[p + 1] == 'x' || s[p + 1] == 'X')) { |
1031 | return 0; |
1032 | } |
1033 | |
1034 | return s.toDouble( true /*tolerant*/, false /* NaN for empty string */ ); |
1035 | } |
1036 | |
1037 | JSValue *GlobalFuncImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args) |
1038 | { |
1039 | JSValue *res = jsUndefined(); |
1040 | |
1041 | static const char do_not_escape[] = |
1042 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
1043 | "abcdefghijklmnopqrstuvwxyz" |
1044 | "0123456789" |
1045 | "*+-./@_" ; |
1046 | |
1047 | static const char do_not_escape_when_encoding_URI_component[] = |
1048 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
1049 | "abcdefghijklmnopqrstuvwxyz" |
1050 | "0123456789" |
1051 | "!'()*-._~" ; |
1052 | static const char do_not_escape_when_encoding_URI[] = |
1053 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
1054 | "abcdefghijklmnopqrstuvwxyz" |
1055 | "0123456789" |
1056 | "!#$&'()*+,-./:;=?@_~" ; |
1057 | static const char do_not_unescape_when_decoding_URI[] = |
1058 | "#$&+,/:;=?@" ; |
1059 | |
1060 | switch (id) { |
1061 | case Eval: { // eval() |
1062 | JSValue *x = args[0]; |
1063 | if (!x->isString()) |
1064 | return x; |
1065 | else { |
1066 | UString s = x->toString(exec); |
1067 | |
1068 | int sourceId; |
1069 | int errLine; |
1070 | UString errMsg; |
1071 | RefPtr<ProgramNode> progNode(parser().parseProgram(UString(), 0, s.data(), s.size(), &sourceId, &errLine, &errMsg)); |
1072 | |
1073 | Debugger *dbg = exec->dynamicInterpreter()->debugger(); |
1074 | if (dbg) { |
1075 | dbg->reportSourceParsed(exec, progNode.get(), sourceId, UString(), s, 0, errLine, errMsg); |
1076 | } |
1077 | |
1078 | // no program node means a syntax occurred |
1079 | if (!progNode) |
1080 | return throwError(exec, SyntaxError, errMsg, errLine, sourceId, NULL); |
1081 | |
1082 | // If the variable object we're working with is an activation, we better |
1083 | // tear it off since stuff inside eval can capture it in a closure |
1084 | if (exec->variableObject()->isActivation()) |
1085 | static_cast<ActivationImp*>(exec->variableObject())->requestTearOff(); |
1086 | |
1087 | // enter a new execution context |
1088 | EvalExecState newExec(exec->dynamicInterpreter(), |
1089 | exec->dynamicInterpreter()->globalObject(), |
1090 | progNode.get(), |
1091 | exec); |
1092 | |
1093 | if (exec->hadException()) |
1094 | newExec.setException(exec->exception()); |
1095 | |
1096 | if (dbg) { |
1097 | bool cont = dbg->enterContext(&newExec, sourceId, 0, 0, List::empty()); |
1098 | if (!cont) { |
1099 | dbg->imp()->abort(); |
1100 | return jsUndefined(); |
1101 | } |
1102 | } |
1103 | |
1104 | // execute the code |
1105 | progNode->processDecls(&newExec); |
1106 | Completion c = progNode->execute(&newExec); |
1107 | |
1108 | dbg = exec->dynamicInterpreter()->debugger(); |
1109 | if (dbg) { |
1110 | bool cont = dbg->exitContext(&newExec, sourceId, 0, 0); |
1111 | if (!cont) { |
1112 | dbg->imp()->abort(); |
1113 | return jsUndefined(); |
1114 | } |
1115 | } |
1116 | |
1117 | // if an exception occurred, propagate it back to the previous execution object |
1118 | if (newExec.hadException()) |
1119 | exec->setException(newExec.exception()); |
1120 | |
1121 | res = jsUndefined(); |
1122 | if (c.complType() == Throw) |
1123 | exec->setException(c.value()); |
1124 | else if (c.isValueCompletion()) |
1125 | res = c.value(); |
1126 | } |
1127 | break; |
1128 | } |
1129 | case ParseInt: |
1130 | res = jsNumber(parseInt(args[0]->toString(exec), args[1]->toInt32(exec))); |
1131 | break; |
1132 | case ParseFloat: |
1133 | res = jsNumber(parseFloat(args[0]->toString(exec))); |
1134 | break; |
1135 | case IsNaN: |
1136 | res = jsBoolean(isNaN(args[0]->toNumber(exec))); |
1137 | break; |
1138 | case IsFinite: { |
1139 | double n = args[0]->toNumber(exec); |
1140 | res = jsBoolean(!isNaN(n) && !isInf(n)); |
1141 | break; |
1142 | } |
1143 | case DecodeURI: |
1144 | res = decode(exec, args, do_not_unescape_when_decoding_URI); |
1145 | break; |
1146 | case DecodeURIComponent: |
1147 | res = decode(exec, args, "" ); |
1148 | break; |
1149 | case EncodeURI: |
1150 | res = encode(exec, args, do_not_escape_when_encoding_URI); |
1151 | break; |
1152 | case EncodeURIComponent: |
1153 | res = encode(exec, args, do_not_escape_when_encoding_URI_component); |
1154 | break; |
1155 | case Escape: |
1156 | { |
1157 | UString r = "" , s, str = args[0]->toString(exec); |
1158 | const UChar* c = str.data(); |
1159 | for (int k = 0; k < str.size(); k++, c++) { |
1160 | int u = c->uc; |
1161 | if (u > 255) { |
1162 | char tmp[7]; |
1163 | sprintf(tmp, "%%u%04X" , u); |
1164 | s = UString(tmp); |
1165 | } else if (u != 0 && strchr(do_not_escape, (char)u)) { |
1166 | s = UString(c, 1); |
1167 | } else { |
1168 | char tmp[4]; |
1169 | sprintf(tmp, "%%%02X" , u); |
1170 | s = UString(tmp); |
1171 | } |
1172 | r += s; |
1173 | } |
1174 | res = jsString(r); |
1175 | break; |
1176 | } |
1177 | case UnEscape: |
1178 | { |
1179 | UString s = "" , str = args[0]->toString(exec); |
1180 | int k = 0, len = str.size(); |
1181 | while (k < len) { |
1182 | const UChar* c = str.data() + k; |
1183 | UChar u; |
1184 | if (*c == UChar('%') && k <= len - 6 && *(c+1) == UChar('u')) { |
1185 | if (Lexer::isHexDigit((c+2)->uc) && Lexer::isHexDigit((c+3)->uc) && |
1186 | Lexer::isHexDigit((c+4)->uc) && Lexer::isHexDigit((c+5)->uc)) { |
1187 | u = Lexer::convertUnicode((c+2)->uc, (c+3)->uc, |
1188 | (c+4)->uc, (c+5)->uc); |
1189 | c = &u; |
1190 | k += 5; |
1191 | } |
1192 | } else if (*c == UChar('%') && k <= len - 3 && |
1193 | Lexer::isHexDigit((c+1)->uc) && Lexer::isHexDigit((c+2)->uc)) { |
1194 | u = UChar(Lexer::convertHex((c+1)->uc, (c+2)->uc)); |
1195 | c = &u; |
1196 | k += 2; |
1197 | } |
1198 | k++; |
1199 | s += UString(c, 1); |
1200 | } |
1201 | res = jsString(s); |
1202 | break; |
1203 | } |
1204 | #ifndef NDEBUG |
1205 | case KJSPrint: |
1206 | puts(args[0]->toString(exec).ascii()); |
1207 | break; |
1208 | #endif |
1209 | } |
1210 | |
1211 | return res; |
1212 | } |
1213 | |
1214 | } // namespace |
1215 | |
1216 | // kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on; |
1217 | |