Files
2025-07-24 18:46:24 +02:00

179 lines
6.4 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 { skipVoid } from './util.js';
import { TomlDate } from './date.js';
import { TomlError } from './error.js';
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_]/;
let ESCAPE_REGEX = /^[0-9a-f]{4,8}$/i;
let ESC_MAP = {
b: '\b',
t: '\t',
n: '\n',
f: '\f',
r: '\r',
'"': '"',
'\\': '\\',
};
export function parseString(str, ptr = 0, endPtr = str.length) {
let isLiteral = str[ptr] === '\'';
let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
if (isMultiline) {
endPtr -= 2;
if (str[ptr += 2] === '\r')
ptr++;
if (str[ptr] === '\n')
ptr++;
}
let tmp = 0;
let isEscape;
let parsed = '';
let sliceStart = ptr;
while (ptr < endPtr - 1) {
let c = str[ptr++];
if (c === '\n' || (c === '\r' && str[ptr] === '\n')) {
if (!isMultiline) {
throw new TomlError('newlines are not allowed in strings', {
toml: str,
ptr: ptr - 1,
});
}
}
else if ((c < '\x20' && c !== '\t') || c === '\x7f') {
throw new TomlError('control characters are not allowed in strings', {
toml: str,
ptr: ptr - 1,
});
}
if (isEscape) {
isEscape = false;
if (c === 'u' || c === 'U') {
// Unicode escape
let code = str.slice(ptr, (ptr += (c === 'u' ? 4 : 8)));
if (!ESCAPE_REGEX.test(code)) {
throw new TomlError('invalid unicode escape', {
toml: str,
ptr: tmp,
});
}
try {
parsed += String.fromCodePoint(parseInt(code, 16));
}
catch {
throw new TomlError('invalid unicode escape', {
toml: str,
ptr: tmp,
});
}
}
else if (isMultiline && (c === '\n' || c === ' ' || c === '\t' || c === '\r')) {
// Multiline escape
ptr = skipVoid(str, ptr - 1, true);
if (str[ptr] !== '\n' && str[ptr] !== '\r') {
throw new TomlError('invalid escape: only line-ending whitespace may be escaped', {
toml: str,
ptr: tmp,
});
}
ptr = skipVoid(str, ptr);
}
else if (c in ESC_MAP) {
// Classic escape
parsed += ESC_MAP[c];
}
else {
throw new TomlError('unrecognized escape sequence', {
toml: str,
ptr: tmp,
});
}
sliceStart = ptr;
}
else if (!isLiteral && c === '\\') {
tmp = ptr - 1;
isEscape = true;
parsed += str.slice(sliceStart, tmp);
}
}
return parsed + str.slice(sliceStart, endPtr - 1);
}
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;
}