AbstractEcmaStringOperations.java

package org.mozilla.javascript;

/** Abstract operations for string manipulation as defined by EcmaScript */
public class AbstractEcmaStringOperations {
    /**
     * GetSubstitution(matched, str, position, captures, namedCaptures, replacementTemplate)
     *
     * <p><a
     * href="https://tc39.es/ecma262/multipage/text-processing.html#sec-getsubstitution">22.1.3.19.1
     * GetSubstitution (matched, str, position, captures, namedCaptures, replacementTemplate)</a>
     */
    public static String getSubstitution(
            Context cx,
            Scriptable scope,
            String matched,
            String str,
            int position,
            NativeArray capturesArray,
            Object namedCaptures,
            String replacementTemplate) {
        // See ECMAScript spec 22.1.3.19.1
        int stringLength = str.length();
        if (position > stringLength) Kit.codeBug();
        StringBuilder result = new StringBuilder();
        String templateRemainder = replacementTemplate;
        while (!templateRemainder.isEmpty()) {
            String ref = templateRemainder.substring(0, 1);
            String refReplacement = ref;

            if (templateRemainder.charAt(0) == '$') {
                if (templateRemainder.length() > 1) {
                    char c = templateRemainder.charAt(1);
                    switch (c) {
                        case '$':
                            ref = "$$";
                            refReplacement = "$";
                            break;

                        case '`':
                            ref = "$`";
                            refReplacement = str.substring(0, position);
                            break;

                        case '&':
                            ref = "$&";
                            refReplacement = matched;
                            break;

                        case '\'':
                            {
                                ref = "$'";
                                int matchLength = matched.length();
                                int tailPos = position + matchLength;
                                refReplacement = str.substring(Math.min(tailPos, stringLength));
                                break;
                            }

                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                        case '8':
                        case '9':
                            {
                                int digitCount = 1;
                                if (templateRemainder.length() > 2) {
                                    char c2 = templateRemainder.charAt(2);
                                    if (isAsciiDigit(c2)) {
                                        digitCount = 2;
                                    }
                                }
                                String digits = templateRemainder.substring(1, 1 + digitCount);

                                // No need for ScriptRuntime version; we know the string is one or
                                // two characters and
                                // contains only [0-9]
                                int index = Integer.parseInt(digits);
                                long captureLen = capturesArray.getLength();
                                if (index > captureLen && digitCount == 2) {
                                    digitCount = 1;
                                    digits = digits.substring(0, 1);
                                    index = Integer.parseInt(digits);
                                }
                                ref = templateRemainder.substring(0, 1 + digitCount);
                                if (1 <= index && index <= captureLen) {
                                    Object capture = capturesArray.get(index - 1);
                                    if (capture
                                            == null) { // Undefined or missing are returned as null
                                        refReplacement = "";
                                    } else {
                                        refReplacement = ScriptRuntime.toString(capture);
                                    }
                                } else {
                                    refReplacement = ref;
                                }
                                break;
                            }

                        case '<':
                            {
                                int gtPos = templateRemainder.indexOf('>');
                                if (gtPos == -1 || Undefined.isUndefined(namedCaptures)) {
                                    ref = "$<";
                                    refReplacement = ref;
                                } else {
                                    ref = templateRemainder.substring(0, gtPos + 1);
                                    String groupName = templateRemainder.substring(2, gtPos);
                                    Object capture =
                                            ScriptRuntime.getObjectProp(
                                                    namedCaptures, groupName, cx, scope);
                                    if (Undefined.isUndefined(capture)) {
                                        refReplacement = "";
                                    } else {
                                        refReplacement = ScriptRuntime.toString(capture);
                                    }
                                }
                            }
                            break;
                    }
                }
            }

            int refLength = ref.length();
            templateRemainder = templateRemainder.substring(refLength);
            result.append(refReplacement);
        }
        return result.toString();
    }

    private static boolean isAsciiDigit(char c) {
        switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                return true;

            default:
                return false;
        }
    }
}