Skip to content

Commit 126503b

Browse files
committed
Implement ES2025 Iterator helper methods
Adds Iterator.prototype methods per TC39 iterator helpers proposal: Transformation methods: - map(mapperFn) - Transform each value - filter(predicateFn) - Filter values - flatMap(mapperFn) - Map and flatten Limiting methods: - take(limit) - Take first N values - drop(limit) - Skip first N values Consumption methods: - toArray() - Collect to array - reduce(reducerFn, initialValue) - Reduce values - forEach(callbackFn) - Iterate with callback Testing methods: - some(predicateFn) - Test if any match - every(predicateFn) - Test if all match - find(predicateFn) - Find first matching value Static method: - Iterator.from(obj) - Create iterator from iterable or iterator-like Implementation follows 2025 Rhino architecture patterns: - JSCode architecture for stateful, resumable execution - Context safety: Context always passed as parameter, never captured - BaseFunction-based constructor (not callable/constructable per spec) - Factory pattern for clean helper method organization - Full serialization support for cross-context caching Integration: - Enabled via Context.VERSION_ECMASCRIPT for backwards compatibility - Coexists with legacy Iterator (pre-ES2025) for older language versions - Integrates with upstream's lambda-based NativeIterator (commit ad0dd02) - Preserves StopIteration for generator compatibility Addresses reviewer feedback: - @gbrail (Sept 16): Uses VERSION_ECMASCRIPT instead of feature flag - @rbri (Oct 13): Clean PR with proper test262.properties
1 parent 63dc6b0 commit 126503b

File tree

10 files changed

+1906
-87
lines changed

10 files changed

+1906
-87
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2+
*
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6+
7+
package org.mozilla.javascript;
8+
9+
/**
10+
* Simple Iterator helper implementations for ES2025 Iterator methods. Groups basic transformation
11+
* and filtering operations in one file for maintainability. Uses aardvark179's JSCode architecture
12+
* for Context safety.
13+
*/
14+
public final class ES2025IteratorOperations {
15+
16+
private ES2025IteratorOperations() {
17+
// Utility class - no instances
18+
}
19+
20+
/**
21+
* JSCode implementation for Iterator.prototype.map(). Transforms each value from the source
22+
* iterator using a mapping function.
23+
*/
24+
public static class MapCode extends IteratorHelperCode {
25+
26+
public MapCode(
27+
Scriptable sourceIterator, Function mapperFunction, Scriptable mapperThisArg) {
28+
super(sourceIterator, mapperFunction, mapperThisArg);
29+
}
30+
31+
@Override
32+
protected Object processNext(
33+
Context cx, JSFunction fn, Scriptable scope, int operation, Object value) {
34+
// Get next value from source iterator
35+
Object sourceValue = getNextFromSource(cx, scope);
36+
if (sourceValue == null) {
37+
// Source iterator is exhausted
38+
return createIteratorResult(cx, scope, Undefined.instance, true);
39+
}
40+
41+
// Apply the mapping function with fresh Context
42+
Object mappedValue = callHelperFunction(cx, scope, sourceValue);
43+
currentIndex++;
44+
45+
// Return the mapped value
46+
return createIteratorResult(cx, scope, mappedValue, false);
47+
}
48+
}
49+
50+
/**
51+
* JSCode implementation for Iterator.prototype.filter(). Filters values from the source
52+
* iterator using a predicate function.
53+
*/
54+
public static class FilterCode extends IteratorHelperCode {
55+
56+
public FilterCode(
57+
Scriptable sourceIterator,
58+
Function predicateFunction,
59+
Scriptable predicateThisArg) {
60+
super(sourceIterator, predicateFunction, predicateThisArg);
61+
}
62+
63+
@Override
64+
protected Object processNext(
65+
Context cx, JSFunction fn, Scriptable scope, int operation, Object value) {
66+
while (true) {
67+
// Get next value from source iterator
68+
Object sourceValue = getNextFromSource(cx, scope);
69+
if (sourceValue == null) {
70+
// Source iterator is exhausted
71+
return createIteratorResult(cx, scope, Undefined.instance, true);
72+
}
73+
74+
// Test the predicate with fresh Context
75+
Object testResult = callHelperFunction(cx, scope, sourceValue);
76+
currentIndex++;
77+
78+
// If predicate passes, return this value
79+
if (ScriptRuntime.toBoolean(testResult)) {
80+
return createIteratorResult(cx, scope, sourceValue, false);
81+
}
82+
83+
// Otherwise, continue to next value
84+
}
85+
}
86+
}
87+
88+
/**
89+
* JSCode implementation for Iterator.prototype.take(). Limits the number of values from the
90+
* source iterator.
91+
*/
92+
public static class TakeCode extends IteratorHelperCode {
93+
private final long limit;
94+
private long taken = 0;
95+
96+
// Static factory method for proper limit handling
97+
public static TakeCode create(Scriptable sourceIterator, long limit) {
98+
return new TakeCode(sourceIterator, limit);
99+
}
100+
101+
private TakeCode(Scriptable sourceIterator, long limit) {
102+
super(sourceIterator, null, null);
103+
this.limit = limit;
104+
}
105+
106+
@Override
107+
protected Object processNext(
108+
Context cx, JSFunction fn, Scriptable scope, int operation, Object value) {
109+
// Check if we've reached the limit
110+
if (taken >= limit) {
111+
isExhausted = true;
112+
return createIteratorResult(cx, scope, Undefined.instance, true);
113+
}
114+
115+
// Get next value from source iterator
116+
Object sourceValue = getNextFromSource(cx, scope);
117+
if (sourceValue == null) {
118+
// Source iterator is exhausted
119+
return createIteratorResult(cx, scope, Undefined.instance, true);
120+
}
121+
122+
taken++;
123+
currentIndex++;
124+
return createIteratorResult(cx, scope, sourceValue, false);
125+
}
126+
}
127+
128+
/**
129+
* JSCode implementation for Iterator.prototype.drop(). Skips the first N values from the source
130+
* iterator.
131+
*/
132+
public static class DropCode extends IteratorHelperCode {
133+
private final long limit;
134+
private long dropped = 0;
135+
private boolean skipPhaseComplete = false;
136+
137+
// Static factory method for proper limit handling
138+
public static DropCode create(Scriptable sourceIterator, long limit) {
139+
return new DropCode(sourceIterator, limit);
140+
}
141+
142+
private DropCode(Scriptable sourceIterator, long limit) {
143+
super(sourceIterator, null, null);
144+
this.limit = limit;
145+
}
146+
147+
@Override
148+
protected Object processNext(
149+
Context cx, JSFunction fn, Scriptable scope, int operation, Object value) {
150+
// Skip the first 'limit' values
151+
while (!skipPhaseComplete && dropped < limit) {
152+
Object sourceValue = getNextFromSource(cx, scope);
153+
if (sourceValue == null) {
154+
// Source iterator exhausted during skip phase
155+
return createIteratorResult(cx, scope, Undefined.instance, true);
156+
}
157+
dropped++;
158+
}
159+
skipPhaseComplete = true;
160+
161+
// Now return values normally
162+
Object sourceValue = getNextFromSource(cx, scope);
163+
if (sourceValue == null) {
164+
// Source iterator is exhausted
165+
return createIteratorResult(cx, scope, Undefined.instance, true);
166+
}
167+
168+
currentIndex++;
169+
return createIteratorResult(cx, scope, sourceValue, false);
170+
}
171+
}
172+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2+
*
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6+
7+
package org.mozilla.javascript;
8+
9+
/**
10+
* Wrapper for iterators returned by Iterator.from(). This class wraps any iterator and ensures it
11+
* inherits from Iterator.prototype while delegating all operations to the wrapped iterator.
12+
*/
13+
public final class ES6IteratorAdapter extends ScriptableObject {
14+
private static final long serialVersionUID = 1L;
15+
16+
private Scriptable wrappedIterator;
17+
18+
public ES6IteratorAdapter(Scriptable wrappedIterator, Scriptable scope) {
19+
this.wrappedIterator = wrappedIterator;
20+
21+
// Set parent scope
22+
setParentScope(scope);
23+
24+
// Set prototype to inherit from Iterator.prototype if available
25+
Scriptable iteratorPrototype = NativeIteratorConstructor.getIteratorPrototype(scope);
26+
if (iteratorPrototype != null) {
27+
setPrototype(iteratorPrototype);
28+
}
29+
}
30+
31+
@Override
32+
public String getClassName() {
33+
return "Iterator";
34+
}
35+
36+
@Override
37+
public Object get(String name, Scriptable start) {
38+
// Special handling for 'next' method - create a bound version
39+
if ("next".equals(name) && wrappedIterator != null) {
40+
Object nextMethod = ScriptableObject.getProperty(wrappedIterator, "next");
41+
if (nextMethod instanceof Callable) {
42+
// Return a function that calls next() with the wrapped iterator as 'this'
43+
return new BaseFunction() {
44+
@Override
45+
public Object call(
46+
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
47+
return ((Callable) nextMethod).call(cx, scope, wrappedIterator, args);
48+
}
49+
50+
@Override
51+
public String getFunctionName() {
52+
return "next";
53+
}
54+
};
55+
}
56+
}
57+
58+
// Special handling for 'return' method
59+
if ("return".equals(name) && wrappedIterator != null) {
60+
Object returnMethod = ScriptableObject.getProperty(wrappedIterator, "return");
61+
if (returnMethod instanceof Callable) {
62+
return new BaseFunction() {
63+
@Override
64+
public Object call(
65+
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
66+
return ((Callable) returnMethod).call(cx, scope, wrappedIterator, args);
67+
}
68+
69+
@Override
70+
public String getFunctionName() {
71+
return "return";
72+
}
73+
};
74+
}
75+
}
76+
77+
// Delegate other properties to wrapped iterator
78+
if (wrappedIterator != null) {
79+
Object value = ScriptableObject.getProperty(wrappedIterator, name);
80+
if (value != Scriptable.NOT_FOUND) {
81+
return value;
82+
}
83+
}
84+
// Fall back to our prototype chain
85+
return super.get(name, start);
86+
}
87+
88+
@Override
89+
public Object get(int index, Scriptable start) {
90+
if (wrappedIterator != null) {
91+
Object value = wrappedIterator.get(index, wrappedIterator);
92+
if (value != Scriptable.NOT_FOUND) {
93+
return value;
94+
}
95+
}
96+
return super.get(index, start);
97+
}
98+
99+
@Override
100+
public boolean has(String name, Scriptable start) {
101+
if (wrappedIterator != null) {
102+
// Check if property exists in wrapped iterator or its prototype chain
103+
Object value = ScriptableObject.getProperty(wrappedIterator, name);
104+
if (value != Scriptable.NOT_FOUND) {
105+
return true;
106+
}
107+
}
108+
return super.has(name, start);
109+
}
110+
111+
@Override
112+
public boolean has(int index, Scriptable start) {
113+
if (wrappedIterator != null && wrappedIterator.has(index, wrappedIterator)) {
114+
return true;
115+
}
116+
return super.has(index, start);
117+
}
118+
119+
@Override
120+
public void put(String name, Scriptable start, Object value) {
121+
if (wrappedIterator != null) {
122+
wrappedIterator.put(name, wrappedIterator, value);
123+
} else {
124+
super.put(name, start, value);
125+
}
126+
}
127+
128+
@Override
129+
public void put(int index, Scriptable start, Object value) {
130+
if (wrappedIterator != null) {
131+
wrappedIterator.put(index, wrappedIterator, value);
132+
} else {
133+
super.put(index, start, value);
134+
}
135+
}
136+
137+
@Override
138+
public void delete(String name) {
139+
if (wrappedIterator != null) {
140+
wrappedIterator.delete(name);
141+
} else {
142+
super.delete(name);
143+
}
144+
}
145+
146+
@Override
147+
public void delete(int index) {
148+
if (wrappedIterator != null) {
149+
wrappedIterator.delete(index);
150+
} else {
151+
super.delete(index);
152+
}
153+
}
154+
155+
@Override
156+
public Object[] getIds() {
157+
if (wrappedIterator != null) {
158+
return wrappedIterator.getIds();
159+
}
160+
return super.getIds();
161+
}
162+
}

0 commit comments

Comments
 (0)