All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pkl.thirdparty.truffle.api.strings.TruffleStringBuilder Maven / Gradle / Ivy

Go to download

Fat Jar containing pkl-cli, pkl-codegen-java, pkl-codegen-kotlin, pkl-config-java, pkl-core, pkl-doc, and their shaded third-party dependencies.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.pkl.thirdparty.truffle.api.strings;

import static org.pkl.thirdparty.truffle.api.strings.AbstractTruffleString.boundsCheckRegionI;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.is7Bit;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.is7BitCompatible;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isBroken;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isBrokenMultiByte;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isFixedWidth;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isUTF16;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isUTF16Or32;

import java.util.Arrays;

import org.pkl.thirdparty.truffle.api.CompilerDirectives.TruffleBoundary;
import org.pkl.thirdparty.truffle.api.dsl.Bind;
import org.pkl.thirdparty.truffle.api.dsl.Cached;
import org.pkl.thirdparty.truffle.api.dsl.Cached.Shared;
import org.pkl.thirdparty.truffle.api.dsl.ImportStatic;
import org.pkl.thirdparty.truffle.api.dsl.NeverDefault;
import org.pkl.thirdparty.truffle.api.dsl.Specialization;
import org.pkl.thirdparty.truffle.api.nodes.Node;
import org.pkl.thirdparty.truffle.api.profiles.InlinedBranchProfile;
import org.pkl.thirdparty.truffle.api.profiles.InlinedConditionProfile;
import org.pkl.thirdparty.truffle.api.strings.TruffleString.CompactionLevel;
import org.pkl.thirdparty.truffle.api.strings.TruffleString.Encoding;

/**
 * The {@link TruffleString} equivalent to {@link java.lang.StringBuilder}. This builder eagerly
 * fills up a byte array with all strings passed to its {@code Append}-nodes. For lazy string
 * concatenation, use {@link TruffleString.ConcatNode} instead.
 *
 * @since 22.1
 */
public final class TruffleStringBuilder {

    private final Encoding encoding;
    private byte[] buf;
    int stride;
    private int length;
    private int codePointLength;
    private int codeRange;

    private TruffleStringBuilder(Encoding encoding) {
        this(encoding, 16);
    }

    private TruffleStringBuilder(Encoding encoding, int initialSize) {
        this.encoding = encoding;
        buf = new byte[initialSize];
        codeRange = is7BitCompatible(encoding) ? TSCodeRange.get7Bit() : TSCodeRange.getUnknownCodeRangeForEncoding(encoding.id);
    }

    private int bufferLength() {
        return buf.length >> stride;
    }

    /**
     * Returns true if the this string builder is empty.
     *
     * @since 22.1
     */
    public boolean isEmpty() {
        return length == 0;
    }

    /**
     * Returns this string builder's byte length.
     *
     * @since 22.1
     */
    public int byteLength() {
        return length << encoding.naturalStride;
    }

    Encoding getEncoding() {
        return encoding;
    }

    int getStride() {
        return stride;
    }

    int getCodeRange() {
        return codeRange;
    }

    private void updateCodeRange(int newCodeRange) {
        codeRange = TSCodeRange.commonCodeRange(codeRange, newCodeRange);
    }

    private void appendLength(int addLength) {
        appendLength(addLength, addLength);
    }

    private void appendLength(int addLength, int addCodePointLength) {
        length += addLength;
        codePointLength += addCodePointLength;
    }

    /**
     * Create a new string builder with the given encoding.
     *
     * @since 22.1
     */
    public static TruffleStringBuilder create(Encoding encoding) {
        return new TruffleStringBuilder(encoding);
    }

    /**
     * Create a new string builder with the given encoding, and pre-allocate the given number of
     * bytes.
     *
     * @since 22.1
     */
    public static TruffleStringBuilder create(Encoding encoding, int initialCapacity) {
        return new TruffleStringBuilder(encoding, initialCapacity);
    }

    /**
     * Node to append a single byte to a string builder.
     *
     * @since 22.1
     */
    public abstract static class AppendByteNode extends AbstractPublicNode {

        AppendByteNode() {
        }

        /**
         * Append a single byte to the string builder. Does not support UTF-16 and UTF-32; use
         * {@link AppendCharUTF16Node} and {@link AppendCodePointNode} instead.
         *
         * @since 22.1
         */
        public abstract void execute(TruffleStringBuilder sb, byte value);

        @Specialization
        final void append(TruffleStringBuilder sb, byte value,
                        @Cached InlinedConditionProfile bufferGrowProfile,
                        @Cached InlinedBranchProfile errorProfile) {
            if (isUTF16Or32(sb.encoding)) {
                throw InternalErrors.unsupportedOperation("appendByte is not supported for UTF-16 and UTF-32, use appendChar and appendInt instead");
            }
            sb.ensureCapacityS0(this, 1, bufferGrowProfile, errorProfile);
            sb.buf[sb.length++] = value;
            if (value < 0) {
                sb.codeRange = TSCodeRange.asciiLatinBytesNonAsciiCodeRange(sb.encoding);
            }
            sb.codePointLength++;
        }

        /**
         * Create a new {@link AppendByteNode}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static AppendByteNode create() {
            return TruffleStringBuilderFactory.AppendByteNodeGen.create();
        }

        /**
         * Get the uncached version of {@link AppendByteNode}.
         *
         * @since 22.1
         */
        public static AppendByteNode getUncached() {
            return TruffleStringBuilderFactory.AppendByteNodeGen.getUncached();
        }
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendByteNode}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendByteUncached(byte value) {
        AppendByteNode.getUncached().execute(this, value);
    }

    /**
     * Node to append a single char to a string builder. For UTF-16 only.
     *
     * @since 22.1
     */
    public abstract static class AppendCharUTF16Node extends AbstractPublicNode {

        AppendCharUTF16Node() {
        }

        /**
         * Append a single char to the string builder. For UTF-16 only.
         *
         * @since 22.1
         */
        public abstract void execute(TruffleStringBuilder sb, char value);

        @Specialization(guards = {"cachedCurCompaction.getStride() == sb.stride", "cachedNewCompaction.getStride() == utf16Stride(sb, value)"}, limit = TStringOpsNodes.LIMIT_STRIDE)
        static void doCached(TruffleStringBuilder sb, char value,
                        @Bind("this") Node node,
                        @Cached("fromStride(sb.stride)") CompactionLevel cachedCurCompaction,
                        @Cached("fromStride(utf16Stride(sb, value))") CompactionLevel cachedNewCompaction,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            doAppend(node, sb, value, cachedCurCompaction.getStride(), cachedNewCompaction.getStride(), bufferGrowProfile, errorProfile);
        }

        @Specialization(replaces = "doCached")
        void doUncached(TruffleStringBuilder sb, char value,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            doAppend(this, sb, value, sb.stride, utf16Stride(sb, value), bufferGrowProfile, errorProfile);
        }

        private static void doAppend(Node node, TruffleStringBuilder sb, char value, int cachedCurStride, int cachedNewStride, InlinedConditionProfile bufferGrowProfile,
                        InlinedBranchProfile errorProfile) {
            if (!isUTF16(sb.encoding)) {
                throw InternalErrors.unsupportedOperation("appendChar is meant for UTF-16 strings only");
            }
            sb.ensureCapacity(node, 1, cachedCurStride, cachedNewStride, bufferGrowProfile, errorProfile);
            sb.updateCodeRange(utf16CodeRange(value));
            TStringOps.writeToByteArray(sb.buf, cachedNewStride, sb.length, value);
            sb.appendLength(1);
        }

        /**
         * Create a new {@link AppendCharUTF16Node}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static AppendCharUTF16Node create() {
            return TruffleStringBuilderFactory.AppendCharUTF16NodeGen.create();
        }

        /**
         * Get the uncached version of {@link AppendCharUTF16Node}.
         *
         * @since 22.1
         */
        public static AppendCharUTF16Node getUncached() {
            return TruffleStringBuilderFactory.AppendCharUTF16NodeGen.getUncached();
        }
    }

    static int utf16Stride(TruffleStringBuilder sb, int value) {
        return value <= 0xff ? sb.stride : 1;
    }

    static int utf16CodeRange(int value) {
        if (value <= 0x7f) {
            return TSCodeRange.get7Bit();
        }
        if (value <= 0xff) {
            return TSCodeRange.get8Bit();
        }
        if (Encodings.isUTF16Surrogate(value)) {
            return TSCodeRange.markImprecise(TSCodeRange.getBrokenMultiByte());
        }
        if (value <= 0xffff) {
            return TSCodeRange.get16Bit();
        }
        return TSCodeRange.getValidMultiByte();
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendCharUTF16Node}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendCharUTF16Uncached(char value) {
        AppendCharUTF16Node.getUncached().execute(this, value);
    }

    static int utf32Stride(TruffleStringBuilder sb, int value) {
        return Math.max(sb.stride, value <= 0xff ? 0 : value <= 0xffff && !Encodings.isUTF16Surrogate(value) ? 1 : 2);
    }

    static int utf32CodeRange(int value) {
        if (value <= 0x7f) {
            return TSCodeRange.get7Bit();
        }
        if (value <= 0xff) {
            return TSCodeRange.get8Bit();
        }
        if (Encodings.isUTF16Surrogate(value)) {
            return TSCodeRange.getBrokenFixedWidth();
        }
        if (value <= 0xffff) {
            return TSCodeRange.get16Bit();
        }
        return TSCodeRange.getValidFixedWidth();
    }

    /**
     * Node to append a codepoint to a string builder.
     *
     * @since 22.1
     */
    public abstract static class AppendCodePointNode extends AbstractPublicNode {

        AppendCodePointNode() {
        }

        /**
         * Append a codepoint to the string builder.
         *
         * @since 22.1
         */
        public final void execute(TruffleStringBuilder sb, int codepoint) {
            execute(sb, codepoint, 1);
        }

        /**
         * Append a codepoint to the string builder, {@code repeat} times.
         *
         * @since 22.2
         */
        public final void execute(TruffleStringBuilder sb, int codepoint, int repeat) {
            execute(sb, codepoint, repeat, false);
        }

        /**
         * Append a codepoint to the string builder, {@code repeat} times.
         *
         * If {@code allowUTF16Surrogates} is {@code true}, {@link Character#isSurrogate(char)
         * UTF-16 surrogate values} passed as {@code codepoint} will not cause an
         * {@link IllegalArgumentException}, but instead be encoded on a best-effort basis. This
         * option is only supported on {@link Encoding#UTF_16} and {@link Encoding#UTF_32}.
         *
         * @since 22.2
         */
        public abstract void execute(TruffleStringBuilder sb, int codepoint, int repeat, boolean allowUTF16Surrogates);

        @Specialization
        final void append(TruffleStringBuilder sb, int c, int repeat, boolean allowUTF16Surrogates,
                        @Cached AppendCodePointIntlNode appendCodePointIntlNode) {
            assert !allowUTF16Surrogates || isUTF16Or32(sb.encoding) : "allowUTF16Surrogates is only supported on UTF-16 and UTF-32";
            if (c < 0 || c > 0x10ffff) {
                throw InternalErrors.invalidCodePoint(c);
            }
            if (repeat < 1) {
                throw InternalErrors.illegalArgument("number of repetitions must be at least 1");
            }
            appendCodePointIntlNode.execute(this, sb, c, sb.encoding, repeat, allowUTF16Surrogates);
            sb.codePointLength += repeat;
        }

        /**
         * Create a new {@link AppendCodePointNode}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static AppendCodePointNode create() {
            return TruffleStringBuilderFactory.AppendCodePointNodeGen.create();
        }

        /**
         * Get the uncached version of {@link AppendCodePointNode}.
         *
         * @since 22.1
         */
        public static AppendCodePointNode getUncached() {
            return TruffleStringBuilderFactory.AppendCodePointNodeGen.getUncached();
        }
    }

    @ImportStatic(TruffleStringBuilder.class)
    abstract static class AppendCodePointIntlNode extends AbstractInternalNode {

        abstract void execute(Node node, TruffleStringBuilder sb, int c, Encoding encoding, int n, boolean allowUTF16Surrogates);

        @Specialization(guards = "isAsciiBytesOrLatin1(enc)")
        static void bytes(Node node, TruffleStringBuilder sb, int c, @SuppressWarnings("unused") Encoding enc, int n, @SuppressWarnings("unused") boolean allowUTF16Surrogates,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            if (c > 0xff) {
                throw InternalErrors.invalidCodePoint(c);
            }
            sb.ensureCapacityS0(node, n, bufferGrowProfile, errorProfile);
            if (c > 0x7f) {
                sb.updateCodeRange(TSCodeRange.asciiLatinBytesNonAsciiCodeRange(sb.encoding));
            }
            Arrays.fill(sb.buf, sb.length, sb.length + n, (byte) c);
            sb.length += n;
        }

        @Specialization(guards = "isUTF8(enc)")
        static void utf8(Node node, TruffleStringBuilder sb, int c, @SuppressWarnings("unused") Encoding enc, int n, @SuppressWarnings("unused") boolean allowUTF16Surrogates,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            if (Encodings.isUTF16Surrogate(c)) {
                throw InternalErrors.invalidCodePoint(c);
            }
            int length = Encodings.utf8EncodedSize(c);
            sb.ensureCapacityS0(node, length * n, bufferGrowProfile, errorProfile);
            for (int i = 0; i < n; i++) {
                Encodings.utf8Encode(c, sb.buf, sb.length, length);
                sb.length += length;
            }
            if (c > 0x7f) {
                sb.updateCodeRange(TSCodeRange.getValidMultiByte());
            }
        }

        @Specialization(guards = {"isUTF16(enc)", "cachedCurStride == sb.stride", "cachedNewStride == utf16Stride(sb, c)"}, limit = TStringOpsNodes.LIMIT_STRIDE)
        static void utf16Cached(Node node, TruffleStringBuilder sb, int c, @SuppressWarnings("unused") Encoding enc, int n, boolean allowUTF16Surrogates,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile,
                        @Cached("sb.stride") int cachedCurStride,
                        @Cached("utf16Stride(sb, c)") int cachedNewStride,
                        @Shared("bmp") @Cached InlinedConditionProfile bmpProfile) {
            doUTF16(node, sb, c, n, allowUTF16Surrogates, bufferGrowProfile, errorProfile, cachedCurStride, cachedNewStride, bmpProfile);
        }

        @Specialization(guards = "isUTF16(enc)", replaces = "utf16Cached")
        static void utf16Uncached(Node node, TruffleStringBuilder sb, int c, @SuppressWarnings("unused") Encoding enc, int n, boolean allowUTF16Surrogates,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile,
                        @Shared("bmp") @Cached InlinedConditionProfile bmpProfile) {
            doUTF16(node, sb, c, n, allowUTF16Surrogates, bufferGrowProfile, errorProfile, sb.stride, utf16Stride(sb, c), bmpProfile);
        }

        private static void doUTF16(Node node, TruffleStringBuilder sb, int c, int n, boolean allowUTF16Surrogates,
                        InlinedConditionProfile bufferGrowProfile, InlinedBranchProfile errorProfile, int cachedCurStride, int cachedNewStride, InlinedConditionProfile bmpProfile) {
            if (!allowUTF16Surrogates && Encodings.isUTF16Surrogate(c)) {
                throw InternalErrors.invalidCodePoint(c);
            }
            sb.ensureCapacity(node, c <= 0xffff ? n : n << 1, cachedCurStride, cachedNewStride, bufferGrowProfile, errorProfile);
            sb.updateCodeRange(utf16CodeRange(c));
            if (bmpProfile.profile(node, c <= 0xffff)) {
                arrayFill(sb, c, n, cachedNewStride);
            } else {
                for (int i = 0; i < n; i++) {
                    Encodings.utf16EncodeSurrogatePair(c, sb.buf, sb.length);
                    sb.length += 2;
                }
            }
        }

        @Specialization(guards = {"isUTF32(enc)", "cachedCurStride == sb.stride", "cachedNewStride == utf32Stride(sb, c)"}, limit = TStringOpsNodes.LIMIT_STRIDE)
        static void utf32Cached(Node node, TruffleStringBuilder sb, int c, @SuppressWarnings("unused") Encoding enc, int n, boolean allowUTF16Surrogates,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile,
                        @Cached(value = "sb.stride") int cachedCurStride,
                        @Cached(value = "utf32Stride(sb, c)") int cachedNewStride) {
            doUTF32(node, sb, c, n, allowUTF16Surrogates, bufferGrowProfile, errorProfile, cachedCurStride, cachedNewStride);
        }

        @Specialization(guards = "isUTF32(enc)", replaces = "utf32Cached")
        static void utf32Uncached(Node node, TruffleStringBuilder sb, int c, @SuppressWarnings("unused") Encoding enc, int n, boolean allowUTF16Surrogates,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            doUTF32(node, sb, c, n, allowUTF16Surrogates, bufferGrowProfile, errorProfile, sb.stride, utf32Stride(sb, c));
        }

        static void doUTF32(Node node, TruffleStringBuilder sb, int c, int n, boolean allowUTF16Surrogates, InlinedConditionProfile bufferGrowProfile, InlinedBranchProfile errorProfile,
                        int cachedCurStride,
                        int cachedNewStride) {
            if (!allowUTF16Surrogates && Encodings.isUTF16Surrogate(c)) {
                throw InternalErrors.invalidCodePoint(c);
            }
            sb.ensureCapacity(node, n, cachedCurStride, cachedNewStride, bufferGrowProfile, errorProfile);
            sb.updateCodeRange(utf32CodeRange(c));
            arrayFill(sb, c, n, cachedNewStride);
        }

        @Specialization(guards = "isUnsupportedEncoding(enc)")
        static void unsupported(Node node, TruffleStringBuilder sb, int c, Encoding enc, int n, @SuppressWarnings("unused") boolean allowUTF16Surrogates,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            JCodings.Encoding jCodingsEnc = JCodings.getInstance().get(enc);
            int length = JCodings.getInstance().getCodePointLength(jCodingsEnc, c);
            if (!(enc.is7BitCompatible() && c <= 0x7f)) {
                sb.updateCodeRange(TSCodeRange.getValid(JCodings.getInstance().isSingleByte(jCodingsEnc)));
            }
            if (length < 1) {
                throw InternalErrors.invalidCodePoint(c);
            }
            sb.ensureCapacityS0(node, length * n, bufferGrowProfile, errorProfile);
            for (int i = 0; i < n; i++) {
                int ret = JCodings.getInstance().writeCodePoint(jCodingsEnc, c, sb.buf, sb.length);
                if (ret != length || JCodings.getInstance().getCodePointLength(jCodingsEnc, sb.buf, sb.length, sb.length + length) != ret ||
                                JCodings.getInstance().readCodePoint(jCodingsEnc, sb.buf, sb.length, sb.length + length, TruffleString.ErrorHandling.RETURN_NEGATIVE) != c) {
                    throw InternalErrors.invalidCodePoint(c);
                }
                sb.length += length;
            }
        }

        private static void arrayFill(TruffleStringBuilder sb, int c, int n, int stride) {
            byte[] buf = sb.buf;
            int from = sb.length;
            int to = from + n;
            for (int i = from; i < to; i++) {
                TStringOps.writeToByteArray(buf, stride, i, c);
            }
            sb.length = to;
        }
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendCodePointNode}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendCodePointUncached(int codepoint) {
        AppendCodePointNode.getUncached().execute(this, codepoint);
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendCodePointNode}.
     *
     * @since 22.2
     */
    @TruffleBoundary
    public void appendCodePointUncached(int codepoint, int repeat) {
        AppendCodePointNode.getUncached().execute(this, codepoint, repeat);
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendCodePointNode}.
     *
     * @since 22.2
     */
    @TruffleBoundary
    public void appendCodePointUncached(int codepoint, int repeat, boolean allowUTF16Surrogates) {
        AppendCodePointNode.getUncached().execute(this, codepoint, repeat, allowUTF16Surrogates);
    }

    /**
     * Node to append an integer to a string builder. See
     * {@link #execute(TruffleStringBuilder, int)} for details.
     *
     * @since 22.1
     */
    public abstract static class AppendIntNumberNode extends AbstractPublicNode {

        AppendIntNumberNode() {
        }

        /**
         * Append the base-10 string equivalent of a given integer to the string builder. For
         * ASCII-compatible encodings only.
         *
         * @since 22.1
         */
        public abstract void execute(TruffleStringBuilder sb, int value);

        @SuppressWarnings("unused")
        @Specialization(guards = "compaction == cachedCompaction", limit = Stride.STRIDE_CACHE_LIMIT, unroll = Stride.STRIDE_UNROLL)
        void doAppend(TruffleStringBuilder sb, int value,
                        @Bind("fromStride(sb.stride)") CompactionLevel compaction,
                        @Cached("compaction") CompactionLevel cachedCompaction,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            if (!is7BitCompatible(sb.encoding)) {
                throw InternalErrors.unsupportedOperation("appendIntNumber is supported on ASCII-compatible encodings only");
            }
            int len = NumberConversion.stringLengthInt(value);
            int cachedStride = cachedCompaction.getStride();
            sb.ensureCapacity(this, len, cachedStride, cachedStride, bufferGrowProfile, errorProfile);
            NumberConversion.writeIntToBytes(this, value, sb.buf, cachedStride, sb.length, len);
            sb.appendLength(len);
        }

        /**
         * Create a new {@link AppendIntNumberNode}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static AppendIntNumberNode create() {
            return TruffleStringBuilderFactory.AppendIntNumberNodeGen.create();
        }

        /**
         * Get the uncached version of {@link AppendIntNumberNode}.
         *
         * @since 22.1
         */
        public static AppendIntNumberNode getUncached() {
            return TruffleStringBuilderFactory.AppendIntNumberNodeGen.getUncached();
        }
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendIntNumberNode}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendIntNumberUncached(int value) {
        AppendIntNumberNode.getUncached().execute(this, value);
    }

    /**
     * Node to append a {@code long} value to a string builder. See
     * {@link #execute(TruffleStringBuilder, long)} for details.
     *
     * @since 22.1
     */
    public abstract static class AppendLongNumberNode extends AbstractPublicNode {

        AppendLongNumberNode() {
        }

        /**
         * Append the base-10 string equivalent of a given long value to the string builder. For
         * ASCII-compatible encodings only.
         *
         * @since 22.1
         */
        public abstract void execute(TruffleStringBuilder sb, long value);

        @SuppressWarnings("unused")
        @Specialization(guards = "compaction == cachedCompaction", limit = Stride.STRIDE_CACHE_LIMIT, unroll = Stride.STRIDE_UNROLL)
        void doAppend(TruffleStringBuilder sb, long value,
                        @Bind("fromStride(sb.stride)") CompactionLevel compaction,
                        @Cached("compaction") CompactionLevel cachedCompaction,
                        @Cached InlinedConditionProfile bufferGrowProfile,
                        @Cached InlinedBranchProfile errorProfile) {
            if (!is7BitCompatible(sb.encoding)) {
                throw InternalErrors.unsupportedOperation("appendIntNumber is supported on ASCII-compatible encodings only");
            }
            int cachedStride = cachedCompaction.getStride();
            int len = NumberConversion.stringLengthLong(value);
            sb.ensureCapacity(this, len, cachedStride, cachedStride, bufferGrowProfile, errorProfile);
            NumberConversion.writeLongToBytes(this, value, sb.buf, cachedStride, sb.length, len);
            sb.appendLength(len);
        }

        /**
         * Create a new {@link AppendLongNumberNode}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static AppendLongNumberNode create() {
            return TruffleStringBuilderFactory.AppendLongNumberNodeGen.create();
        }

        /**
         * Get the uncached version of {@link AppendLongNumberNode}.
         *
         * @since 22.1
         */
        public static AppendLongNumberNode getUncached() {
            return TruffleStringBuilderFactory.AppendLongNumberNodeGen.getUncached();
        }
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendLongNumberNode}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendLongNumberUncached(long value) {
        AppendLongNumberNode.getUncached().execute(this, value);
    }

    /**
     * Node to append a given {@link TruffleString} to a string builder.
     *
     * @since 22.1
     */
    public abstract static class AppendStringNode extends AbstractPublicNode {

        AppendStringNode() {
        }

        /**
         * Append a given {@link TruffleString} to the string builder.
         *
         * @since 22.1
         */
        public abstract void execute(TruffleStringBuilder sb, AbstractTruffleString a);

        @Specialization
        void append(TruffleStringBuilder sb, AbstractTruffleString a,
                        @Cached TruffleString.ToIndexableNode toIndexableNode,
                        @Cached TStringInternalNodes.GetCodePointLengthNode getCodePointLengthNode,
                        @Cached TStringInternalNodes.GetPreciseCodeRangeNode getPreciseCodeRangeNode,
                        @Cached AppendArrayIntlNode appendArrayIntlNode) {
            if (a.length() == 0) {
                return;
            }
            a.checkEncoding(sb.encoding);
            Object arrayA = toIndexableNode.execute(this, a, a.data());
            int codeRangeA = getPreciseCodeRangeNode.execute(this, a, sb.encoding);
            sb.updateCodeRange(codeRangeA);
            int newStride = Math.max(sb.stride, Stride.fromCodeRangeAllowImprecise(codeRangeA, sb.encoding));
            appendArrayIntlNode.execute(this, sb, arrayA, a.offset(), a.length(), a.stride(), newStride);
            sb.appendLength(a.length(), getCodePointLengthNode.execute(this, a, sb.encoding));
        }

        /**
         * Create a new {@link AppendStringNode}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static AppendStringNode create() {
            return TruffleStringBuilderFactory.AppendStringNodeGen.create();
        }

        /**
         * Get the uncached version of {@link AppendStringNode}.
         *
         * @since 22.1
         */
        public static AppendStringNode getUncached() {
            return TruffleStringBuilderFactory.AppendStringNodeGen.getUncached();
        }
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendStringNode}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendStringUncached(TruffleString a) {
        AppendStringNode.getUncached().execute(this, a);
    }

    /**
     * Node to append a substring of a given {@link TruffleString} to a string builder. See
     * {@link #execute(TruffleStringBuilder, AbstractTruffleString, int, int)} for details.
     *
     * @since 22.1
     */
    public abstract static class AppendSubstringByteIndexNode extends AbstractPublicNode {

        AppendSubstringByteIndexNode() {
        }

        /**
         * Append a substring of a given {@link TruffleString}, starting at byte index
         * {@code fromByteIndex} and ending at byte index {@code fromByteIndex + byteLength}, to the
         * string builder.
         *
         * @since 22.1
         */
        public abstract void execute(TruffleStringBuilder sb, AbstractTruffleString a, int fromByteIndex, int byteLength);

        @Specialization
        final void append(TruffleStringBuilder sb, AbstractTruffleString a, int fromByteIndex, int byteLength,
                        @Cached TruffleString.ToIndexableNode toIndexableNode,
                        @Cached TStringInternalNodes.GetCodePointLengthNode getCodePointLengthNode,
                        @Cached TStringInternalNodes.GetPreciseCodeRangeNode getPreciseCodeRangeNode,
                        @Cached AppendArrayIntlNode appendArrayIntlNode,
                        @Cached TStringInternalNodes.CalcStringAttributesNode calcAttributesNode,
                        @Cached InlinedConditionProfile calcAttrsProfile) {
            if (byteLength == 0) {
                return;
            }
            a.checkEncoding(sb.encoding);
            final int fromIndex = TruffleString.rawIndex(fromByteIndex, sb.encoding);
            final int length = TruffleString.rawIndex(byteLength, sb.encoding);
            a.boundsCheckRegionRaw(fromIndex, length);
            Object arrayA = toIndexableNode.execute(this, a, a.data());
            final int codeRangeA = getPreciseCodeRangeNode.execute(this, a, sb.encoding);
            final int codeRange;
            final int codePointLength;
            if (fromIndex == 0 && length == a.length()) {
                codeRange = codeRangeA;
                codePointLength = getCodePointLengthNode.execute(this, a, sb.encoding);
            } else if (isFixedWidth(codeRangeA) && !TSCodeRange.isMoreGeneralThan(codeRangeA, sb.codeRange)) {
                codeRange = codeRangeA;
                codePointLength = length;
            } else if (calcAttrsProfile.profile(this, !isBroken(sb.codeRange))) {
                long attrs = calcAttributesNode.execute(this, a, arrayA, a.offset(), length, a.stride(), sb.encoding, fromIndex, codeRangeA);
                codeRange = StringAttributes.getCodeRange(attrs);
                codePointLength = StringAttributes.getCodePointLength(attrs);
            } else {
                codeRange = TSCodeRange.getUnknownCodeRangeForEncoding(sb.encoding.id);
                codePointLength = 0;
            }
            sb.updateCodeRange(codeRange);
            appendArrayIntlNode.execute(this, sb, arrayA, a.offset() + (fromIndex << a.stride()), length, a.stride(), Stride.fromCodeRangeAllowImprecise(sb.codeRange, sb.encoding));
            sb.appendLength(length, codePointLength);
        }

        /**
         * Create a new {@link AppendSubstringByteIndexNode}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static AppendSubstringByteIndexNode create() {
            return TruffleStringBuilderFactory.AppendSubstringByteIndexNodeGen.create();
        }

        /**
         * Get the uncached version of {@link AppendSubstringByteIndexNode}.
         *
         * @since 22.1
         */
        public static AppendSubstringByteIndexNode getUncached() {
            return TruffleStringBuilderFactory.AppendSubstringByteIndexNodeGen.getUncached();
        }
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendSubstringByteIndexNode}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendSubstringByteIndexUncached(TruffleString a, int fromByteIndex, int byteLength) {
        AppendSubstringByteIndexNode.getUncached().execute(this, a, fromByteIndex, byteLength);
    }

    /**
     * Node to append a substring of a given {@link java.lang.String} to a string builder. See
     * {@link #execute(TruffleStringBuilder, String, int, int)} for details.
     *
     * @since 22.1
     */
    public abstract static class AppendJavaStringUTF16Node extends AbstractPublicNode {

        AppendJavaStringUTF16Node() {
        }

        /**
         * Append a substring of a given {@link java.lang.String} to the string builder. For UTF-16
         * only.
         *
         * @since 22.1
         */
        public final void execute(TruffleStringBuilder sb, String a) {
            execute(sb, a, 0, a.length());
        }

        /**
         * Append a substring of a given {@link java.lang.String}, starting at char index
         * {@code fromIndex} and ending at {@code fromIndex + length}, to the string builder. For
         * UTF-16 only.
         *
         * @since 22.1
         */
        public abstract void execute(TruffleStringBuilder sb, String a, int fromCharIndex, int charLength);

        @Specialization
        final void append(TruffleStringBuilder sb, String javaString, int fromIndex, int lengthStr,
                        @Cached AppendArrayIntlNode appendArrayIntlNode,
                        @Cached InlinedConditionProfile stride0Profile) {
            if (!isUTF16(sb)) {
                throw InternalErrors.unsupportedOperation("appendJavaString is supported on UTF-16 only, use appendString for other encodings");
            }
            if (lengthStr == 0) {
                return;
            }
            boundsCheckRegionI(fromIndex, lengthStr, javaString.length());
            final int appendCodePointLength;
            final byte[] arrayStr = TStringUnsafe.getJavaStringArray(javaString);
            final int strideStr = TStringUnsafe.getJavaStringStride(javaString);
            final int offsetStr = fromIndex << strideStr;
            if (stride0Profile.profile(this, strideStr == 0)) {
                if (is7Bit(sb.codeRange)) {
                    sb.updateCodeRange(TStringOps.calcStringAttributesLatin1(this, arrayStr, offsetStr, lengthStr));
                }
                appendCodePointLength = lengthStr;
            } else {
                if (!isBrokenMultiByte(sb.codeRange)) {
                    long attrs = TStringOps.calcStringAttributesUTF16(this, arrayStr, offsetStr, lengthStr, false);
                    sb.updateCodeRange(StringAttributes.getCodeRange(attrs));
                    appendCodePointLength = StringAttributes.getCodePointLength(attrs);
                } else {
                    appendCodePointLength = 0;
                }
            }
            appendArrayIntlNode.execute(this, sb, arrayStr, offsetStr, lengthStr, strideStr, Stride.fromCodeRangeUTF16AllowImprecise(sb.codeRange));
            sb.appendLength(lengthStr, appendCodePointLength);
        }

        /**
         * Create a new {@link AppendJavaStringUTF16Node}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static AppendJavaStringUTF16Node create() {
            return TruffleStringBuilderFactory.AppendJavaStringUTF16NodeGen.create();
        }

        /**
         * Get the uncached version of {@link AppendJavaStringUTF16Node}.
         *
         * @since 22.1
         */
        public static AppendJavaStringUTF16Node getUncached() {
            return TruffleStringBuilderFactory.AppendJavaStringUTF16NodeGen.getUncached();
        }
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendJavaStringUTF16Node}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendJavaStringUTF16Uncached(String a) {
        AppendJavaStringUTF16Node.getUncached().execute(this, a);
    }

    /**
     * Shorthand for calling the uncached version of {@link AppendJavaStringUTF16Node}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public void appendJavaStringUTF16Uncached(String a, int fromCharIndex, int charLength) {
        AppendJavaStringUTF16Node.getUncached().execute(this, a, fromCharIndex, charLength);
    }

    /**
     * Node to materialize a string builder as a {@link TruffleString}.
     *
     * @since 22.1
     */
    public abstract static class ToStringNode extends AbstractPublicNode {

        ToStringNode() {
        }

        /**
         * Materialize this string builder to a {@link TruffleString}.
         *
         * @since 22.1
         */
        public final TruffleString execute(TruffleStringBuilder sb) {
            return execute(sb, false);
        }

        /**
         * Materialize this string builder to a {@link TruffleString}.
         *
         * If {@code lazy} is {@code true}, {@code sb}'s internal storage will be re-used even if
         * there are unused bytes. Since the resulting string will have a reference to {@code sb}'s
         * internal storage, and {@link TruffleString} currently does not resize/trim the
         * substring's internal storage at any point, the {@code lazy} variant effectively creates a
         * memory leak! The caller is responsible for deciding whether this is acceptable or not.
         *
         * @since 22.1
         */
        public abstract TruffleString execute(TruffleStringBuilder sb, boolean lazy);

        @Specialization
        final TruffleString createString(TruffleStringBuilder sb, boolean lazy,
                        @Cached InlinedConditionProfile calcAttributesProfile,
                        @Cached TStringInternalNodes.CalcStringAttributesNode calcAttributesNode) {
            if (sb.length == 0) {
                return sb.encoding.getEmpty();
            }
            final int codeRange;
            final int codePointLength;
            if (calcAttributesProfile.profile(this, !TSCodeRange.isPrecise(sb.codeRange) || TSCodeRange.isBrokenMultiByte(sb.codeRange))) {
                long attrs = calcAttributesNode.execute(this, null, sb.buf, 0, sb.length, sb.stride, sb.encoding, 0, sb.codeRange);
                codeRange = StringAttributes.getCodeRange(attrs);
                codePointLength = StringAttributes.getCodePointLength(attrs);
            } else {
                codeRange = sb.codeRange;
                codePointLength = sb.codePointLength;
            }
            int byteLength = sb.length << sb.stride;
            byte[] bytes = lazy || sb.buf.length == byteLength ? sb.buf : Arrays.copyOf(sb.buf, byteLength);
            return TruffleString.createFromByteArray(bytes, sb.length, sb.stride, sb.encoding, codePointLength, codeRange);
        }

        /**
         * Create a new {@link ToStringNode}.
         *
         * @since 22.1
         */
        @NeverDefault
        public static ToStringNode create() {
            return TruffleStringBuilderFactory.ToStringNodeGen.create();
        }

        /**
         * Get the uncached version of {@link ToStringNode}.
         *
         * @since 22.1
         */
        public static ToStringNode getUncached() {
            return TruffleStringBuilderFactory.ToStringNodeGen.getUncached();
        }
    }

    /**
     * Shorthand for calling the uncached version of {@link ToStringNode}.
     *
     * @since 22.1
     */
    @TruffleBoundary
    public TruffleString toStringUncached() {
        return ToStringNode.getUncached().execute(this);
    }

    abstract static class AppendArrayIntlNode extends AbstractInternalNode {

        abstract void execute(Node node, TruffleStringBuilder sb, Object array, int offsetA, int lengthA, int strideA, int strideNew);

        @Specialization(guards = {"sb.stride == cachedStrideSB", "strideA == cachedStrideA", "strideNew == cachedStrideNew"}, limit = TStringOpsNodes.LIMIT_STRIDE)
        static void doCached(Node node, TruffleStringBuilder sb, Object array, int offsetA, int lengthA, @SuppressWarnings("unused") int strideA, @SuppressWarnings("unused") int strideNew,
                        @Cached(value = "sb.stride") int cachedStrideSB,
                        @Cached(value = "strideA") int cachedStrideA,
                        @Cached(value = "strideNew") int cachedStrideNew,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            doAppend(node, sb, array, offsetA, lengthA, cachedStrideSB, cachedStrideA, cachedStrideNew, bufferGrowProfile, errorProfile);
        }

        @Specialization(replaces = "doCached")
        static void doUncached(Node node, TruffleStringBuilder sb, Object array, int offsetA, int lengthA, int strideA, int strideNew,
                        @Shared("bufferGrow") @Cached InlinedConditionProfile bufferGrowProfile,
                        @Shared("error") @Cached InlinedBranchProfile errorProfile) {
            doAppend(node, sb, array, offsetA, lengthA, sb.stride, strideA, strideNew, bufferGrowProfile, errorProfile);
        }

        private static void doAppend(Node location, TruffleStringBuilder sb, Object array, int offsetA, int lengthA, int cachedStrideSB, int cachedStrideA, int cachedStrideNew,
                        InlinedConditionProfile bufferGrowProfile,
                        InlinedBranchProfile errorProfile) {
            sb.ensureCapacity(location, lengthA, cachedStrideSB, cachedStrideNew, bufferGrowProfile, errorProfile);
            assert sb.stride == cachedStrideNew;
            TStringOps.arraycopyWithStride(location,
                            array, offsetA, cachedStrideA, 0,
                            sb.buf, 0, cachedStrideNew, sb.length, lengthA);
        }
    }

    void ensureCapacityS0(Node node, int appendLength, InlinedConditionProfile bufferGrowProfile, InlinedBranchProfile errorProfile) {
        assert stride == 0;
        final long newLength = (long) length + appendLength;
        if (bufferGrowProfile.profile(node, newLength > bufferLength())) {
            long newBufferLength = ((long) bufferLength() << 1) + 2;
            assert newLength >= 0;
            final int maxLength = TStringConstants.MAX_ARRAY_SIZE;
            if (newLength > maxLength) {
                errorProfile.enter(node);
                throw InternalErrors.outOfMemory();
            }
            newBufferLength = Math.min(newBufferLength, maxLength);
            newBufferLength = Math.max(newBufferLength, newLength);
            buf = Arrays.copyOf(buf, (int) newBufferLength);
        }
    }

    void ensureCapacity(Node location, int appendLength, int curStride, int newStride, InlinedConditionProfile bufferGrowProfile, InlinedBranchProfile errorProfile) {
        assert curStride == stride;
        assert newStride >= stride;
        final long newLength = (long) length + appendLength;
        if (bufferGrowProfile.profile(location, curStride != newStride || newLength > bufferLength())) {
            long newBufferLength = newLength > bufferLength() ? ((long) bufferLength() << 1) + 2 : bufferLength();
            assert newLength >= 0;
            final int maxLength = TStringConstants.MAX_ARRAY_SIZE >> newStride;
            if (newLength > maxLength) {
                errorProfile.enter(location);
                throw InternalErrors.outOfMemory();
            }
            newBufferLength = Math.min(newBufferLength, maxLength);
            newBufferLength = Math.max(newBufferLength, newLength);
            buf = TStringOps.arraycopyOfWithStride(location, buf, 0, length, curStride, (int) newBufferLength, newStride);
            stride = newStride;
        }
    }

    /**
     * Convert the string builder's content to a java string. Do not use this on a fast path.
     *
     * @since 22.1
     */
    @TruffleBoundary
    @Override
    public String toString() {
        return ToStringNode.getUncached().execute(this).toJavaStringUncached();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy