OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / smol-toml / dist / primitive.js

237 lines · javascript ref:
/*!
 * Copyright (c) Squirrel Chat et al., All rights reserved.
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
import { TomlDate } from './date.js';
import { TomlError } from './error.js';
// let CTRL_REGEX = /[\x00-\x08\x0f-\x1f\x7f]/
let INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
let FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
let LEADING_ZERO = /^[+-]?0[0-9_]/;
export function parseString(str, ptr) {
    let c = str[ptr++];
    let first = c;
    let isLiteral = c === "'";
    let isMultiline = c === str[ptr] && c === str[ptr + 1];
    if (isMultiline) {
        // Trim initial newline
        if (str[ptr += 2] === '\n')
            ptr++;
        else if (str[ptr] === '\r' && str[ptr + 1] === '\n')
            ptr += 2;
    }
    /*
    The fast path does not seem to bring significant performance gains, so it's commented out.
    Kept for reference and/or future fafoing.

    Without: spec  5.08 µs/iter    3.88 ipc (99.44% cache)   23.90 branch misses   28.61k cycles    111.01k instructions
             5MB   115.73 ms/iter  2.51 ipc (98.36% cache)   3.12M branch misses   619.30M cycles   1.56G instructions

    With:    spec  5.09 µs/iter    3.90 ipc (99.46% cache)   24.42 branch misses   28.57k cycles    111.49k instructions
             5MB   113.89 ms/iter  2.47 ipc (98.38% cache)   3.12M branch misses   611.94M cycles   1.51G instructions

    if (c === "'") {
        // Literal strings fast path - no transform needs to occur; just grab the str and that's it
        let endPtr = str.indexOf(isMultiline ? "'''" : "'", ptr)
        if (endPtr < 0) {
            throw new TomlError("unfinished string literal", { toml: str, ptr })
        }

        if (isMultiline) {
            // If the string ends with 4-5 quotes, then the first 1-2 are part of the string
            if (str[endPtr + 3] === "'") endPtr++
            if (str[endPtr + 3] === "'") endPtr++
        }

        let string = str.slice(ptr, endPtr)
        if (CTRL_REGEX.test(string)) {
            let match = string.match(CTRL_REGEX)!
            throw new TomlError('control characters are not allowed in strings', { toml: str, ptr: ptr + (match.index ?? 0) })
        }
        return [string, endPtr + (isMultiline ? 3 : 1)]
    }
    */
    let parsed = '';
    let sliceStart = ptr;
    // states:
    //   0 - decoding
    //   1 - decoding escape
    //   2 - whitespace escape (no newline encountered yet, must fail on non-whitespace)
    //   3 - whitespace escape (newline encountered, allowed to transition back to normal decode)
    let state = 0;
    for (let i = ptr; i < str.length; i++) {
        c = str[i];
        // Deal with newlines first, since that simplifies control character checking and handling across all states
        if (isMultiline && (c === '\n' || (c === '\r' && str[i + 1] === '\n'))) {
            state = state && 3;
        }
        // Control characters are banned in TOML, so we throw an error if we encounter them
        else if ((c < '\x20' && c !== '\t') || c === '\x7f') {
            throw new TomlError('control characters are not allowed in strings', {
                toml: str,
                ptr: i,
            });
        }
        // The string might terminate while we're parsing through a newline escape.
        // It must have encountered a newline; otherwise, it'll simply fail in another branch.
        else if ((!state || state === 3) && c === first && (!isMultiline || (str[i + 1] === first && str[i + 2] === first))) {
            if (isMultiline) {
                // If the string ends with 4-5 quotes, then the first 1-2 are part of the string
                if (str[i + 3] === first)
                    i++;
                if (str[i + 3] === first)
                    i++;
            }
            return [
                // If we're in a newline escape still, then there's nothing to add.
                // Also try to avoid concat if there's nothing to add to parsed, or nothing has been added to parsed.
                state ? parsed : parsed + str.slice(sliceStart, i),
                i + (isMultiline ? 3 : 1),
            ];
        }
        else if (!state) {
            if (!isLiteral && c === '\\') {
                parsed += str.slice(sliceStart, (sliceStart = i));
                state = 1;
            }
        }
        else if (state === 1) {
            if (c === 'x' || c === 'u' || c === 'U') { // Unicode escape
                let value = 0;
                let len = c === 'x' ? 2 : c === 'u' ? 4 : 8;
                for (let j = 0; j < len; j++, i++) {
                    let hex = str.charCodeAt(i + 1);
                    let digit = 
                    /* 0-9 */ hex >= 0x30 && hex <= 0x39 ? hex - 0x30 :
                        /* A-F */ hex >= 0x41 && hex <= 0x46 ? hex - 0x41 + 10 :
                            /* a-f */ hex >= 0x61 && hex <= 0x66 ? hex - 0x61 + 10 : -1;
                    if (digit < 0)
                        throw new TomlError('invalid non-hex character in unicode escape', { toml: str, ptr: i + 1 });
                    value = (value << 4) | digit;
                }
                // Because JS does bitwise on signed 32bit integers, all 0xfzzzzzzz values are actually seen as negative
                if (value < 0 || value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff)) {
                    throw new TomlError('invalid unicode escape', { toml: str, ptr: i });
                }
                parsed += String.fromCodePoint(value);
                sliceStart = i + 1;
                state = 0;
            }
            else if (c === ' ' || c === '\t') { // If it was a newline, it'd have been handled earlier
                state = 2;
            }
            else {
                if (c === 'b')
                    parsed += '\b';
                else if (c === 't')
                    parsed += '\t';
                else if (c === 'n')
                    parsed += '\n';
                else if (c === 'f')
                    parsed += '\f';
                else if (c === 'r')
                    parsed += '\r';
                else if (c === 'e')
                    parsed += '\x1b';
                else if (c === '"')
                    parsed += '"';
                else if (c === '\\')
                    parsed += '\\';
                else
                    throw new TomlError('unrecognized escape sequence', { toml: str, ptr: i });
                sliceStart = i + 1;
                state = 0;
            }
        }
        else if (c !== ' ' && c !== '\t') {
            if (state === 2) {
                throw new TomlError('invalid escape: only line-ending whitespace may be escaped', {
                    toml: str,
                    ptr: sliceStart,
                });
            }
            // State cannot be zero, or we'd have branched earlier already.
            // If it's a backslash, immediately transition to the escape state so it can be processed.
            state = !isLiteral && c === '\\' ? 1 : 0;
            sliceStart = i;
        }
    }
    throw new TomlError('unfinished string', { toml: str, ptr });
}
export function parseValue(value, toml, ptr, integersAsBigInt) {
    // Constant values
    if (value === 'true')
        return true;
    if (value === 'false')
        return false;
    if (value === '-inf')
        return -Infinity;
    if (value === 'inf' || value === '+inf')
        return Infinity;
    if (value === 'nan' || value === '+nan' || value === '-nan')
        return NaN;
    // Avoid FP representation of -0
    if (value === '-0')
        return integersAsBigInt ? 0n : 0;
    // Numbers
    let isInt = INT_REGEX.test(value);
    if (isInt || FLOAT_REGEX.test(value)) {
        if (LEADING_ZERO.test(value)) {
            throw new TomlError('leading zeroes are not allowed', {
                toml: toml,
                ptr: ptr,
            });
        }
        value = value.replace(/_/g, '');
        let numeric = +value;
        if (isNaN(numeric)) {
            throw new TomlError('invalid number', {
                toml: toml,
                ptr: ptr,
            });
        }
        if (isInt) {
            if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
                throw new TomlError('integer value cannot be represented losslessly', {
                    toml: toml,
                    ptr: ptr,
                });
            }
            if (isInt || integersAsBigInt === true)
                numeric = BigInt(value);
        }
        return numeric;
    }
    const date = new TomlDate(value);
    if (!date.isValid()) {
        throw new TomlError('invalid value', {
            toml: toml,
            ptr: ptr,
        });
    }
    return date;
}
//# sourceMappingURL=primitive.js.map