IOweb/node_modules/smol-toml/dist/primitive.js
2026-07-03 15:07:38 -05:00

237 lines
No EOL
10 KiB
JavaScript

/*!
* 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