Ver Fonte

String.prototype.matchAll implementation (#248)

* Basic String.prototype.matchAll and friends implementation

* add one more test

* add one more test 2

* fix bugs

* fix RegExpStringIteratorPrototype ancestry

* Fix test/built-ins/RegExpStringIteratorPrototype/next/custom-regexpexec-not-callable.js

* Apply suggestions from code review

Co-authored-by: Dmitry Panov <[email protected]>

* Rename classRegExpStringIteratorPrototype to classRegExpStringIterator

* Return *regexpObject instead of *Object for *newRegExp* methods

* Update builtin_string.go

Co-authored-by: Dmitry Panov <[email protected]>

* better way to check the original regex doesn't change

* Remove a check from the go code that we do in the JS one

* fixup! Remove a check from the go code that we do in the JS one

Co-authored-by: Dmitry Panov <[email protected]>
Mihail Stoykov há 4 anos atrás
pai
commit
f5884268f0
9 ficheiros alterados com 402 adições e 23 exclusões
  1. 102 6
      builtin_regexp.go
  2. 36 2
      builtin_string.go
  3. 19 1
      builtin_string_test.go
  4. 2 0
      builtin_symbol.go
  5. 5 4
      object.go
  6. 4 4
      regexp.go
  7. 227 0
      regexp_test.go
  8. 6 5
      runtime.go
  9. 1 1
      vm.go

+ 102 - 6
builtin_regexp.go

@@ -22,13 +22,13 @@ func (r *Runtime) newRegexpObject(proto *Object) *regexpObject {
 	return o
 }
 
-func (r *Runtime) newRegExpp(pattern *regexpPattern, patternStr valueString, proto *Object) *Object {
+func (r *Runtime) newRegExpp(pattern *regexpPattern, patternStr valueString, proto *Object) *regexpObject {
 	o := r.newRegexpObject(proto)
 
 	o.pattern = pattern
 	o.source = patternStr
 
-	return o.val
+	return o
 }
 
 func decodeHex(s string) (int, bool) {
@@ -276,7 +276,7 @@ func compileRegexp(patternStr, flags string) (p *regexpPattern, err error) {
 	return
 }
 
-func (r *Runtime) _newRegExp(patternStr valueString, flags string, proto *Object) *Object {
+func (r *Runtime) _newRegExp(patternStr valueString, flags string, proto *Object) *regexpObject {
 	pattern, err := compileRegexpFromValueString(patternStr, flags)
 	if err != nil {
 		panic(r.newSyntaxError(err.Error(), -1))
@@ -292,10 +292,10 @@ func (r *Runtime) builtin_newRegExp(args []Value, proto *Object) *Object {
 	if len(args) > 1 {
 		flagsVal = args[1]
 	}
-	return r.newRegExp(patternVal, flagsVal, proto)
+	return r.newRegExp(patternVal, flagsVal, proto).val
 }
 
-func (r *Runtime) newRegExp(patternVal, flagsVal Value, proto *Object) *Object {
+func (r *Runtime) newRegExp(patternVal, flagsVal Value, proto *Object) *regexpObject {
 	var pattern valueString
 	var flags string
 	if isRegexp(patternVal) { // this may have side effects so need to call it anyway
@@ -344,7 +344,7 @@ func (r *Runtime) builtin_RegExp(call FunctionCall) Value {
 			}
 		}
 	}
-	return r.newRegExp(pattern, flags, r.global.RegExpPrototype)
+	return r.newRegExp(pattern, flags, r.global.RegExpPrototype).val
 }
 
 func (r *Runtime) regexpproto_compile(call FunctionCall) Value {
@@ -759,6 +759,82 @@ func (r *Runtime) regexpproto_stdSearchGeneric(rxObj *Object, arg valueString) V
 	return r.toObject(result).self.getStr("index", nil)
 }
 
+func (r *Runtime) regexpproto_stdMatcherAll(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	s := call.Argument(0).toString()
+	flags := nilSafe(thisObj.self.getStr("flags", nil)).toString()
+	c := r.speciesConstructorObj(call.This.(*Object), r.global.RegExp)
+	matcher := r.toConstructor(c)([]Value{call.This, flags}, nil)
+	matcher.self.setOwnStr("lastIndex", valueInt(toLength(thisObj.Get("lastIndex"))), true)
+	flagsStr := flags.String()
+	global := strings.Contains(flagsStr, "g")
+	fullUnicode := strings.Contains(flagsStr, "u")
+	return r.createRegExpStringIterator(matcher, s, global, fullUnicode)
+}
+
+func (r *Runtime) createRegExpStringIterator(matcher *Object, s valueString, global, fullUnicode bool) Value {
+	o := &Object{runtime: r}
+
+	ri := &regExpStringIterObject{
+		matcher:     matcher,
+		s:           s,
+		global:      global,
+		fullUnicode: fullUnicode,
+	}
+	ri.class = classRegExpStringIterator
+	ri.val = o
+	ri.extensible = true
+	o.self = ri
+	ri.prototype = r.global.RegExpStringIteratorPrototype
+	ri.init()
+
+	return o
+}
+
+type regExpStringIterObject struct {
+	baseObject
+	matcher                   *Object
+	s                         valueString
+	global, fullUnicode, done bool
+}
+
+// RegExpExec as defined in 21.2.5.2.1
+func regExpExec(r *Object, s valueString) Value {
+	exec := r.Get("exec")
+	if execObject, ok := exec.(*Object); ok {
+		if execFn, ok := execObject.self.assertCallable(); ok {
+			return r.runtime.regExpExec(execFn, r, s)
+		}
+	}
+	if rx, ok := r.self.(*regexpObject); ok {
+		return rx.exec(s)
+	}
+	panic(r.runtime.NewTypeError("no RegExpMatcher internal slot"))
+}
+
+func (ri *regExpStringIterObject) next() (v Value) {
+	if ri.done {
+		return ri.val.runtime.createIterResultObject(_undefined, true)
+	}
+
+	match := regExpExec(ri.matcher, ri.s)
+	if IsNull(match) {
+		ri.done = true
+		return ri.val.runtime.createIterResultObject(_undefined, true)
+	}
+	if !ri.global {
+		ri.done = true
+		return ri.val.runtime.createIterResultObject(match, false)
+	}
+
+	matchStr := nilSafe(ri.val.runtime.toObject(match).self.getIdx(valueInt(0), nil)).toString()
+	if matchStr.length() == 0 {
+		thisIndex := toLength(ri.matcher.self.getStr("lastIndex", nil))
+		ri.matcher.self.setOwnStr("lastIndex", valueInt(advanceStringIndex64(ri.s, thisIndex, ri.fullUnicode)), true)
+	}
+	return ri.val.runtime.createIterResultObject(match, false)
+}
+
 func (r *Runtime) regexpproto_stdSearch(call FunctionCall) Value {
 	thisObj := r.toObject(call.This)
 	s := call.Argument(0).toString()
@@ -1119,10 +1195,29 @@ func (r *Runtime) regexpproto_stdReplacer(call FunctionCall) Value {
 	return stringReplace(s, found, replaceStr, rcall)
 }
 
+func (r *Runtime) regExpStringIteratorProto_next(call FunctionCall) Value {
+	thisObj := r.toObject(call.This)
+	if iter, ok := thisObj.self.(*regExpStringIterObject); ok {
+		return iter.next()
+	}
+	panic(r.NewTypeError("Method RegExp String Iterator.prototype.next called on incompatible receiver %s", thisObj.String()))
+}
+
+func (r *Runtime) createRegExpStringIteratorPrototype(val *Object) objectImpl {
+	o := newBaseObjectObj(val, r.global.IteratorPrototype, classObject)
+
+	o._putProp("next", r.newNativeFunc(r.regExpStringIteratorProto_next, nil, "next", nil, 0), true, false, true)
+	o._putSym(SymToStringTag, valueProp(asciiString(classRegExpStringIterator), false, false, true))
+
+	return o
+}
+
 func (r *Runtime) initRegExp() {
 	o := r.newGuardedObject(r.global.ObjectPrototype, classObject)
 	r.global.RegExpPrototype = o.val
 	r.global.stdRegexpProto = o
+	r.global.RegExpStringIteratorPrototype = r.newLazyObject(r.createRegExpStringIteratorPrototype)
+
 	o._putProp("compile", r.newNativeFunc(r.regexpproto_compile, nil, "compile", nil, 2), true, false, true)
 	o._putProp("exec", r.newNativeFunc(r.regexpproto_exec, nil, "exec", nil, 1), true, false, true)
 	o._putProp("test", r.newNativeFunc(r.regexpproto_test, nil, "test", nil, 1), true, false, true)
@@ -1164,6 +1259,7 @@ func (r *Runtime) initRegExp() {
 	}, false)
 
 	o._putSym(SymMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, nil, "[Symbol.match]", nil, 1), true, false, true))
+	o._putSym(SymMatchAll, valueProp(r.newNativeFunc(r.regexpproto_stdMatcherAll, nil, "[Symbol.matchAll]", nil, 1), true, false, true))
 	o._putSym(SymSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, nil, "[Symbol.search]", nil, 1), true, false, true))
 	o._putSym(SymSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, nil, "[Symbol.split]", nil, 2), true, false, true))
 	o._putSym(SymReplace, valueProp(r.newNativeFunc(r.regexpproto_stdReplacer, nil, "[Symbol.replace]", nil, 2), true, false, true))

+ 36 - 2
builtin_string.go

@@ -358,7 +358,7 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value {
 	}
 
 	if rx == nil {
-		rx = r.newRegExp(regexp, nil, r.global.RegExpPrototype).self.(*regexpObject)
+		rx = r.newRegExp(regexp, nil, r.global.RegExpPrototype)
 	}
 
 	if matcher, ok := r.toObject(rx.getSym(SymMatch, nil)).self.assertCallable(); ok {
@@ -371,6 +371,39 @@ func (r *Runtime) stringproto_match(call FunctionCall) Value {
 	panic(r.NewTypeError("RegExp matcher is not a function"))
 }
 
+func (r *Runtime) stringproto_matchAll(call FunctionCall) Value {
+	r.checkObjectCoercible(call.This)
+	regexp := call.Argument(0)
+	if regexp != _undefined && regexp != _null {
+		if isRegexp(regexp) {
+			if o, ok := regexp.(*Object); ok {
+				flags := o.Get("flags")
+				r.checkObjectCoercible(flags)
+				if !strings.Contains(flags.toString().String(), "g") {
+					panic(r.NewTypeError("RegExp doesn't have global flag set"))
+				}
+			}
+		}
+		if matcher := toMethod(r.getV(regexp, SymMatchAll)); matcher != nil {
+			return matcher(FunctionCall{
+				This:      regexp,
+				Arguments: []Value{call.This},
+			})
+		}
+	}
+
+	rx := r.newRegExp(regexp, asciiString("g"), r.global.RegExpPrototype)
+
+	if matcher, ok := r.toObject(rx.getSym(SymMatchAll, nil)).self.assertCallable(); ok {
+		return matcher(FunctionCall{
+			This:      rx.val,
+			Arguments: []Value{call.This.toString()},
+		})
+	}
+
+	panic(r.NewTypeError("RegExp matcher is not a function"))
+}
+
 func (r *Runtime) stringproto_normalize(call FunctionCall) Value {
 	r.checkObjectCoercible(call.This)
 	s := call.This.toString()
@@ -662,7 +695,7 @@ func (r *Runtime) stringproto_search(call FunctionCall) Value {
 	}
 
 	if rx == nil {
-		rx = r.newRegExp(regexp, nil, r.global.RegExpPrototype).self.(*regexpObject)
+		rx = r.newRegExp(regexp, nil, r.global.RegExpPrototype)
 	}
 
 	if searcher, ok := r.toObject(rx.getSym(SymSearch, nil)).self.assertCallable(); ok {
@@ -924,6 +957,7 @@ func (r *Runtime) initString() {
 	o._putProp("lastIndexOf", r.newNativeFunc(r.stringproto_lastIndexOf, nil, "lastIndexOf", nil, 1), true, false, true)
 	o._putProp("localeCompare", r.newNativeFunc(r.stringproto_localeCompare, nil, "localeCompare", nil, 1), true, false, true)
 	o._putProp("match", r.newNativeFunc(r.stringproto_match, nil, "match", nil, 1), true, false, true)
+	o._putProp("matchAll", r.newNativeFunc(r.stringproto_matchAll, nil, "matchAll", nil, 1), true, false, true)
 	o._putProp("normalize", r.newNativeFunc(r.stringproto_normalize, nil, "normalize", nil, 0), true, false, true)
 	o._putProp("padEnd", r.newNativeFunc(r.stringproto_padEnd, nil, "padEnd", nil, 1), true, false, true)
 	o._putProp("padStart", r.newNativeFunc(r.stringproto_padStart, nil, "padStart", nil, 1), true, false, true)

+ 19 - 1
builtin_string_test.go

@@ -108,6 +108,25 @@ var prefix2 = new Prefix("def");
 	testScript1(SCRIPT, valueTrue, t)
 }
 
+func TestStringMatchAllSym(t *testing.T) {
+	const SCRIPT = `
+function Prefix(p) {
+	this.p = p;
+}
+
+Prefix.prototype[Symbol.matchAll] = function(s) {
+	return s.substring(0, this.p.length) === this.p;
+}
+
+var prefix1 = new Prefix("abc");
+var prefix2 = new Prefix("def");
+
+"abc123".matchAll(prefix1) === true && "abc123".matchAll(prefix2) === false &&
+"def123".matchAll(prefix1) === false && "def123".matchAll(prefix2) === true;
+`
+	testScript1(SCRIPT, valueTrue, t)
+}
+
 func TestGenericSplitter(t *testing.T) {
 	const SCRIPT = `
 function MyRegexp(pattern, flags) {
@@ -223,5 +242,4 @@ func TestValueStringBuilder(t *testing.T) {
 			t.Fatal(res)
 		}
 	})
-
 }

+ 2 - 0
builtin_symbol.go

@@ -7,6 +7,7 @@ var (
 	SymIsConcatSpreadable = newSymbol(asciiString("Symbol.isConcatSpreadable"))
 	SymIterator           = newSymbol(asciiString("Symbol.iterator"))
 	SymMatch              = newSymbol(asciiString("Symbol.match"))
+	SymMatchAll           = newSymbol(asciiString("Symbol.matchAll"))
 	SymReplace            = newSymbol(asciiString("Symbol.replace"))
 	SymSearch             = newSymbol(asciiString("Symbol.search"))
 	SymSpecies            = newSymbol(asciiString("Symbol.species"))
@@ -117,6 +118,7 @@ func (r *Runtime) createSymbol(val *Object) objectImpl {
 		SymIsConcatSpreadable,
 		SymIterator,
 		SymMatch,
+		SymMatchAll,
 		SymReplace,
 		SymSearch,
 		SymSpecies,

+ 5 - 4
object.go

@@ -27,10 +27,11 @@ const (
 	classJSON     = "JSON"
 	classGlobal   = "global"
 
-	classArrayIterator  = "Array Iterator"
-	classMapIterator    = "Map Iterator"
-	classSetIterator    = "Set Iterator"
-	classStringIterator = "String Iterator"
+	classArrayIterator        = "Array Iterator"
+	classMapIterator          = "Map Iterator"
+	classSetIterator          = "Set Iterator"
+	classStringIterator       = "String Iterator"
+	classRegExpStringIterator = "RegExp String Iterator"
 )
 
 var (

+ 4 - 4
regexp.go

@@ -578,12 +578,12 @@ func (r *regexpObject) test(target valueString) bool {
 	return match
 }
 
-func (r *regexpObject) clone() *Object {
+func (r *regexpObject) clone() *regexpObject {
 	r1 := r.val.runtime.newRegexpObject(r.prototype)
 	r1.source = r.source
 	r1.pattern = r.pattern
 
-	return r1.val
+	return r1
 }
 
 func (r *regexpObject) init() {
@@ -612,7 +612,7 @@ func (r *regexpObject) defineOwnPropertySym(name *Symbol, desc PropertyDescripto
 	res := r.baseObject.defineOwnPropertySym(name, desc, throw)
 	if res && r.standard {
 		switch name {
-		case SymMatch, SymSearch, SymSplit, SymReplace:
+		case SymMatch, SymMatchAll, SymSearch, SymSplit, SymReplace:
 			r.standard = false
 		}
 	}
@@ -639,7 +639,7 @@ func (r *regexpObject) setOwnSym(name *Symbol, value Value, throw bool) bool {
 	res := r.baseObject.setOwnSym(name, value, throw)
 	if res && r.standard {
 		switch name {
-		case SymMatch, SymSearch, SymSplit, SymReplace:
+		case SymMatch, SymMatchAll, SymSearch, SymSplit, SymReplace:
 			r.standard = false
 		}
 	}

+ 227 - 0
regexp_test.go

@@ -350,6 +350,19 @@ func TestRegexpUnicodeAdvanceStringIndex(t *testing.T) {
 
 	re.lastIndex = 5;
 	assert.sameValue(re.exec(str), null, "#5");
+
+	var iterator = str.matchAll(re); // regexp is copied by matchAll, but resets lastIndex
+	var matches = [];
+	for (var v of iterator) {matches.push(v);}
+	assert.sameValue(matches.length, 4, "#6");
+	assert.sameValue(matches[0].index, 0, "#7 index");
+	assert.sameValue(matches[0][0], "", "#7 value");
+	assert.sameValue(matches[1].index, 1, "#8 index");
+	assert.sameValue(matches[1][0], "", "#8 value");
+	assert.sameValue(matches[2].index, 3, "#9 index");
+	assert.sameValue(matches[2][0], "", "#9 value");
+	assert.sameValue(matches[3].index, 4, "#10 index");
+	assert.sameValue(matches[3][0], "", "#10 value");
 	`
 	testScript1(TESTLIB+SCRIPT, _undefined, t)
 }
@@ -466,7 +479,106 @@ func TestRegexpConsecutiveMatchCache(t *testing.T) {
 	if regex.self.(*regexpObject).pattern.regexp2Wrapper.cache != nil {
 		t.Fatal("Cache is not nil (unicode)")
 	}
+}
+
+func TestRegexpMatchAll(t *testing.T) {
+	const SCRIPT = `
+	(function test(unicode) {
+		var regex = new RegExp('t(e)(st(\\d?))', unicode?'gu':'g');
+		var string = 'test1test2';
+		var matches = [];
+		for (var match of string.matchAll(regex)) {
+			matches.push(match);
+		}
+		var expectedMatches = [
+		  [
+			'test1',
+			'e',
+			'st1',
+			'1'
+		  ],
+		  [
+			'test2',
+			'e',
+			'st2',
+			'2'
+		  ]
+		];
+		expectedMatches[0].index = 0;
+		expectedMatches[0].input = 'test1test2';
+		expectedMatches[1].index = 5;
+		expectedMatches[1].input = 'test1test2';
+
+		assert(deepEqual(matches, expectedMatches), "#1");
+		assert.sameValue(regex.lastIndex, 0, "#1 lastIndex");
+
+		// try the same regexp with a different string
+		string = ' test5';
+		matches = [];
+		for (var match of string.matchAll(regex)) {
+			matches.push(match);
+		}
+		expectedMatches = [
+			[
+			  'test5',
+			  'e',
+			  'st5',
+			  '5'
+			]
+		];
+		expectedMatches[0].index = 1;
+		expectedMatches[0].input = ' test5';
+		assert(deepEqual(matches, expectedMatches), "#2");
+		assert.sameValue(regex.lastIndex, 0, "#2 lastIndex");
+
+		// continue matching with a different string
+		string = ' test5test6';
+		matches = [];
+		for (var match of string.matchAll(regex)) {
+			matches.push(match);
+		}
+		var expectedMatches = [
+		  [
+			'test5',
+			'e',
+			'st5',
+			'5'
+		  ],
+		  [
+			'test6',
+			'e',
+			'st6',
+			'6'
+		  ]
+		];
+		expectedMatches[0].index = 1;
+		expectedMatches[0].input = ' test5test6';
+		expectedMatches[1].index = 6;
+		expectedMatches[1].input = ' test5test6';
+		assert(deepEqual(matches, expectedMatches), "#3");
+		assert.sameValue(regex.lastIndex, 0, "#3 lastindex");
+	});
+	`
+	vm := New()
+	v, err := vm.RunString(TESTLIBX + SCRIPT)
+	if err != nil {
+		t.Fatal(err)
+	}
+	var f func(bool) (*Object, error)
+	err = vm.ExportTo(v, &f)
+	if err != nil {
+		t.Fatal(err)
+	}
 
+	_, err = f(false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = f(true)
+	if err != nil {
+		t.Fatal(err)
+	}
 }
 
 func TestRegexpOverrideSpecies(t *testing.T) {
@@ -489,6 +601,60 @@ func TestRegexpOverrideSpecies(t *testing.T) {
 	testScript1(SCRIPT, _undefined, t)
 }
 
+func TestRegexpSymbolMatchAllCallsIsRegexp(t *testing.T) {
+	// This is tc39's test/built-ins/RegExp/prototype/Symbol.matchAll/isregexp-this-throws.js
+	const SCRIPT = `
+	var a = new Object();
+	Object.defineProperty(a, Symbol.match, {
+		get: function() {
+			throw "passed";
+		}
+	});
+	try {
+		RegExp.prototype[Symbol.matchAll].call(a, '');
+		throw new Error("Expected error");
+	} catch(e) {
+		if (e !== "passed") {
+			throw e;
+		}
+	}
+	`
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestRegexpMatchAllConstructor(t *testing.T) {
+	// This is tc39's test/built-ins/RegExp/prototype/Symbol.matchAll/species-constuctor.js
+	const SCRIPT = `
+	var callCount = 0;
+	var callArgs;
+	var regexp = /\d/u;
+	var obj = {}
+	Object.defineProperty(obj, Symbol.species, {
+		value: function() {
+		  callCount++;
+		  callArgs = arguments;
+		  return /\w/g;
+		}
+	});
+	regexp.constructor = obj;
+	var str = 'a*b';
+	var iter = regexp[Symbol.matchAll](str);
+
+	assert.sameValue(callCount, 1);
+	assert.sameValue(callArgs.length, 2);
+	assert.sameValue(callArgs[0], regexp);
+	assert.sameValue(callArgs[1], 'u');
+
+	var first = iter.next()
+	assert.sameValue(first.done, false);
+	assert.sameValue(first.value.length, 1);
+	assert.sameValue(first.value[0], "a");
+	var second = iter.next()
+	assert.sameValue(second.done, true);
+	`
+	testScript1(TESTLIB+SCRIPT, _undefined, t)
+}
+
 func TestRegexp2InvalidEscape(t *testing.T) {
 	testScript1(`/(?=)\x0/.test("x0")`, valueTrue, t)
 }
@@ -645,6 +811,67 @@ func BenchmarkRegexpMatchCache(b *testing.B) {
 	}
 }
 
+func BenchmarkRegexpMatchAll(b *testing.B) {
+	const SCRIPT = `
+	(function() {
+		var s = "a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+         a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
+        "
+		var r = /[^\r\n]+/g
+		for (var v of s.matchAll(r)) {}
+	});
+	`
+	vm := New()
+	v, err := vm.RunString(SCRIPT)
+	if err != nil {
+		b.Fatal(err)
+	}
+	if fn, ok := AssertFunction(v); ok {
+		b.ResetTimer()
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			fn(_undefined)
+		}
+	} else {
+		b.Fatal("not a function")
+	}
+}
+
 func BenchmarkRegexpSingleExec(b *testing.B) {
 	vm := New()
 	regexp := vm.Get("RegExp")

+ 6 - 5
runtime.go

@@ -101,11 +101,12 @@ type global struct {
 	MapPrototype         *Object
 	SetPrototype         *Object
 
-	IteratorPrototype       *Object
-	ArrayIteratorPrototype  *Object
-	MapIteratorPrototype    *Object
-	SetIteratorPrototype    *Object
-	StringIteratorPrototype *Object
+	IteratorPrototype             *Object
+	ArrayIteratorPrototype        *Object
+	MapIteratorPrototype          *Object
+	SetIteratorPrototype          *Object
+	StringIteratorPrototype       *Object
+	RegExpStringIteratorPrototype *Object
 
 	ErrorPrototype          *Object
 	TypeErrorPrototype      *Object

+ 1 - 1
vm.go

@@ -1277,7 +1277,7 @@ type newRegexp struct {
 }
 
 func (n *newRegexp) exec(vm *vm) {
-	vm.push(vm.r.newRegExpp(n.pattern.clone(), n.src, vm.r.global.RegExpPrototype))
+	vm.push(vm.r.newRegExpp(n.pattern.clone(), n.src, vm.r.global.RegExpPrototype).val)
 	vm.pc++
 }