1 // string.cpp: ActionScript "String" class, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19
20 #include "String_as.h"
21
22 #include <boost/algorithm/string/case_conv.hpp>
23 #include <algorithm>
24 #include <locale>
25 #include <stdexcept>
26
27 #include "SWFCtype.h"
28 #include "fn_call.h"
29 #include "Global_as.h"
30 #include "as_object.h"
31 #include "NativeFunction.h"
32 #include "log.h"
33 #include "as_value.h"
34 #include "GnashException.h"
35 #include "movie_definition.h"
36 #include "VM.h"
37 #include "namedStrings.h"
38 #include "utf8.h"
39 #include "GnashNumeric.h"
40 #include "Global_as.h"
41
42 namespace gnash {
43
44 // Forward declarations
45 namespace {
46
47 as_value string_concat(const fn_call& fn);
48 as_value string_slice(const fn_call& fn);
49 as_value string_split(const fn_call& fn);
50 as_value string_lastIndexOf(const fn_call& fn);
51 as_value string_substr(const fn_call& fn);
52 as_value string_substring(const fn_call& fn);
53 as_value string_indexOf(const fn_call& fn);
54 as_value string_fromCharCode(const fn_call& fn);
55 as_value string_charCodeAt(const fn_call& fn);
56 as_value string_charAt(const fn_call& fn);
57 as_value string_toUpperCase(const fn_call& fn);
58 as_value string_toLowerCase(const fn_call& fn);
59 as_value string_toString(const fn_call& fn);
60 as_value string_valueOf(const fn_call& fn);
61 as_value string_oldToLower(const fn_call& fn);
62 as_value string_oldToUpper(const fn_call& fn);
63 as_value string_ctor(const fn_call& fn);
64
65 size_t validIndex(const std::wstring& subject, int index);
66 void attachStringInterface(as_object& o);
67
68 inline bool checkArgs(const fn_call& fn, size_t min, size_t max,
69 const std::string& function);
70
71 inline int getStringVersioned(const fn_call& fn, const as_value& arg,
72 std::string& str);
73
74 }
75
String_as(std::string s)76 String_as::String_as(std::string s)
77 :
78 _string(std::move(s))
79 {
80 }
81
82 void
registerStringNative(as_object & global)83 registerStringNative(as_object& global)
84 {
85 VM& vm = getVM(global);
86 vm.registerNative(string_ctor, 251, 0);
87 vm.registerNative(string_valueOf, 251, 1);
88 vm.registerNative(string_toString, 251, 2);
89 vm.registerNative(string_oldToUpper, 102, 0);
90 vm.registerNative(string_toUpperCase, 251, 3);
91 vm.registerNative(string_oldToLower, 102, 1);
92 vm.registerNative(string_toLowerCase, 251, 4);
93 vm.registerNative(string_charAt, 251, 5);
94 vm.registerNative(string_charCodeAt, 251, 6);
95 vm.registerNative(string_concat, 251, 7);
96 vm.registerNative(string_indexOf, 251, 8);
97 vm.registerNative(string_lastIndexOf, 251, 9);
98 vm.registerNative(string_slice, 251, 10);
99 vm.registerNative(string_substring, 251, 11);
100 vm.registerNative(string_split, 251, 12);
101 vm.registerNative(string_substr, 251, 13);
102 vm.registerNative(string_fromCharCode, 251, 14);
103 }
104
105 // extern (used by Global.cpp)
106 void
string_class_init(as_object & where,const ObjectURI & uri)107 string_class_init(as_object& where, const ObjectURI& uri)
108 {
109 // This is going to be the global String "class"/"function"
110
111 VM& vm = getVM(where);
112 Global_as& gl = getGlobal(where);
113
114 as_object* proto = createObject(gl);
115 as_object* cl = vm.getNative(251, 0);
116 cl->init_member(NSV::PROP_PROTOTYPE, proto);
117 proto->init_member(NSV::PROP_CONSTRUCTOR, cl);
118
119 attachStringInterface(*proto);
120
121 cl->init_member("fromCharCode", vm.getNative(251, 14));
122
123 const int flags = PropFlags::dontEnum;
124 where.init_member(uri, cl, flags);
125 }
126
127
128 /// String class interface
129 namespace {
130
131 void
attachStringInterface(as_object & o)132 attachStringInterface(as_object& o)
133 {
134 VM& vm = getVM(o);
135
136 o.init_member("valueOf", vm.getNative(251, 1));
137 o.init_member("toString", vm.getNative(251, 2));
138 o.init_member("toUpperCase", vm.getNative(251, 3));
139 o.init_member("toLowerCase", vm.getNative(251, 4));
140 o.init_member("charAt", vm.getNative(251, 5));
141 o.init_member("charCodeAt", vm.getNative(251, 6));
142 o.init_member("concat", vm.getNative(251, 7));
143 o.init_member("indexOf", vm.getNative(251, 8));
144 o.init_member("lastIndexOf", vm.getNative(251, 9));
145 o.init_member("slice", vm.getNative(251, 10));
146 o.init_member("substring", vm.getNative(251, 11));
147 o.init_member("split", vm.getNative(251, 12));
148 o.init_member("substr", vm.getNative(251, 13));
149 }
150
151 // all the arguments will be converted to string and concatenated.
152 as_value
string_concat(const fn_call & fn)153 string_concat(const fn_call& fn)
154 {
155 as_value val(fn.this_ptr);
156
157 std::string str;
158 const int version = getStringVersioned(fn, val, str);
159
160 for (size_t i = 0; i < fn.nargs; i++) {
161 str += fn.arg(i).to_string(version);
162 }
163
164 return as_value(str);
165 }
166
167
168 // 1st param: start_index, 2nd param: end_index
169 as_value
string_slice(const fn_call & fn)170 string_slice(const fn_call& fn)
171 {
172 as_value val(fn.this_ptr);
173
174 std::string str;
175 const int version = getStringVersioned(fn, val, str);
176
177 std::wstring wstr = utf8::decodeCanonicalString(str, version);
178
179 if (!checkArgs(fn, 1, 2, "String.slice()")) return as_value();
180
181 size_t start = validIndex(wstr, toInt(fn.arg(0), getVM(fn)));
182
183 size_t end = wstr.length();
184
185 if (fn.nargs >= 2)
186 {
187 end = validIndex(wstr, toInt(fn.arg(1), getVM(fn)));
188
189 }
190
191 if (end < start) // move out of if ?
192 {
193 return as_value("");
194 }
195
196 size_t retlen = end - start;
197
198 //log_debug("start: %d, end: %d, retlen: %d", start, end, retlen);
199
200 return as_value(utf8::encodeCanonicalString(
201 wstr.substr(start, retlen), version));
202 }
203
204 // String.split(delimiter[, limit])
205 // For SWF5, the following conditions mean that an array with a single
206 // element containing the entire string is returned:
207 // 1. No arguments are passed.
208 // 2. The delimiter is empty.
209 // 3. The delimiter has more than one DisplayObject or is undefined and limit is not 0.
210 // 4. The delimiter is not present in the string and the limit is not 0.
211 //
212 // Accordingly, an empty array is returned only when the limit is less
213 // than 0 and a non-empty delimiter is passed.
214 //
215 // For SWF6:
216 // Full string returned in 1-element array:
217 // 1. If no arguments are passed.
218 // 2. If delimiter undefined.
219 // 3: empty string, non-empty delimiter.
220 //
221 // Empty array returned:
222 // 4. string and delimiter are empty but defined.
223 // 5. non-empty string, non-empty delimiter; 0 or less elements required.
224 as_value
string_split(const fn_call & fn)225 string_split(const fn_call& fn)
226 {
227 as_value val(fn.this_ptr);
228
229 std::string str;
230 const int version = getStringVersioned(fn, val, str);
231
232 std::wstring wstr = utf8::decodeCanonicalString(str, version);
233
234 Global_as& gl = getGlobal(fn);
235 as_object* array = gl.createArray();
236
237 if (fn.nargs == 0)
238 {
239 // Condition 1:
240 callMethod(array, NSV::PROP_PUSH, str);
241 return as_value(array);
242 }
243
244 const std::wstring& delim = utf8::decodeCanonicalString(
245 fn.arg(0).to_string(), version);
246 const size_t delimiterSize = delim.size();
247
248 if ((version < 6 && delimiterSize == 0) ||
249 (version >= 6 && fn.arg(0).is_undefined()))
250 {
251 // Condition 2:
252 callMethod(array, NSV::PROP_PUSH, str);
253 return as_value(array);
254 }
255
256 size_t max = wstr.size() + 1;
257
258 if (version < 6)
259 {
260 // SWF5
261 if (fn.nargs > 1 && !fn.arg(1).is_undefined())
262 {
263 int limit = toInt(fn.arg(1), getVM(fn));
264 if (limit < 1)
265 {
266 // Return empty array.
267 return as_value(array);
268 }
269 max = clamp<size_t>(limit, 0, max);
270 }
271
272 if (delimiterSize > 1 || fn.arg(0).is_undefined() || wstr.empty())
273 {
274 // Condition 3 (plus a shortcut if the string itself
275 // is empty).
276 callMethod(array, NSV::PROP_PUSH, str);
277 return as_value(array);
278 }
279 }
280 else
281 {
282 // SWF6+
283 if (wstr.empty())
284 {
285 // If the string itself is empty, SWF6 returns a 0-sized
286 // array only if the delimiter is also empty. Otherwise
287 // it returns an array with 1 empty element.
288 if (delimiterSize) callMethod(array, NSV::PROP_PUSH, str);
289 return as_value(array);
290 }
291
292 // If we reach this point, the string is not empty and
293 // the delimiter is defined.
294 if (fn.nargs > 1 && !fn.arg(1).is_undefined())
295 {
296 int limit = toInt(fn.arg(1), getVM(fn));
297 if (limit < 1) {
298 // Return empty array if
299 return as_value(array);
300 }
301 max = clamp<size_t>(limit, 0, max);
302 }
303
304 // If the delimiter is empty, put each character in an
305 // array element.
306 if (delim.empty()) {
307 for (size_t i = 0, e = std::min<size_t>(wstr.size(), max);
308 i < e; ++i) {
309 callMethod(array, NSV::PROP_PUSH,
310 utf8::encodeCanonicalString(wstr.substr(i, 1), version));
311 }
312 return as_value(array);
313 }
314
315 }
316
317 size_t pos = 0, prevpos = 0;
318 size_t num = 0;
319
320 while (num < max) {
321 pos = wstr.find(delim, pos);
322
323 callMethod(array, NSV::PROP_PUSH, utf8::encodeCanonicalString(
324 wstr.substr(prevpos, pos - prevpos), version));
325
326 if (pos == std::wstring::npos) break;
327 num++;
328 prevpos = pos + delimiterSize;
329 pos++;
330 }
331
332 return as_value(array);
333 }
334
335 /// String.lastIndexOf[string[, pos]]
336 //
337 /// Performs a reverse search for the complete search string, optionally
338 /// starting from pos. Returns -1 if not found.
339 as_value
string_lastIndexOf(const fn_call & fn)340 string_lastIndexOf(const fn_call& fn)
341 {
342 as_value val(fn.this_ptr);
343
344 std::string str;
345 const int version = getStringVersioned(fn, val, str);
346 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
347
348 if (!checkArgs(fn, 1, 2, "String.lastIndexOf()")) return as_value(-1);
349
350 const std::wstring& toFind = utf8::decodeCanonicalString(
351 fn.arg(0).to_string(version), version);
352
353 int start = str.size();
354
355 if (fn.nargs >= 2) {
356 start = toInt(fn.arg(1), getVM(fn));
357 }
358
359 if (start < 0) {
360 return as_value(-1);
361 }
362
363 size_t found = wstr.rfind(toFind, start);
364
365 if (found == std::string::npos) {
366 return as_value(-1);
367 }
368
369 return as_value(found);
370 }
371
372 // String.substr(start[, length]).
373 // If the second value is absent or undefined, the remainder of the string from
374 // <start> is returned.
375 // If start is more than string length or length is 0, empty string is returned.
376 // If length is negative, the substring is taken from the *end* of the string.
377 as_value
string_substr(const fn_call & fn)378 string_substr(const fn_call& fn)
379 {
380 as_value val(fn.this_ptr);
381
382 std::string str;
383 const int version = getStringVersioned(fn, val, str);
384
385 std::wstring wstr = utf8::decodeCanonicalString(str, version);
386
387 if (!checkArgs(fn, 1, 2, "String.substr()")) return as_value(str);
388
389 int start = validIndex(wstr, toInt(fn.arg(0), getVM(fn)));
390
391 int num = wstr.length();
392
393 if (fn.nargs >= 2 && !fn.arg(1).is_undefined())
394 {
395 num = toInt(fn.arg(1), getVM(fn));
396 if ( num < 0 )
397 {
398 if ( -num <= start ) num = 0;
399 else
400 {
401 num = wstr.length() + num;
402 if ( num < 0 ) return as_value("");
403 }
404 }
405 }
406
407 return as_value(utf8::encodeCanonicalString(wstr.substr(start, num), version));
408 }
409
410 // string.substring(start[, end])
411 // If *either* value is less than 0, 0 is used.
412 // The values are *then* swapped if end is before start.
413 // Valid values for the start position are up to string
414 // length - 1.
415 as_value
string_substring(const fn_call & fn)416 string_substring(const fn_call& fn)
417 {
418 as_value val(fn.this_ptr);
419
420 std::string str;
421 const int version = getStringVersioned(fn, val, str);
422
423 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
424
425 if (!checkArgs(fn, 1, 2, "String.substring()")) return as_value(str);
426
427 const as_value& s = fn.arg(0);
428
429 int start = toInt(s, getVM(fn));
430 int end = wstr.size();
431
432 if (s.is_undefined() || start < 0) {
433 start = 0;
434 }
435
436 if (static_cast<unsigned>(start) >= wstr.size()) {
437 return as_value("");
438 }
439
440 if (fn.nargs >= 2 && !fn.arg(1).is_undefined()) {
441 int num = toInt(fn.arg(1), getVM(fn));
442
443 if (num < 0) {
444 num = 0;
445 }
446
447 end = num;
448
449 if (end < start) {
450 IF_VERBOSE_ASCODING_ERRORS(
451 log_aserror(_("string.slice() called with end < start"));
452 )
453 std::swap (end, start);
454 }
455 }
456
457 if (static_cast<unsigned>(end) > wstr.size()) {
458 end = wstr.size();
459 }
460
461 end -= start;
462 //log_debug("Start: %d, End: %d", start, end);
463
464 return as_value(utf8::encodeCanonicalString(wstr.substr(start, end), version));
465 }
466
467 as_value
string_indexOf(const fn_call & fn)468 string_indexOf(const fn_call& fn)
469 {
470 as_value val(fn.this_ptr);
471
472 /// Do not return before this, because the toString method should always
473 /// be called. (TODO: test).
474 std::string str;
475 const int version = getStringVersioned(fn, val, str);
476
477 if (!checkArgs(fn, 1, 2, "String.indexOf")) return as_value(-1);
478
479 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
480
481 const as_value& tfarg = fn.arg(0); // to find arg
482 const std::wstring& toFind =
483 utf8::decodeCanonicalString(tfarg.to_string(version),
484 version);
485
486 size_t start = 0;
487
488 if (fn.nargs >= 2)
489 {
490 const as_value& saval = fn.arg(1); // start arg val
491 int start_arg = toInt(saval, getVM(fn));
492 if (start_arg > 0) start = (size_t) start_arg;
493 else {
494 IF_VERBOSE_ASCODING_ERRORS(
495 if (start_arg < 0) {
496 log_aserror(_("String.indexOf(%s, %s): second argument casts "
497 "to invalid offset (%d)"), tfarg, saval, start_arg);
498 }
499 );
500 }
501 }
502
503 const size_t pos = wstr.find(toFind, start);
504
505 if (pos == std::wstring::npos) {
506 return as_value(-1);
507 }
508
509 return as_value(pos);
510 }
511
512 // String.fromCharCode(code1[, code2[, code3[, code4[, ...]]]])
513 // Makes a string out of any number of char codes.
514 // The string is always UTF8, so SWF5 mangles it.
515 as_value
string_fromCharCode(const fn_call & fn)516 string_fromCharCode(const fn_call& fn)
517 {
518
519 const int version = getSWFVersion(fn);
520
521 if (version == 5)
522 {
523 std::string str;
524 for (unsigned int i = 0; i < fn.nargs; i++)
525 {
526 // Maximum 65535, as with all DisplayObject codes.
527 const std::uint16_t c =
528 static_cast<std::uint16_t>(toInt(fn.arg(i), getVM(fn)));
529
530 // If more than 255, push 'overflow' byte.
531 if (c > 255) {
532 str.push_back(static_cast<unsigned char>(c >> 8));
533 }
534
535 // 0 terminates the string, but mustn't be pushed or it
536 // will break concatenation.
537 if (static_cast<unsigned char>(c) == 0) break;
538 str.push_back(static_cast<unsigned char>(c));
539 }
540 return as_value(str);
541 }
542
543 std::wstring wstr;
544
545 for (unsigned int i = 0; i < fn.nargs; i++)
546 {
547 const std::uint16_t c =
548 static_cast<std::uint16_t>(toInt(fn.arg(i), getVM(fn)));
549 if (c == 0) break;
550 wstr.push_back(c);
551 }
552
553 return as_value(utf8::encodeCanonicalString(wstr, version));
554
555 }
556
557 as_value
string_charCodeAt(const fn_call & fn)558 string_charCodeAt(const fn_call& fn)
559 {
560 as_value val(fn.this_ptr);
561
562 std::string str;
563 const int version = getStringVersioned(fn, val, str);
564
565 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
566
567 if (fn.nargs == 0) {
568 IF_VERBOSE_ASCODING_ERRORS(
569 log_aserror(_("string.charCodeAt needs one argument"));
570 )
571 as_value rv;
572 setNaN(rv);
573 return rv; // Same as for out-of-range arg
574 }
575
576 IF_VERBOSE_ASCODING_ERRORS(
577 if (fn.nargs > 1) {
578 log_aserror(_("string.charCodeAt has more than one argument"));
579 }
580 )
581
582 size_t index = static_cast<size_t>(toInt(fn.arg(0), getVM(fn)));
583
584 if (index >= wstr.length()) {
585 as_value rv;
586 setNaN(rv);
587 return rv;
588 }
589
590 return as_value(wstr.at(index));
591 }
592
593 as_value
string_charAt(const fn_call & fn)594 string_charAt(const fn_call& fn)
595 {
596 as_value val(fn.this_ptr);
597
598 std::string str;
599 const int version = getStringVersioned(fn, val, str);
600
601 if (!checkArgs(fn, 1, 1, "String.charAt()")) return as_value("");
602
603 // to_int() makes this safe from overflows.
604 const size_t index = static_cast<size_t>(toInt(fn.arg(0), getVM(fn)));
605
606 size_t currentIndex = 0;
607
608 std::string::const_iterator it = str.begin(), e = str.end();
609
610 while (std::uint32_t code = utf8::decodeNextUnicodeCharacter(it, e))
611 {
612 if (currentIndex == index)
613 {
614 if (version == 5)
615 {
616 return as_value(utf8::encodeLatin1Character(code));
617 }
618 return as_value(utf8::encodeUnicodeCharacter(code));
619 }
620 ++currentIndex;
621 }
622
623 // We've reached the end without finding the index
624 return as_value("");
625 }
626
627 as_value
string_toUpperCase(const fn_call & fn)628 string_toUpperCase(const fn_call& fn)
629 {
630 as_value val(fn.this_ptr);
631
632 std::string str;
633 const int version = getStringVersioned(fn, val, str);
634
635 std::wstring wstr = utf8::decodeCanonicalString(str, version);
636
637 #if !defined(__HAIKU__) && !defined(__amigaos4__) && !defined(__ANDROID__)
638 static const std::locale swfLocale((std::locale()), new SWFCtype());
639 boost::to_upper(wstr, swfLocale);
640 #else
641 size_t l = wstr.size();
642 for (size_t i = 0; i < l; ++i) {
643 if (wstr[i] >= 'a' && wstr[i] <= 'z') {
644 wstr[i] += 'A' - 'a';
645 }
646 }
647 #endif
648
649 return as_value(utf8::encodeCanonicalString(wstr, version));
650
651 }
652
653 as_value
string_toLowerCase(const fn_call & fn)654 string_toLowerCase(const fn_call& fn)
655 {
656 as_value val(fn.this_ptr);
657
658 std::string str;
659 const int version = getStringVersioned(fn, val, str);
660
661 std::wstring wstr = utf8::decodeCanonicalString(str, version);
662
663 #if !defined(__HAIKU__) && !defined(__amigaos4__) && !defined(__ANDROID__)
664 static const std::locale swfLocale((std::locale()), new SWFCtype());
665 boost::to_lower(wstr, swfLocale);
666 #else
667 size_t l = wstr.size();
668 for (size_t i = 0; i < l; ++i) {
669 if (wstr[i] >= 'A' && wstr[i] <= 'Z') {
670 wstr[i] -= 'A' - 'a';
671 }
672 }
673 #endif
674
675 return as_value(utf8::encodeCanonicalString(wstr, version));
676 }
677
678 as_value
string_oldToLower(const fn_call & fn)679 string_oldToLower(const fn_call& fn)
680 {
681 as_value val(fn.this_ptr);
682
683 // This should use the C locale; extended DisplayObjects are
684 // left alone. FIXME: SWF5 should garble the output.
685 std::string str = boost::to_lower_copy(val.to_string());
686 return as_value(str);
687 }
688
689
690 as_value
string_oldToUpper(const fn_call & fn)691 string_oldToUpper(const fn_call& fn)
692 {
693 as_value val(fn.this_ptr);
694
695 // This should use the C locale; extended DisplayObjects are
696 // left alone. FIXME: SWF5 should garble the output.
697 std::string str = boost::to_upper_copy(val.to_string());
698 return as_value(str);
699 }
700
701 /// This returns as_value.toString() value of an object. For Strings this is
702 /// a string, for objects "[type Object]" or "[type Function]", for Booleans
703 /// "true" or "false", etc.
704 as_value
string_valueOf(const fn_call & fn)705 string_valueOf(const fn_call& fn)
706 {
707 const int version = getSWFVersion(fn);
708 return as_value(fn.this_ptr).to_string(version);
709 }
710
711 as_value
string_toString(const fn_call & fn)712 string_toString(const fn_call& fn)
713 {
714 String_as* str = ensure<ThisIsNative<String_as> >(fn);
715 return as_value(str->value());
716 }
717
718
719 as_value
string_ctor(const fn_call & fn)720 string_ctor(const fn_call& fn)
721 {
722 const int version = getSWFVersion(fn);
723
724 std::string str;
725
726 if (fn.nargs) {
727 str = fn.arg(0).to_string(version);
728 }
729
730 if (!fn.isInstantiation())
731 {
732 return as_value(str);
733 }
734
735 as_object* obj = fn.this_ptr;
736
737 obj->setRelay(new String_as(str));
738 std::wstring wstr = utf8::decodeCanonicalString(str, getSWFVersion(fn));
739 obj->init_member(NSV::PROP_LENGTH, wstr.size(), as_object::DefaultFlags);
740
741 return as_value();
742 }
743
744 inline int
getStringVersioned(const fn_call & fn,const as_value & val,std::string & str)745 getStringVersioned(const fn_call& fn, const as_value& val, std::string& str)
746 {
747
748 /// version to use is the one of the SWF containing caller code.
749 /// If callerDef is null, this calls is spontaneous (system-event?)
750 /// in which case we should research on which version should drive
751 /// behaviour.
752 /// NOTE: it is unlikely that a system event triggers string_split so
753 /// in most cases a null callerDef means the caller forgot to
754 /// set the field (ie: a programmatic error)
755 if (!fn.callerDef) {
756 log_error(_("No fn_call::callerDef in string function call"));
757 }
758
759 const int version = fn.callerDef ? fn.callerDef->get_version() :
760 getSWFVersion(fn);
761
762 str = val.to_string(version);
763
764 return version;
765
766
767 }
768
769 /// Check the number of arguments, returning false if there
770 /// aren't enough, or true if there are either enough or too many.
771 /// Logs an error if the number isn't between min and max.
checkArgs(const fn_call & fn,size_t min,size_t max,const std::string & function)772 inline bool checkArgs(const fn_call& fn, size_t min, size_t max,
773 const std::string& function)
774 {
775
776 if (fn.nargs < min) {
777 IF_VERBOSE_ASCODING_ERRORS(
778 std::ostringstream os;
779 fn.dump_args(os);
780 log_aserror(_("%1%(%2%) needs %3% argument(s)"),
781 function, os.str(), min);
782 )
783 return false;
784 }
785
786 IF_VERBOSE_ASCODING_ERRORS(
787 if (fn.nargs > max)
788 {
789 std::ostringstream os;
790 fn.dump_args(os);
791 log_aserror(_("%1%(%2%) has more than %3% argument(s)"),
792 function, os.str(), max);
793 }
794 );
795 return true;
796 }
797
798 size_t
validIndex(const std::wstring & subject,int index)799 validIndex(const std::wstring& subject, int index)
800 {
801
802 if (index < 0) {
803 index = subject.size() + index;
804 }
805
806 index = clamp<int>(index, 0, subject.size());
807
808 return index;
809 }
810
811 } // anonymous namespace
812 } // namespace gnash
813