NativeRegExpStringIterator.java

/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript.regexp;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ES6Iterator;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;

// See ECMAScript spec 22.2.9.1
public final class NativeRegExpStringIterator extends ES6Iterator {
    private static final long serialVersionUID = 1L;
    private static final String ITERATOR_TAG = "RegExpStringIterator";

    private Scriptable regexp;
    private String string;
    private boolean global;
    private boolean fullUnicode;
    private boolean nextDone;
    private Object next = null;

    public static void init(ScriptableObject scope, boolean sealed) {
        ES6Iterator.init(scope, sealed, new NativeRegExpStringIterator(), ITERATOR_TAG);
    }

    /** Only for constructing the prototype object. */
    private NativeRegExpStringIterator() {
        super();
    }

    public NativeRegExpStringIterator(
            Scriptable scope,
            Scriptable regexp,
            String string,
            boolean global,
            boolean fullUnicode) {
        super(scope, ITERATOR_TAG);

        this.regexp = regexp;
        this.string = string;
        this.global = global;
        this.fullUnicode = fullUnicode;
        this.nextDone = false;
    }

    @Override
    public String getClassName() {
        return "RegExp String Iterator";
    }

    @Override
    protected boolean isDone(Context cx, Scriptable scope) {
        // The base class calls _first_ isDone and _then_ nextValue, so we'll just compute the next
        // value here and return it form "nextValue".
        // Also, for non-global regexp, we need to return the first match and then "done" on the
        // next iteration.

        if (nextDone) {
            return true;
        }

        next = NativeRegExp.regExpExec(regexp, string, cx, scope);
        if (next == null) {
            // Done! Point ii of the spec
            next = Undefined.instance;
            nextDone = true;
            return true;
        } else if (!global) {
            // Return false at this iteration, but true at the next. Point iii of the spec
            nextDone = true;
            return false;
        }

        // Increment index if matched empty string, as per the spec, point v.
        String matchStr = ScriptRuntime.toString(ScriptRuntime.getObjectIndex(next, 0, cx, scope));
        if (matchStr.isEmpty()) {
            long thisIndex =
                    ScriptRuntime.toLength(ScriptRuntime.getObjectProp(regexp, "lastIndex", cx));
            long nextIndex = ScriptRuntime.advanceStringIndex(string, thisIndex, fullUnicode);
            ScriptRuntime.setObjectProp(regexp, "lastIndex", nextIndex, cx);
        }

        return false;
    }

    @Override
    protected Object nextValue(Context cx, Scriptable scope) {
        return next;
    }

    @Override
    protected String getTag() {
        return ITERATOR_TAG;
    }
}