mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
457 lines
14 KiB
457 lines
14 KiB
|
|
// FIXME: util
|
|
|
|
function printCodepoints(x) {
|
|
var i;
|
|
var tmp = [];
|
|
for (i = 0; i < x.length; i++) {
|
|
tmp.push(x.charCodeAt(i));
|
|
}
|
|
print(tmp.join(' '));
|
|
}
|
|
|
|
/*===
|
|
basic
|
|
fOo
|
|
fOO
|
|
bar-1123 bar-1234 bar-1345
|
|
xfoo
|
|
xfoo
|
|
xfxoxox
|
|
17185 291 50 4660 65244
|
|
17185 291 50 4660 65244
|
|
17185 291 50 17185 65244
|
|
120 9285 120 13654 120 18023
|
|
17185 4660 291 65244
|
|
17185 4660 291 65244
|
|
17185 4660 17185 291 17185 65244 17185
|
|
===*/
|
|
|
|
print('basic');
|
|
|
|
function basicTest() {
|
|
print('foo'.replace('o', 'O'));
|
|
|
|
print('foo'.replace(/o/g, 'O'));
|
|
|
|
print('foo-123 foo-234 foo-345'.replace(/foo-(\d+)/g,
|
|
function replacer(matchSub, cap, matchOffset, str) {
|
|
return 'bar-' + String(Number(cap) + 1000);
|
|
})
|
|
);
|
|
|
|
// empty search string; matches once at the beginning
|
|
|
|
print('foo'.replace('', 'x'));
|
|
|
|
// empty non-global regexp match; matches once at the beginning
|
|
|
|
print('foo'.replace(/(?:)/, 'x'));
|
|
|
|
// empty global regexp match; matches at the beginning (before first
|
|
// char), between every char, and after the last char. Progress
|
|
// check in the matching process (replace() should perform regexp
|
|
// matching like match() does, according to E5.1) ensures this
|
|
// process terminates and matches only once at each point.
|
|
|
|
print('foo'.replace(/(?:)/g, 'x'));
|
|
|
|
// non-BMP variants of above tests: these are important because
|
|
// the implementation works with both byte and char offsets
|
|
|
|
printCodepoints('\u1234\u0123\u0032\u1234\ufedc'.replace('\u1234', '\u4321'));
|
|
printCodepoints('\u1234\u0123\u0032\u1234\ufedc'.replace(/\u1234/, '\u4321'));
|
|
printCodepoints('\u1234\u0123\u0032\u1234\ufedc'.replace(/\u1234/g, '\u4321'));
|
|
printCodepoints('\u1234\u2345\u1234\u3456\u1234\u4567'.replace(/\u1234(.)/g,
|
|
function replacer(matchSub, cap, matchOffset, str) {
|
|
return 'x' + String.fromCharCode(cap.charCodeAt(0) + 0x100);
|
|
})
|
|
);
|
|
printCodepoints('\u1234\u0123\ufedc'.replace('', '\u4321'));
|
|
printCodepoints('\u1234\u0123\ufedc'.replace(/(?:)/, '\u4321'));
|
|
printCodepoints('\u1234\u0123\ufedc'.replace(/(?:)/g, '\u4321'));
|
|
}
|
|
|
|
try {
|
|
basicTest();
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|
|
/*===
|
|
dollar replacements
|
|
string xooxxr
|
|
string $oo$$r
|
|
string ffoobbaar
|
|
string f[f][fo]bar
|
|
string f[obar][bar]bar
|
|
string fo$0ar
|
|
string foobobar
|
|
string foobar
|
|
string foob2ar
|
|
string foob1ar
|
|
string $0abcdefghijklmnopqrstuvwxyzb7b8b9c0c1c2c3c4c5c6c7c8c9d0d1d2d3d4d5d6d7d8d9e0e1e2e3e4e5e6e7e8e9f0f1f2f3f4f5f6f7f8f9g0g1g2g3g4g5g6g7g8g9h0h1h2h3h4h5h6h7h8h9i0i1i2i3i4i5i6i7i8i9j0
|
|
string $1-$11,$1-$22
|
|
===*/
|
|
|
|
/* Test all dollar replacement syntax variants */
|
|
|
|
print('dollar replacements');
|
|
|
|
function dollarTest() {
|
|
function test(str, searchValue, replaceValue) {
|
|
var t;
|
|
|
|
try {
|
|
t = str.replace(searchValue, replaceValue);
|
|
print(typeof t, t);
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
}
|
|
|
|
test('foobar', /([a-f])/g, 'x');
|
|
|
|
test('foobar', /([a-f])/g, '$$');
|
|
|
|
test('foobar', /([a-f])/g, '$&$&');
|
|
|
|
test('foobar', /o/g, '[$`]');
|
|
|
|
test('foobar', /o/g, '[$\']');
|
|
|
|
test('foobar', /(ob)/g, '$0'); // $0 is not valid, so is interpreted literally as "$0"
|
|
test('foobar', /(ob)/g, '$1$1');
|
|
test('foobar', /(ob)/g, '$01'); // interpreted as "replacement 1"
|
|
test('foobar', /(ob)/g, '$012'); // interpreted as "replacement 01 + '2'"
|
|
|
|
// Behavior here is implemented dependent because 11 > NCapturingParens.
|
|
// Rhino and V8 both seem to fall back to interpreting the expression
|
|
// as '$1' followed by a verbatim '1', so we test for that.
|
|
test('foobar', /(ob)/g, '$11'); // interpreted as "replacement 11" not "replacement 1 + '1'"
|
|
|
|
// $27 -> $29 are larger than NCapturingParens but $2 is not, so V8/Rhino fall back
|
|
// to interpreting them as $2 followed by a literal digit. Same goes for all matches
|
|
// up to $99. $100 will be interpreted as $10 followed by a literal '0'.
|
|
test('abcdefghijklmnopqrstuvwxyz',
|
|
/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)/g,
|
|
'$0$1$2$3$4$5$6$7$8$9$10$11$12$13$14$15$16$17$18$19$20$21$22$23$24$25$26$27$28$29' +
|
|
'$30$31$32$33$34$35$36$37$38$39$40$41$42$43$44$45$46$47$48$49' +
|
|
'$50$51$52$53$54$55$56$57$58$59$60$61$62$63$64$65$66$67$68$69' +
|
|
'$70$71$72$73$74$75$76$77$78$79$80$81$82$83$84$85$86$87$88$89' +
|
|
'$90$91$92$93$94$95$96$97$98$99$100');
|
|
|
|
// Specific test form E5.1 Section 15.5.4.11:
|
|
//
|
|
// test("$1,$2", /(\$(\d))/g, "$$1-$1$2");
|
|
//
|
|
// This test cannot be used as is, because "\$" is not a valid RegExp
|
|
// identity escape: identity escape characters cannot come from
|
|
// IdentifierPart; since IdentifierPart includes '$' it must cause a
|
|
// SyntaxError. On the other hand, a plain '$' has a special meaning,
|
|
// so a dollar sign must be encoded as a hex/unicode escape!
|
|
|
|
test("$1,$2", /(\u0024(\d))/g, "$$1-$1$2");
|
|
}
|
|
|
|
try {
|
|
dollarTest();
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|
|
/*===
|
|
function replacer
|
|
repl1 o 1 foobar
|
|
string f[repl-1]obar
|
|
repl1 o 1 foobar
|
|
repl1 o 2 foobar
|
|
string f[repl-1][repl-2]bar
|
|
repl2 oba o b a 2 foobar
|
|
string fo[repl-2]r
|
|
repl3 yada-1 ya d 1 0 yada-1 yadda-2 yaddda-3
|
|
repl3 yadda-2 ya dd 2 7 yada-1 yadda-2 yaddda-3
|
|
repl3 yaddda-3 ya ddd 3 15 yada-1 yadda-2 yaddda-3
|
|
string Yadda-2 Yadda-4 Yadda-6
|
|
repl_args 3 o <> 1 foobar
|
|
repl_args 3 o <> 2 foobar
|
|
string f[repl-args-1][repl-args-2]bar
|
|
repl_args 6 oba <o:b:a> 2 foobar
|
|
string fo[repl-args-2]r
|
|
repl4 o o 1 foObar
|
|
toString() repl4 retval
|
|
repl4 O O 2 foObar
|
|
toString() repl4 retval
|
|
string f[repl-1 cap=o][repl-2 cap=O]bar
|
|
repl2 foo foo foo undefined 0 foobar
|
|
repl2 bar bar undefined bar 3 foobar
|
|
string [repl-0][repl-3]
|
|
===*/
|
|
|
|
/* Function replacer, regexp captures. */
|
|
|
|
print('function replacer');
|
|
|
|
function functionReplacerTest() {
|
|
// zero captures
|
|
function repl1(matchSub, matchOffset, str) {
|
|
print('repl1', matchSub, matchOffset, str);
|
|
return '[repl-' + matchOffset + ']';
|
|
}
|
|
|
|
// three captures
|
|
function repl2(matchSub, cap1, cap2, cap3, matchOffset, str) {
|
|
print('repl2', matchSub, cap1, cap2, cap3, matchOffset, str);
|
|
return '[repl-' + matchOffset + ']';
|
|
}
|
|
|
|
// three captures
|
|
function repl3(matchSub, cap1, cap2, cap3, matchOffset, str) {
|
|
print('repl3', matchSub, cap1, cap2, cap3, matchOffset, str);
|
|
return 'Yadda-' + Number(cap3) * 2;
|
|
}
|
|
|
|
// one capture, returns an object which needs to be string coerced
|
|
function repl4(matchSub, cap, matchOffset, str) {
|
|
print('repl4', matchSub, cap, matchOffset, str);
|
|
return {
|
|
toString: function() { print('toString() repl4 retval');
|
|
return '[repl-' + matchOffset + ' cap=' + cap + ']'; },
|
|
valueOf: function() { print('valueOf() repl4 retval');
|
|
return 'not called'; }
|
|
};
|
|
}
|
|
|
|
// allows any captures, use arguments object to figure out args
|
|
function repl_args() {
|
|
var matchSub, matchOffset, str;
|
|
var caps = [];
|
|
var i, n;
|
|
|
|
matchSub = arguments[0];
|
|
matchOffset = arguments[arguments.length - 2];
|
|
str = arguments[arguments.length - 1];
|
|
for (i = 1; i < arguments.length - 2; i++) {
|
|
caps.push(arguments[i]);
|
|
}
|
|
|
|
print('repl_args', arguments.length, matchSub, '<' + caps.join(':') + '>', matchOffset, str);
|
|
|
|
return '[repl-args-' + matchOffset + ']';
|
|
}
|
|
|
|
function test(str, searchValue, replaceValue) {
|
|
var t;
|
|
|
|
try {
|
|
t = str.replace(searchValue, replaceValue);
|
|
print(typeof t, t);
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
}
|
|
|
|
test('foobar', 'o', repl1);
|
|
test('foobar', /o/g, repl1);
|
|
|
|
test('foobar', /(o)(b)(.)/g, repl2);
|
|
|
|
test('yada-1 yadda-2 yaddda-3', /(ya)(d+)a-(\d+)/g, repl3);
|
|
|
|
test('foobar', /o/g, repl_args);
|
|
test('foobar', /(o)(b)(.)/g, repl_args);
|
|
|
|
// replace function value coercion
|
|
test('foObar', /(o)/gi, repl4);
|
|
|
|
// even captures with no match are given to the replacer, as undefined
|
|
test('foobar', /((foo)|(bar))/g, repl2);
|
|
}
|
|
|
|
try {
|
|
functionReplacerTest();
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|
|
/*===
|
|
regexp details
|
|
non-global, match
|
|
re: source foo global false ignoreCase false multiline false lastIndex number 0
|
|
re: source foo global false ignoreCase false multiline false lastIndex string 123
|
|
replace func
|
|
re: source foo global false ignoreCase false multiline false lastIndex string 123
|
|
bar[repl]barfoo
|
|
re: source foo global false ignoreCase false multiline false lastIndex string 123
|
|
non-global, no match
|
|
re: source goo global false ignoreCase false multiline false lastIndex number 0
|
|
re: source goo global false ignoreCase false multiline false lastIndex string 123
|
|
barfoobarfoo
|
|
re: source goo global false ignoreCase false multiline false lastIndex number 0
|
|
global, match
|
|
re: source foo global true ignoreCase false multiline false lastIndex number 0
|
|
re: source foo global true ignoreCase false multiline false lastIndex string 123
|
|
replace func
|
|
re: source foo global true ignoreCase false multiline false lastIndex number 6
|
|
replace func
|
|
re: source foo global true ignoreCase false multiline false lastIndex number 12
|
|
replace func
|
|
re: source foo global true ignoreCase false multiline false lastIndex number 18
|
|
bar[repl]bar[repl]bar[repl]bar
|
|
re: source foo global true ignoreCase false multiline false lastIndex number 0
|
|
global, no match
|
|
re: source goo global true ignoreCase false multiline false lastIndex number 0
|
|
re: source goo global true ignoreCase false multiline false lastIndex string 123
|
|
barfoobarfoobarfoobar
|
|
re: source goo global true ignoreCase false multiline false lastIndex number 0
|
|
===*/
|
|
|
|
/* RegExp details, especially how the RegExp instance properties are
|
|
* changed during non-global or global matching.
|
|
*
|
|
* lastIndex handling:
|
|
*
|
|
* - If RegExp is not global, E5.1 Section 15.5.4.11 is quite vague
|
|
* on how lastIndex should behave. If the normal RegExp exec()
|
|
* algorithm is used, lastIndex is not updated on a match, but is
|
|
* written to zero if no match is found (E5.1 Section 15.10.6.2,
|
|
* step 9.a.i).
|
|
*
|
|
* - If RegExp is global, E5.1 Section 15.5.4.11 indicates that matching
|
|
* should proceed similar to what match() does. Match calls RegExp
|
|
* exec() which, for global RegExps, updates lastIndex on every call.
|
|
* On successful matches, lastIndex points to the next character after
|
|
* the matching part. On unsuccessful matches, lastIndex is written
|
|
* to zero (E5.1 Section 15.10.6.2). When matching starts, lastIndex
|
|
* is written to zero and any previous value is ignored (and is thus
|
|
* never coerced).
|
|
*
|
|
* The current implementation will call the replacer while doing the
|
|
* RegExp matching. This matters for the global case because the replacer
|
|
* will see the intermediate lastIndex values and will even be able to
|
|
* interfere with the matching process. E5.1 Section 15.5.4.11 can also
|
|
* be read to imply that all matches are first made into some internal
|
|
* value before making any replacer calls. In this interpretation the
|
|
* replacer would see a lastIndex of zero (since the final non-match will
|
|
* set lastIndex to zero) and although it could tamper with lastIndex, it
|
|
* couldn't affect the replacement process any more.
|
|
*
|
|
* The current test case tests for the former interpretation, i.e. that
|
|
* replacer is called during matching. Note that at least V8 deviates
|
|
* from this interpretation.
|
|
*/
|
|
|
|
print('regexp details');
|
|
|
|
function regexpDetailsTest() {
|
|
var re;
|
|
|
|
function dumpRe(r) {
|
|
print('re:', 'source', r.source, 'global', r.global, 'ignoreCase', r.ignoreCase,
|
|
'multiline', r.multiline, 'lastIndex', typeof r.lastIndex, r.lastIndex);
|
|
}
|
|
|
|
// non-global case with a match: lastIndex is not touched
|
|
print('non-global, match');
|
|
re = /foo/;
|
|
dumpRe(re);
|
|
re.lastIndex = '123';
|
|
dumpRe(re);
|
|
print('barfoobarfoo'.replace(re, function() {
|
|
print('replace func');
|
|
dumpRe(re);
|
|
return '[repl]';
|
|
}));
|
|
dumpRe(re);
|
|
|
|
// non-global case with no match: lastIndex is written to zero
|
|
print('non-global, no match');
|
|
re = /goo/;
|
|
dumpRe(re);
|
|
re.lastIndex = '123';
|
|
dumpRe(re);
|
|
print('barfoobarfoo'.replace(re, function() {
|
|
print('replace func');
|
|
dumpRe(re);
|
|
return '[repl]';
|
|
}));
|
|
dumpRe(re);
|
|
|
|
// global case with a matches: lastIndex is updated before every
|
|
// replacer call, and written to zero at the end
|
|
print('global, match');
|
|
re = /foo/g;
|
|
dumpRe(re);
|
|
re.lastIndex = '123';
|
|
dumpRe(re);
|
|
print('barfoobarfoobarfoobar'.replace(re, function() {
|
|
print('replace func');
|
|
dumpRe(re);
|
|
return '[repl]';
|
|
}));
|
|
dumpRe(re);
|
|
|
|
// global case with no match: lastIndex is written to zero
|
|
print('global, no match');
|
|
re = /goo/g;
|
|
dumpRe(re);
|
|
re.lastIndex = '123';
|
|
dumpRe(re);
|
|
print('barfoobarfoobarfoobar'.replace(re, function() {
|
|
print('replace func');
|
|
dumpRe(re);
|
|
return '[repl]';
|
|
}));
|
|
dumpRe(re);
|
|
}
|
|
|
|
try {
|
|
regexpDetailsTest();
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|
|
/*===
|
|
coercion
|
|
TypeError
|
|
TypeError
|
|
string tRUE
|
|
string faLSE
|
|
string 1xy
|
|
string qUux
|
|
string 1:3
|
|
string [object OBJECT]
|
|
===*/
|
|
|
|
/* Coercion of 'this' and arguments. */
|
|
|
|
print('coercion');
|
|
|
|
function coercionTest() {
|
|
function test(thisValue, searchValue, replaceValue) {
|
|
var t;
|
|
|
|
try {
|
|
t = String.prototype.replace.call(thisValue, searchValue, replaceValue);
|
|
print(typeof t, t);
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
}
|
|
|
|
test(undefined, 'foo', 'bar');
|
|
test(null, 'foo', 'bar');
|
|
test(true, 'rue', 'RUE');
|
|
test(false, 'lse', 'LSE');
|
|
test(123, '23', 'xy');
|
|
test('quux', 'u', 'U');
|
|
test([1,2], ',2', ':3');
|
|
test({ foo:1, bar:2 }, 'Object', 'OBJECT');
|
|
}
|
|
|
|
try {
|
|
coercionTest();
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|
|
|