String#replace String#match and regexp.lastIndex
I noticed in the ECMA 5 draft that String#replace references String#match's handling of regexp.lastIndex when the regexp is flagged global. According to the draft String#match sets the regexp.lastIndex when the regexp is global.
All of this is identical to ECMA-262 Edition 3.
Currently Firefox, Chrome, Safari, Opera do not set the lastIndex on String#match
Yes they do. Leaving aside whatever happens during the matching process (since it's unobservable to users), String#match called with a "global" RegExp always starts searching from index 0 and the RegExp's lastIndex is always 0 after after the method is complete. (That is, except in IE, which has the bug you mentioned where lastIndex is not always 0 after a match or replace using a "global" regex is complete.)
This should alert 0, demonstrating that the browsers you mentioned do update lastIndex:
var x = /x/g; x.lastIndex = 1; "x123x5".match(x); alert(x.lastIndex);
or String#replace. IE does set the lastIndex but only once, not during each internal iteration of
Repeat, while lastMatch is true
, and for non-global regexp's as well.According to spec I would expect:
var s = '0x2x4x6x8'; var p = /x/g; s.replace(p, function() { alert(p.lastIndex) }); // A: alerts 2, then 4, then 6, then 8 alert(p.lastIndex); // B: alerts 0 because internal exec returned null (which set lastIndex 0) triggering the end of the iteration;
// IE7 // A: alerts 0, then 0, then 0, then 0 // B: alerts 8
// All others // A: alerts 0, then 0, then 0, then 0 // B: alerts 0
Opera 9.64 (and other versions I've previously tested this issue with) alerts A: 2, 4, 6, 8 and B: 0. Like you, this is my interpretation of correct handling according to the spec. Opera tends to follow the spec most closely for regex issues, in general.
This might be a compatibility issue to consider.
Sure, but it's a decade old.
Steve
Steven Levithan Baghdad, Iraq blog.stevenlevithan.com
On Jul 1, 2009, at 10:08 AM, Steve L. wrote:
Opera 9.64 (and other versions I've previously tested this issue
with) alerts A: 2, 4, 6, 8 and B: 0. Like you, this is my
interpretation of correct handling according to the spec. Opera
tends to follow the spec most closely for regex issues, in general.
This seems like a bug for other vendors than Opera to fix. Filed:
bugzilla.mozilla.org/show_bug.cgi?id=501739
I suspect no one will mind the fix, and a few will welcome it. But we
will have to try it out on real developers and users to be sure.
@Steve - You are correct about Opera and about other browsers use of String#match. I must have been using an invalid test at the time. Thanks for the additional info.
@Brendan - Thanks for creating the bug report :D
It may be beneficial to clarify in the ES5 spec that searches should take place and lastIndex should be updated based on a copy of the value of lastIndex made at the start of String#replace's processing. If you follow the dependency chain--where String#replace relies on the definition of String#match, which in turn relies on RegExp#exec--it looks to me like the spec requires this code to become an infinite loop:
var str="0x2x3", a=["x","y","z"], i=0, re=/x/g; alert(str.replace(re, function(){ re.lastIndex = 0; return a[i++]; }));
However, it is not an infinite loop in 5 out of 5 browsers. It should also be unambiguous that this would alert "0x2y3" (as it does in all the big browsers) and not "0y2z3".
While we're discussing String#replace, is it too late to formally spec what $n and $nn in replacement strings should mean when n is greater than the number of capturing groups within the regex? ES 3 & 5-draft say this is implementation-defined. Firefox 3.5, IE 8, Safari 4, Chrome 2, Opera 9.64, and all other browsers I've tested with say that e.g. "test".replace(/(e)/, "$2") returns "t$2st". I rely on this behavior, and I may not be the only one.
Steve
Steven Levithan Baghdad, Iraq blog.stevenlevithan.com
I noticed in the ECMA 5 draft that String#replace references String#match's handling of regexp.lastIndex when the regexp is flagged global. According to the draft String#match sets the regexp.lastIndex when the regexp is global. Currently Firefox, Chrome, Safari, Opera do not set the lastIndex on String#match or String#replace. IE does set the lastIndex but only once, not during each internal iteration of
Repeat, while lastMatch is true
, and for non-global regexp's as well.According to spec I would expect:
var s = '0x2x4x6x8'; var p = /x/g; s.replace(p, function() { alert(p.lastIndex) }); // A: alerts 2, then 4, then 6, then 8 alert(p.lastIndex); // B: alerts 0 because internal exec returned null (which set lastIndex 0) triggering the end of the iteration;
// IE7 // A: alerts 0, then 0, then 0, then 0 // B: alerts 8
// All others // A: alerts 0, then 0, then 0, then 0 // B: alerts 0
This might be a compatibility issue to consider.