Source: accessors/string-accessor.js

/**
 * @author Toru Nagashima
 * @copyright 2016 Toru Nagashima. All rights reserved.
 * See LICENSE file in root directory for full license.
 */
"use strict"

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const assert = require("../assert")
const getTextEncoder = require("../text-encoder-registry").getTextEncoder
const Accessor = require("./accessor").Accessor

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const instances = new Map()

/**
 * The field accessor for stringN fields.
 *
 * @private
 */
class StringAccessor extends Accessor {
    /**
     * @param {string} encoding - The encoding of strings to access.
     * @param {byteLength} byteLength - The maximum length of strings.
     */
    constructor(encoding, byteLength) {
        const encoder = getTextEncoder()
        assert(
            encoder != null,
            "'bre.setTextEncoder()' is required before a use of string type."
        )
        assert.string(encoding, "encoding")
        assert(
            encoder.encodingExists(encoding),
            "'encoding' should be a valid encoding type, " +
            `but got ${JSON.stringify(encoding)}.`
        )
        assert.integer(byteLength, "byteLength")
        assert.gte(byteLength, 1, "byteLength")
        super(`string$${encoding}$${byteLength}`, byteLength << 3)

        this.encoding = JSON.stringify(encoding)
        this.byteLength = byteLength
    }

    /**
     * Gets the getter/setter pair logic for this accessor.
     *
     * @param {string} name - The property name to access.
     * @param {string} bitOffset - The data position to access.
     * @returns {string} The getter/setter code of the property.
     */
    propertyCode(name, bitOffset) {
        assert.numberOrString(name, "name")
        assert.bitOffsetX8(bitOffset, this.name)

        const byteOffset = bitOffset >> 3
        const byteLength = this.byteLength
        const encoding = this.encoding

        return `get ${name}() {
        const b = this[s.buffer]
        let i = 0
        for (; i < ${byteLength} && b.getUint8(${byteOffset} + i) !== 0; ++i)
            ;
        return TextEncoder.decode(b, ${byteOffset}, i,${encoding})
    }
    set ${name}(value) {
        assert.string(value, ${JSON.stringify(name)})

        const buffer = this[s.buffer]
        const encoded = TextEncoder.encode(value, ${encoding})
        assert.lte(
            encoded.byteLength,
            ${byteLength},
            "byteLength of " + ${JSON.stringify(name)}
        )

        let i = 0
        for (; i < encoded.byteLength; ++i) {
            buffer.setUint8(${byteOffset} + i, encoded.getUint8(i))
        }
        for (; i < ${byteLength}; ++i) {
            buffer.setUint8(${byteOffset} + i, 0x00)
        }
    }`
    }
}

//------------------------------------------------------------------------------
// Exports
//------------------------------------------------------------------------------

exports.string = (encoding, byteLength) => {
    const key = `string$${encoding}$${byteLength}`

    let accessor = instances.get(key)
    if (accessor == null) {
        accessor = new StringAccessor(encoding, byteLength)
        instances.set(key, accessor)
    }

    return accessor
}