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

commonMain.org.luaj.vm2.LuaString.kt Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2009-2011 Luaj.org. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall 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.luaj.vm2


import org.luaj.vm2.internal.*
import org.luaj.vm2.io.*

import org.luaj.vm2.lib.MathLib
import kotlin.jvm.*
import kotlin.native.concurrent.*

/**
 * Subclass of [LuaValue] for representing lua strings.
 *
 *
 * Because lua string values are more nearly sequences of bytes than
 * sequences of characters or unicode code points, the [LuaString]
 * implementation holds the string value in an internal byte array.
 *
 *
 * [LuaString] values are not considered mutable once constructed,
 * so multiple [LuaString] values can chare a single byte array.
 *
 *
 * Currently [LuaString]s are pooled via a centrally managed weak table.
 * To ensure that as many string values as possible take advantage of this,
 * Constructors are not exposed directly.  As with number, booleans, and nil,
 * instance construction should be via [LuaValue.valueOf] or similar API.
 *
 *
 * Because of this pooling, users of LuaString *must not directly alter the
 * bytes in a LuaString*, or undefined behavior will result.
 *
 *
 * When Java Strings are used to initialize [LuaString] data, the UTF8 encoding is assumed.
 * The functions
 * [.lengthAsUtf8],
 * [.encodeToUtf8], and
 * [.decodeAsUtf8]
 * are used to convert back and forth between UTF8 byte arrays and character arrays.
 *
 * @see LuaValue
 *
 * @see LuaValue.valueOf
 * @see LuaValue.valueOf
 */
class LuaString
/** Construct a [LuaString] around a byte array without copying the contents.
 *
 *
 * The array is used directly after this is called, so clients must not change contents.
 *
 *
 * @param bytes byte buffer
 * @param offset offset into the byte buffer
 * @param length length of the byte buffer
 * @return [LuaString] wrapping the byte buffer
 */
private constructor(
    /** The bytes for the string.  These ***must not be mutated directly*** because
     * the backing may be shared by multiple LuaStrings, and the hash code is
     * computed only at construction time.
     * It is exposed only for performance and legacy reasons.  */
    @kotlin.jvm.JvmField val m_bytes: ByteArray,
    /** The offset into the byte array, 0 means start at the first byte  */
    @kotlin.jvm.JvmField val m_offset: Int,
    /** The number of bytes that comprise this string  */
    @kotlin.jvm.JvmField val m_length: Int
) : LuaValue() {

    /** The hashcode for this string.  Computed at construct time.  */
    private val m_hashcode: Int = hashCode(m_bytes, m_offset, m_length)

    /** Check that a byte sequence is valid UTF-8
     * @return true if it is valid UTF-8, otherwise false
     * @see .lengthAsUtf8
     * @see .encodeToUtf8
     * @see .decodeAsUtf8
     */
    val isValidUtf8: Boolean
        get() {
            var i = m_offset
            val j = m_offset + m_length
            while (i < j) {
                val c = m_bytes[i++].toInt()
                if (c >= 0) continue
                if (c and 0xE0 == 0xC0
                    && i < j
                    && m_bytes[i++].toInt() and 0xC0 == 0x80
                )
                    continue
                if (c and 0xF0 == 0xE0
                    && i + 1 < j
                    && m_bytes[i++].toInt() and 0xC0 == 0x80
                    && m_bytes[i++].toInt() and 0xC0 == 0x80
                )
                    continue
                return false
            }
            return true
        }

    override fun isstring(): Boolean = true
    override fun getmetatable(): LuaValue? = s_metatable
    override fun type(): Int = LuaValue.TSTRING
    override fun typename(): String = "string"
    override fun tojstring(): String = decodeAsUtf8(m_bytes, m_offset, m_length)

    // unary operators
    override fun neg(): LuaValue = scannumber().let { d -> if ((d).isNaN()) super.neg() else LuaValue.valueOf(-d) }

    // basic binary arithmetic
    override fun add(rhs: LuaValue): LuaValue = scannumber().let { d -> if ((d).isNaN()) arithmt(LuaValue.ADD, rhs) else rhs.add(d) }
    override fun add(rhs: Double): LuaValue = LuaValue.valueOf(checkarith() + rhs)
    override fun add(rhs: Int): LuaValue = LuaValue.valueOf(checkarith() + rhs)
    override fun sub(rhs: LuaValue): LuaValue = scannumber().let { d -> if ((d).isNaN()) arithmt(LuaValue.SUB, rhs) else rhs.subFrom(d) }
    override fun sub(rhs: Double): LuaValue = LuaValue.valueOf(checkarith() - rhs)
    override fun sub(rhs: Int): LuaValue = LuaValue.valueOf(checkarith() - rhs)
    override fun subFrom(lhs: Double): LuaValue = LuaValue.valueOf(lhs - checkarith())
    override fun mul(rhs: LuaValue): LuaValue = scannumber().let { d -> if ((d).isNaN()) arithmt(LuaValue.MUL, rhs) else rhs.mul(d) }
    override fun mul(rhs: Double): LuaValue = LuaValue.valueOf(checkarith() * rhs)
    override fun mul(rhs: Int): LuaValue = LuaValue.valueOf(checkarith() * rhs)
    override fun pow(rhs: LuaValue): LuaValue = scannumber().let { d -> if ((d).isNaN()) arithmt(LuaValue.POW, rhs) else rhs.powWith(d) }
    override fun pow(rhs: Double): LuaValue = MathLib.dpow(checkarith(), rhs)
    override fun pow(rhs: Int): LuaValue = MathLib.dpow(checkarith(), rhs.toDouble())
    override fun powWith(lhs: Double): LuaValue = MathLib.dpow(lhs, checkarith())
    override fun powWith(lhs: Int): LuaValue = MathLib.dpow(lhs.toDouble(), checkarith())
    override fun div(rhs: LuaValue): LuaValue = scannumber().let { d -> if ((d).isNaN()) arithmt(LuaValue.DIV, rhs) else rhs.divInto(d) }
    override fun div(rhs: Double): LuaValue = LuaDouble.ddiv(checkarith(), rhs)
    override fun div(rhs: Int): LuaValue = LuaDouble.ddiv(checkarith(), rhs.toDouble())
    override fun divInto(lhs: Double): LuaValue = LuaDouble.ddiv(lhs, checkarith())
    override fun mod(rhs: LuaValue): LuaValue = scannumber().let { d -> if ((d).isNaN()) arithmt(LuaValue.MOD, rhs) else rhs.modFrom(d) }
    override fun mod(rhs: Double): LuaValue = LuaDouble.dmod(checkarith(), rhs)
    override fun mod(rhs: Int): LuaValue = LuaDouble.dmod(checkarith(), rhs.toDouble())
    override fun modFrom(lhs: Double): LuaValue = LuaDouble.dmod(lhs, checkarith())

    // relational operators, these only work with other strings
    override fun lt(rhs: LuaValue): LuaValue = if (rhs.strcmp(this) > 0) LuaValue.TRUE else LuaValue.FALSE
    override fun lt_b(rhs: LuaValue): Boolean = rhs.strcmp(this) > 0

    override fun lt_b(rhs: Int): Boolean = typerror("attempt to compare string with number")
    override fun lt_b(rhs: Double): Boolean = typerror("attempt to compare string with number")
    override fun lteq(rhs: LuaValue): LuaValue = if (rhs.strcmp(this) >= 0) LuaValue.TRUE else LuaValue.FALSE
    override fun lteq_b(rhs: LuaValue): Boolean = rhs.strcmp(this) >= 0
    override fun lteq_b(rhs: Int): Boolean = typerror("attempt to compare string with number")
    override fun lteq_b(rhs: Double): Boolean = typerror("attempt to compare string with number")
    override fun gt(rhs: LuaValue): LuaValue = if (rhs.strcmp(this) < 0) LuaValue.TRUE else LuaValue.FALSE
    override fun gt_b(rhs: LuaValue): Boolean = rhs.strcmp(this) < 0
    override fun gt_b(rhs: Int): Boolean = typerror("attempt to compare string with number")
    override fun gt_b(rhs: Double): Boolean = typerror("attempt to compare string with number")
    override fun gteq(rhs: LuaValue): LuaValue = if (rhs.strcmp(this) <= 0) LuaValue.TRUE else LuaValue.FALSE
    override fun gteq_b(rhs: LuaValue): Boolean = rhs.strcmp(this) <= 0
    override fun gteq_b(rhs: Int): Boolean = typerror("attempt to compare string with number")
    override fun gteq_b(rhs: Double): Boolean = typerror("attempt to compare string with number")

    // concatenation
    override fun concat(rhs: LuaValue): LuaValue = rhs.concatTo(this)
    override fun concat(rhs: Buffer): Buffer = rhs.concatTo(this)
    override fun concatTo(lhs: LuaNumber): LuaValue = concatTo(lhs.strvalue()!!)
    override fun concatTo(lhs: LuaString): LuaValue {
        val b = ByteArray(lhs.m_length + this.m_length)
        arraycopy(lhs.m_bytes, lhs.m_offset, b, 0, lhs.m_length)
        arraycopy(this.m_bytes, this.m_offset, b, lhs.m_length, this.m_length)
        return valueUsing(b, 0, b.size)
    }

    // string comparison
    override fun strcmp(lhs: LuaValue): Int = -lhs.strcmp(this)

    override fun strcmp(rhs: LuaString): Int {
        var i = 0
        var j = 0
        while (i < m_length && j < rhs.m_length) {
            if (m_bytes[m_offset + i] != rhs.m_bytes[rhs.m_offset + j]) {
                return m_bytes[m_offset + i].toInt() - rhs.m_bytes[rhs.m_offset + j].toInt()
            }
            ++i
            ++j
        }
        return m_length - rhs.m_length
    }

    /** Check for number in arithmetic, or throw aritherror  */
    private fun checkarith(): Double {
        val d = scannumber()
        if ((d).isNaN()) aritherror()
        return d
    }

    override fun checkint(): Int = checkdouble().toLong().toInt()
    override fun checkinteger(): LuaInteger? = LuaValue.valueOf(checkint())
    override fun checklong(): Long = checkdouble().toLong()

    override fun checkdouble(): Double {
        val d = scannumber()
        if ((d).isNaN()) argerror("number")
        return d
    }

    override fun checknumber(): LuaNumber? = LuaValue.valueOf(checkdouble())
    override fun checknumber(msg: String): LuaNumber {
        val d = scannumber()
        if ((d).isNaN())
            LuaValue.error(msg)
        return LuaValue.valueOf(d)
    }

    override fun isnumber(): Boolean = scannumber().let { d -> !(d.isNaN()) }

    override fun isint(): Boolean {
        val d = scannumber()
        if (d.isNaN())
            return false
        val i = d.toInt()
        return i.toDouble() == d
    }

    override fun islong(): Boolean {
        val d = scannumber()
        if ((d.isNaN()))
            return false
        val l = d.toLong()
        return l.toDouble() == d
    }

    override fun tobyte(): Byte = toint().toByte()
    override fun tochar(): Char = toint().toChar()

    override fun todouble(): Double = scannumber().let { d -> if ((d.isNaN())) 0.0 else d }
    override fun tofloat(): Float = todouble().toFloat()
    override fun toint(): Int = tolong().toInt()
    override fun tolong(): Long = todouble().toLong()
    override fun toshort(): Short = toint().toShort()
    override fun optdouble(defval: Double): Double = checknumber()!!.checkdouble()
    override fun optint(defval: Int): Int = checknumber()!!.checkint()
    override fun optinteger(defval: LuaInteger?): LuaInteger? = checknumber()!!.checkinteger()
    override fun optlong(defval: Long): Long = checknumber()!!.checklong()
    override fun optnumber(defval: LuaNumber?): LuaNumber? = checknumber()!!.checknumber()
    override fun optstring(defval: LuaString?): LuaString? = this
    override fun tostring(): LuaValue = this
    override fun optjstring(defval: String?): String? = tojstring()
    override fun strvalue(): LuaString? = this

    /** Take a substring using Java zero-based indexes for begin and end or range.
     * @param beginIndex  The zero-based index of the first character to include.
     * @param endIndex  The zero-based index of position after the last character.
     * @return LuaString which is a substring whose first character is at offset
     * beginIndex and extending for (endIndex - beginIndex ) characters.
     */
    fun substring(beginIndex: Int, endIndex: Int): LuaString {
        val off = m_offset + beginIndex
        val len = endIndex - beginIndex
        return if (len >= m_length / 2) valueUsing(m_bytes, off, len) else valueOf(m_bytes, off, len)
    }

    override fun hashCode(): Int = m_hashcode

    // object comparison, used in key comparison
    override fun equals(o: Any?): Boolean = if (o is LuaString) raweq(o) else false

    // equality w/ metatable processing
    override fun eq(`val`: LuaValue): LuaValue = if (`val`.raweq(this)) LuaValue.TRUE else LuaValue.FALSE
    override fun eq_b(`val`: LuaValue): Boolean = `val`.raweq(this)

    // equality w/o metatable processing
    override fun raweq(`val`: LuaValue): Boolean = `val`.raweq(this)

    override fun raweq(s: LuaString): Boolean {
        if (this === s) return true
        if (s.m_length != m_length) return false
        if (s.m_bytes == m_bytes && s.m_offset == m_offset) return true
        if (s.hashCode() != hashCode()) return false
        for (i in 0 until m_length) if (s.m_bytes[s.m_offset + i] != m_bytes[m_offset + i]) return false
        return true
    }

    /** Return true if the bytes in the supplied range match this LuaStrings bytes.  */
    private fun byteseq(bytes: ByteArray, off: Int, len: Int): Boolean = m_length == len && equals(m_bytes, m_offset, bytes, off, len)
    fun write(writer: LuaBinOutput, i: Int, len: Int) = writer.write(m_bytes, m_offset + i, len)
    override fun len(): LuaValue = LuaInteger.valueOf(m_length)
    override fun length(): Int = m_length
    override fun rawlen(): Int = m_length
    fun luaByte(index: Int): Int = m_bytes[m_offset + index].toInt() and 0x0FF
    fun charAt(index: Int): Int {
        if (index < 0 || index >= m_length) throw IndexOutOfBoundsException()
        return luaByte(index)
    }

    override fun checkjstring(): String? = tojstring()
    override fun checkstring(): LuaString = this

    /** Convert value to an input stream.
     *
     * @return [InputStream] whose data matches the bytes in this [LuaString]
     */
    fun toLuaBinInput(): BytesLuaBinInput = BytesLuaBinInput(m_bytes, m_offset, m_length)

    /**
     * Copy the bytes of the string into the given byte array.
     * @param strOffset offset from which to copy
     * @param bytes destination byte array
     * @param arrayOffset offset in destination
     * @param len number of bytes to copy
     */
    fun copyInto(strOffset: Int, bytes: ByteArray, arrayOffset: Int, len: Int) =
        arraycopy(m_bytes, m_offset + strOffset, bytes, arrayOffset, len)

    /** Java version of strpbrk - find index of any byte that in an accept string.
     * @param accept [LuaString] containing characters to look for.
     * @return index of first match in the `accept` string, or -1 if not found.
     */
    fun indexOfAny(accept: LuaString): Int {
        val ilimit = m_offset + m_length
        val jlimit = accept.m_offset + accept.m_length
        for (i in m_offset until ilimit) {
            for (j in accept.m_offset until jlimit) {
                if (m_bytes[i] == accept.m_bytes[j]) {
                    return i - m_offset
                }
            }
        }
        return -1
    }

    /**
     * Find the index of a byte starting at a point in this string
     * @param b the byte to look for
     * @param start the first index in the string
     * @return index of first match found, or -1 if not found.
     */
    fun indexOf(b: Byte, start: Int): Int {
        for (i in start until m_length) if (m_bytes[m_offset + i] == b) return i
        return -1
    }

    /**
     * Find the index of a string starting at a point in this string
     * @param s the string to search for
     * @param start the first index in the string
     * @return index of first match found, or -1 if not found.
     */
    fun indexOf(s: LuaString, start: Int): Int {
        val slen = s.length()
        val limit = m_length - slen
        for (i in start..limit) if (equals(m_bytes, m_offset + i, s.m_bytes, s.m_offset, slen)) return i
        return -1
    }

    /**
     * Find the last index of a string in this string
     * @param s the string to search for
     * @return index of last match found, or -1 if not found.
     */
    fun lastIndexOf(s: LuaString): Int {
        val slen = s.length()
        val limit = m_length - slen
        for (i in limit downTo 0) if (equals(m_bytes, m_offset + i, s.m_bytes, s.m_offset, slen)) return i
        return -1
    }

    // --------------------- number conversion -----------------------

    /**
     * convert to a number using baee 10 or base 16 if it starts with '0x',
     * or NIL if it can't be converted
     * @return IntValue, DoubleValue, or NIL depending on the content of the string.
     * @see LuaValue.tonumber
     */
    override fun tonumber(): LuaValue {
        val d = scannumber()
        return if ((d.isNaN())) LuaValue.NIL else LuaValue.valueOf(d)
    }

    /**
     * convert to a number using a supplied base, or NIL if it can't be converted
     * @param base the base to use, such as 10
     * @return IntValue, DoubleValue, or NIL depending on the content of the string.
     * @see LuaValue.tonumber
     */
    fun tonumber(base: Int): LuaValue {
        val d = scannumber(base)
        return if ((d.isNaN())) LuaValue.NIL else LuaValue.valueOf(d)
    }

    /**
     * Convert to a number in base 10, or base 16 if the string starts with '0x',
     * or return Double.NaN if it cannot be converted to a number.
     * @return double value if conversion is valid, or Double.NaN if not
     */
    fun scannumber(): Double {
        var i = m_offset
        var j = m_offset + m_length
        while (i < j && m_bytes[i] == ' '.toByte()) ++i
        while (i < j && m_bytes[j - 1] == ' '.toByte()) --j
        if (i >= j)
            return Double.NaN
        if (m_bytes[i] == '0'.toByte() && i + 1 < j && (m_bytes[i + 1] == 'x'.toByte() || m_bytes[i + 1] == 'X'.toByte()))
            return scanlong(16, i + 2, j)
        val l = scanlong(10, i, j)
        return if ((l.isNaN())) scandouble(i, j) else l
    }

    /**
     * Convert to a number in a base, or return Double.NaN if not a number.
     * @param base the base to use between 2 and 36
     * @return double value if conversion is valid, or Double.NaN if not
     */
    fun scannumber(base: Int): Double {
        if (base < 2 || base > 36)
            return Double.NaN
        var i = m_offset
        var j = m_offset + m_length
        while (i < j && m_bytes[i] == ' '.toByte()) ++i
        while (i < j && m_bytes[j - 1] == ' '.toByte()) --j
        return if (i >= j) Double.NaN else scanlong(base, i, j)
    }

    /**
     * Scan and convert a long value, or return Double.NaN if not found.
     * @param base the base to use, such as 10
     * @param start the index to start searching from
     * @param end the first index beyond the search range
     * @return double value if conversion is valid,
     * or Double.NaN if not
     */
    private fun scanlong(base: Int, start: Int, end: Int): Double {
        var x: Long = 0
        val neg = m_bytes[start] == '-'.toByte()
        for (i in (if (neg) start + 1 else start) until end) {
            val digit = (m_bytes[i].toInt() and 0xFF) - if (base <= 10 || m_bytes[i] >= '0'.toByte() && m_bytes[i] <= '9'.toByte())
                '0'.toInt()
            else if (m_bytes[i] >= 'A'.toByte() && m_bytes[i] <= 'Z'.toByte()) 'A'.toInt() - 10 else 'a'.toInt() - 10
            if (digit < 0 || digit >= base)
                return Double.NaN
            x = x * base + digit
            if (x < 0)
                return Double.NaN // overflow
        }
        return (if (neg) -x else x).toDouble()
    }

    /**
     * Scan and convert a double value, or return Double.NaN if not a double.
     * @param start the index to start searching from
     * @param end the first index beyond the search range
     * @return double value if conversion is valid,
     * or Double.NaN if not
     */
    private fun scandouble(start: Int, end: Int): Double {
        var end = end
        if (end > start + 64) end = start + 64
        for (i in start until end) {
            when (m_bytes[i].toChar()) {
                '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> {
                }
                else -> return Double.NaN
            }
        }
        val c = CharArray(end - start)
        for (i in start until end)
            c[i - start] = m_bytes[i].toChar()
        try {
            return (c.concatToString()).toDouble()
        } catch (e: Exception) {
            return Double.NaN
        }

    }

    /**
     * Print the bytes of the LuaString to a PrintStream as if it were
     * an ASCII string, quoting and escaping control characters.
     * @param ps PrintStream to print to.
     */
    fun printToStream(ps: LuaWriter) {
        var i = 0
        val n = m_length
        while (i < n) {
            val c = m_bytes[m_offset + i].toInt()
            ps.print(c.toChar())
            i++
        }
    }

    companion object {

        /** The singleton instance for string metatables that forwards to the string functions.
         * Typically, this is set to the string metatable as a side effect of loading the string
         * library, and is read-write to provide flexible behavior by default.  When used in a
         * server environment where there may be roge scripts, this should be replaced with a
         * read-only table since it is shared across all lua code in this Java VM.
         */
        var s_metatable: LuaValue?
            get() = LuaString_metatable
            set(value) {
                LuaString_metatable = value
            }

        /** Size of cache of recent short strings. This is the maximum number of LuaStrings that
         * will be retained in the cache of recent short strings.  Exposed to package for testing.  */
        const val RECENT_STRINGS_CACHE_SIZE = 128

        /** Maximum length of a string to be considered for recent short strings caching.
         * This effectively limits the total memory that can be spent on the recent strings cache,
         * because no LuaString whose backing exceeds this length will be put into the cache.
         * Exposed to package for testing.  */
        const val RECENT_STRINGS_MAX_LENGTH = 32

        /**
         * Get a [LuaString] instance whose bytes match
         * the supplied Java String using the UTF8 encoding.
         * @param string Java String containing characters to encode as UTF8
         * @return [LuaString] with UTF8 bytes corresponding to the supplied String
         */
        @UseExperimental(ExperimentalStdlibApi::class)
        @JvmName("valueOf2")
        fun valueOf(string: String): LuaString {
            val c = string.toCharArray()
            val b = ByteArray(lengthAsUtf8(c))
            encodeToUtf8(c, c.size, b, 0)
            return valueUsing(b, 0, b.size)
        }

        /** Construct a [LuaString] for a portion of a byte array.
         *
         *
         * The array is first be used as the backing for this object, so clients must not change contents.
         * If the supplied value for 'len' is more than half the length of the container, the
         * supplied byte array will be used as the backing, otherwise the bytes will be copied to a
         * new byte array, and cache lookup may be performed.
         *
         *
         * @param bytes byte buffer
         * @param off offset into the byte buffer
         * @param len length of the byte buffer
         * @return [LuaString] wrapping the byte buffer
         */
        @JvmName("valueOf2")

        fun valueOf(bytes: ByteArray, off: Int, len: Int, runtime: LuaRuntime? = LuaRuntime.default): LuaString {
            if (len > RECENT_STRINGS_MAX_LENGTH)
                return valueFromCopy(bytes, off, len)
            val hash = hashCode(bytes, off, len)
            val bucket = hash and RECENT_STRINGS_CACHE_SIZE - 1
            val t = runtime?.recent_short_strings?.get(bucket)
            if (t != null && t.m_hashcode == hash && t.byteseq(bytes, off, len)) return t
            return valueFromCopy(bytes, off, len).also {
                if (runtime != null) runtime.recent_short_strings[bucket] = it
            }
        }

        /** Construct a new LuaString using a copy of the bytes array supplied  */

        private fun valueFromCopy(bytes: ByteArray, off: Int, len: Int): LuaString {
            val copy = ByteArray(len)
            for (i in 0 until len) copy[i] = bytes[off + i]
            return LuaString(copy, 0, len)
        }

        /** Construct a [LuaString] around, possibly using the the supplied
         * byte array as the backing store.
         *
         *
         * The caller must ensure that the array is not mutated after the call.
         * However, if the string is short enough the short-string cache is checked
         * for a match which may be used instead of the supplied byte array.
         *
         *
         * @param bytes byte buffer
         * @return [LuaString] wrapping the byte buffer, or an equivalent string.
         */
        @JvmOverloads

        fun valueUsing(bytes: ByteArray, off: Int = 0, len: Int = bytes.size, runtime: LuaRuntime? = LuaRuntime.default): LuaString {
            if (bytes.size > RECENT_STRINGS_MAX_LENGTH)
                return LuaString(bytes, off, len)
            val hash = hashCode(bytes, off, len)
            val bucket = hash and RECENT_STRINGS_CACHE_SIZE - 1
            val t = runtime?.recent_short_strings?.get(bucket)
            if (t != null && t.m_hashcode == hash && t.byteseq(bytes, off, len)) return t
            return LuaString(bytes, off, len).also {
                if (runtime != null) {
                    runtime.recent_short_strings[bucket] = it
                }
            }
        }

        /** Construct a [LuaString] using the supplied characters as byte values.
         *
         *
         * Only the low-order 8-bits of each character are used, the remainder is ignored.
         *
         *
         * This is most useful for constructing byte sequences that do not conform to UTF8.
         * @param bytes array of char, whose values are truncated at 8-bits each and put into a byte array.
         * @return [LuaString] wrapping a copy of the byte buffer
         */
        @JvmOverloads

        fun valueOf(bytes: CharArray, off: Int = 0, len: Int = bytes.size): LuaString {
            val b = ByteArray(len)
            for (i in 0 until len)
                b[i] = bytes[i + off].toByte()
            return valueUsing(b, 0, len)
        }

        /** Construct a [LuaString] for all the bytes in a byte array.
         *
         *
         * The LuaString returned will either be a new LuaString containing a copy
         * of the bytes array, or be an existing LuaString used already having the same value.
         *
         *
         * @param bytes byte buffer
         * @return [LuaString] wrapping the byte buffer
         */
        @JvmName("valueOf2")

        fun valueOf(bytes: ByteArray): LuaString {
            return valueOf(bytes, 0, bytes.size)
        }

        /** Compute the hash code of a sequence of bytes within a byte array using
         * lua's rules for string hashes.  For long strings, not all bytes are hashed.
         * @param bytes  byte array containing the bytes.
         * @param offset  offset into the hash for the first byte.
         * @param length number of bytes starting with offset that are part of the string.
         * @return hash for the string defined by bytes, offset, and length.
         */

        fun hashCode(bytes: ByteArray, offset: Int, length: Int): Int {
            var h = length  /* seed */
            val step = (length shr 5) + 1  /* if string is too long, don't hash all its chars */
            var l1 = length
            while (l1 >= step) {
                /* compute hash */
                h = h xor (h shl 5) + (h shr 2) + (bytes[offset + l1 - 1].toInt() and 0x0FF)
                l1 -= step
            }
            return h
        }


        fun equals(a: LuaString, i: Int, b: LuaString, j: Int, n: Int): Boolean {
            return equals(a.m_bytes, a.m_offset + i, b.m_bytes, b.m_offset + j, n)
        }


        fun equals(a: ByteArray, i: Int, b: ByteArray, j: Int, n: Int): Boolean {
            var i = i
            var j = j
            var n = n
            if (a.size < i + n || b.size < j + n) return false
            while (--n >= 0) if (a[i++] != b[j++]) return false
            return true
        }


        /**
         * Convert to Java String interpreting as utf8 characters.
         *
         * @param bytes byte array in UTF8 encoding to convert
         * @param offset starting index in byte array
         * @param length number of bytes to convert
         * @return Java String corresponding to the value of bytes interpreted using UTF8
         * @see .lengthAsUtf8
         * @see .encodeToUtf8
         * @see .isValidUtf8
         */

        fun decodeAsUtf8(bytes: ByteArray, offset: Int, length: Int): String {
            var i: Int
            var j: Int
            var n: Int
            var b: Int
            i = offset
            j = offset + length
            n = 0
            while (i < j) {
                when (0xE0 and bytes[i++].toInt()) {
                    0xE0 -> {
                        ++i
                        ++i
                    }
                    0xC0 -> ++i
                }
                ++n
            }
            val chars = CharArray(n)
            i = offset
            j = offset + length
            n = 0
            while (i < j) {
                chars[n++] = (if ((run { b = bytes[i++].toInt(); b }) >= 0 || i >= j)
                    b
                else if (b < -32 || i + 1 >= j)
                    b and 0x3f shl 6 or (bytes[i++].toInt() and 0x3f)
                else
                    b and 0xf shl 12 or (bytes[i++].toInt() and 0x3f shl 6) or (bytes[i++].toInt() and 0x3f)).toChar()
            }
            return chars.concatToString()
        }

        /**
         * Count the number of bytes required to encode the string as UTF-8.
         * @param chars Array of unicode characters to be encoded as UTF-8
         * @return count of bytes needed to encode using UTF-8
         * @see .encodeToUtf8
         * @see .decodeAsUtf8
         * @see .isValidUtf8
         */

        fun lengthAsUtf8(chars: CharArray): Int {
            var i: Int
            var c: Char
            var b = chars.size
            i = b
            while (--i >= 0) if ((run { c = chars[i]; c }).toInt() >= 0x80) b += if (c.toInt() >= 0x800) 2 else 1
            return b
        }

        /**
         * Encode the given Java string as UTF-8 bytes, writing the result to bytes
         * starting at offset.
         *
         *
         * The string should be measured first with lengthAsUtf8
         * to make sure the given byte array is large enough.
         * @param chars Array of unicode characters to be encoded as UTF-8
         * @param nchars Number of characters in the array to convert.
         * @param bytes byte array to hold the result
         * @param off offset into the byte array to start writing
         * @return number of bytes converted.
         * @see .lengthAsUtf8
         * @see .decodeAsUtf8
         * @see .isValidUtf8
         */

        fun encodeToUtf8(chars: CharArray, nchars: Int, bytes: ByteArray, off: Int): Int {
            var c: Char
            var j = off
            for (i in 0 until nchars) {
                when {
                    (run { c = chars[i]; c }).toInt() < 0x80 -> bytes[j++] = c.toByte()
                    c.toInt() < 0x800 -> {
                        bytes[j++] = (0xC0 or (c.toInt() shr 6 and 0x1f)).toByte()
                        bytes[j++] = (0x80 or (c.toInt() and 0x3f)).toByte()
                    }
                    else -> {
                        bytes[j++] = (0xE0 or (c.toInt() shr 12 and 0x0f)).toByte()
                        bytes[j++] = (0x80 or (c.toInt() shr 6 and 0x3f)).toByte()
                        bytes[j++] = (0x80 or (c.toInt() and 0x3f)).toByte()
                    }
                }
            }
            return j - off
        }
    }
}
/** Construct a [LuaString] using the supplied characters as byte values.
 *
 *
 * Only the low-order 8-bits of each character are used, the remainder is ignored.
 *
 *
 * This is most useful for constructing byte sequences that do not conform to UTF8.
 * @param bytes array of char, whose values are truncated at 8-bits each and put into a byte array.
 * @return [LuaString] wrapping a copy of the byte buffer
 */
/** Construct a [LuaString] for all the bytes in a byte array, possibly using
 * the supplied array as the backing store.
 *
 *
 * The LuaString returned will either be a new LuaString containing the byte array,
 * or be an existing LuaString used already having the same value.
 *
 *
 * The caller must not mutate the contents of the byte array after this call, as
 * it may be used elsewhere due to recent short string caching.
 * @param bytes byte buffer
 * @return [LuaString] wrapping the byte buffer
 */

@ThreadLocal
private var LuaString_metatable: LuaValue? = null




© 2015 - 2024 Weber Informatics LLC | Privacy Policy