@humanwhocodes/momoa
Version:
JSON AST parser, tokenizer, printer, traverser.
1,236 lines (1,019 loc) • 94.2 kB
JavaScript
'use strict';
/**
* @fileoverview Character codes.
* @author Nicholas C. Zakas
*/
const CHAR_0 = 48; // 0
const CHAR_1 = 49; // 1
const CHAR_9 = 57; // 9
const CHAR_BACKSLASH = 92; // \
const CHAR_DOLLAR = 36; // $
const CHAR_DOT = 46; // .
const CHAR_DOUBLE_QUOTE = 34; // "
const CHAR_LOWER_A = 97; // a
const CHAR_LOWER_E = 101; // e
const CHAR_LOWER_F = 102; // f
const CHAR_LOWER_N = 110; // n
const CHAR_LOWER_T = 116; // t
const CHAR_LOWER_U = 117; // u
const CHAR_LOWER_X = 120; // x
const CHAR_LOWER_Z = 122; // z
const CHAR_MINUS = 45; // -
const CHAR_NEWLINE = 10; // newline
const CHAR_PLUS = 43; // +
const CHAR_RETURN = 13; // return
const CHAR_SINGLE_QUOTE = 39; // '
const CHAR_SLASH = 47; // /
const CHAR_SPACE = 32; // space
const CHAR_TAB = 9; // tab
const CHAR_UNDERSCORE = 95; // _
const CHAR_UPPER_A = 65; // A
const CHAR_UPPER_E = 69; // E
const CHAR_UPPER_F = 70; // F
const CHAR_UPPER_N = 78; // N
const CHAR_UPPER_X = 88; // X
const CHAR_UPPER_Z = 90; // Z
const CHAR_LOWER_B = 98; // b
const CHAR_LOWER_R = 114; // r
const CHAR_LOWER_V = 118; // v
const CHAR_LINE_SEPARATOR = 0x2028;
const CHAR_PARAGRAPH_SEPARATOR = 0x2029;
const CHAR_UPPER_I = 73; // I
const CHAR_STAR = 42; // *
const CHAR_VTAB = 11; // U+000B Vertical tab
const CHAR_FORM_FEED = 12; // U+000C Form feed
const CHAR_NBSP = 160; // U+00A0 Non-breaking space
const CHAR_BOM = 65279; // U+FEFF
const CHAR_NON_BREAKING_SPACE = 160;
const CHAR_EN_QUAD = 8192;
const CHAR_EM_QUAD = 8193;
const CHAR_EN_SPACE = 8194;
const CHAR_EM_SPACE = 8195;
const CHAR_THREE_PER_EM_SPACE = 8196;
const CHAR_FOUR_PER_EM_SPACE = 8197;
const CHAR_SIX_PER_EM_SPACE = 8198;
const CHAR_FIGURE_SPACE = 8199;
const CHAR_PUNCTUATION_SPACE = 8200;
const CHAR_THIN_SPACE = 8201;
const CHAR_HAIR_SPACE = 8202;
const CHAR_NARROW_NO_BREAK_SPACE = 8239;
const CHAR_MEDIUM_MATHEMATICAL_SPACE = 8287;
const CHAR_IDEOGRAPHIC_SPACE = 12288;
/**
* @fileoverview JSON syntax helpers
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Types
//-----------------------------------------------------------------------------
/** @typedef {import("./typedefs.js").TokenType} TokenType */
//-----------------------------------------------------------------------------
// Predefined Tokens
//-----------------------------------------------------------------------------
const LBRACKET = "[";
const RBRACKET = "]";
const LBRACE = "{";
const RBRACE = "}";
const COLON = ":";
const COMMA = ",";
const TRUE = "true";
const FALSE = "false";
const NULL = "null";
const NAN$1 = "NaN";
const INFINITY$1 = "Infinity";
const QUOTE = "\"";
const escapeToChar = new Map([
[CHAR_DOUBLE_QUOTE, QUOTE],
[CHAR_BACKSLASH, "\\"],
[CHAR_SLASH, "/"],
[CHAR_LOWER_B, "\b"],
[CHAR_LOWER_N, "\n"],
[CHAR_LOWER_F, "\f"],
[CHAR_LOWER_R, "\r"],
[CHAR_LOWER_T, "\t"]
]);
const json5EscapeToChar = new Map([
...escapeToChar,
[CHAR_LOWER_V, "\v"],
[CHAR_0, "\0"]
]);
const charToEscape = new Map([
[QUOTE, QUOTE],
["\\", "\\"],
["/", "/"],
["\b", "b"],
["\n", "n"],
["\f", "f"],
["\r", "r"],
["\t", "t"]
]);
const json5CharToEscape = new Map([
...charToEscape,
["\v", "v"],
["\0", "0"],
["\u2028", "u2028"],
["\u2029", "u2029"]
]);
/** @type {Map<string,TokenType>} */
const knownTokenTypes = new Map([
[LBRACKET, "LBracket"],
[RBRACKET, "RBracket"],
[LBRACE, "LBrace"],
[RBRACE, "RBrace"],
[COLON, "Colon"],
[COMMA, "Comma"],
[TRUE, "Boolean"],
[FALSE, "Boolean"],
[NULL, "Null"]
]);
/** @type {Map<string,TokenType>} */
const knownJSON5TokenTypes = new Map([
...knownTokenTypes,
[NAN$1, "Number"],
[INFINITY$1, "Number"]
]);
// JSON5
const json5LineTerminators = new Set([
CHAR_NEWLINE,
CHAR_RETURN,
CHAR_LINE_SEPARATOR,
CHAR_PARAGRAPH_SEPARATOR
]);
/**
* @fileoverview JSON tokenization/parsing errors
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Typedefs
//-----------------------------------------------------------------------------
/** @typedef {import("./typedefs.js").Location} Location */
/** @typedef {import("./typedefs.js").Token} Token */
//-----------------------------------------------------------------------------
// Errors
//-----------------------------------------------------------------------------
/**
* Base class that attaches location to an error.
*/
class ErrorWithLocation extends Error {
/**
* Creates a new instance.
* @param {string} message The error message to report.
* @param {Location} loc The location information for the error.
*/
constructor(message, { line, column, offset }) {
super(`${ message } (${ line }:${ column})`);
/**
* The line on which the error occurred.
* @type {number}
*/
this.line = line;
/**
* The column on which the error occurred.
* @type {number}
*/
this.column = column;
/**
* The index into the string where the error occurred.
* @type {number}
*/
this.offset = offset;
}
}
/**
* Error thrown when an unexpected character is found during tokenizing.
*/
class UnexpectedChar extends ErrorWithLocation {
/**
* Creates a new instance.
* @param {number} unexpected The character that was found.
* @param {Location} loc The location information for the found character.
*/
constructor(unexpected, loc) {
super(`Unexpected character '${ String.fromCharCode(unexpected) }' found.`, loc);
}
}
/**
* Error thrown when an unexpected identifier is found during tokenizing.
*/
class UnexpectedIdentifier extends ErrorWithLocation {
/**
* Creates a new instance.
* @param {string} unexpected The character that was found.
* @param {Location} loc The location information for the found character.
*/
constructor(unexpected, loc) {
super(`Unexpected identifier '${ unexpected }' found.`, loc);
}
}
/**
* Error thrown when an unexpected token is found during parsing.
*/
class UnexpectedToken extends ErrorWithLocation {
/**
* Creates a new instance.
* @param {Token} token The token that was found.
*/
constructor(token) {
super(`Unexpected token ${ token.type } found.`, token.loc.start);
}
}
/**
* Error thrown when the end of input is found where it isn't expected.
*/
class UnexpectedEOF extends ErrorWithLocation {
/**
* Creates a new instance.
* @param {Location} loc The location information for the found character.
*/
constructor(loc) {
super("Unexpected end of input found.", loc);
}
}
/*
* The following is extracted from https://github.com/json5/json5/
*
* MIT License
*
* Copyright (c) 2012-2018 Aseem Kishore, and others.
*
* 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.
*/
const ID_Start = /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE83\uDE86-\uDE89\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]/;
// eslint-disable-next-line no-misleading-character-class
const ID_Continue = /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u08D4-\u08E1\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D00-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1CD0-\u1CD2\u1CD4-\u1CF9\u1D00-\u1DF9\u1DFB-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC00-\uDC4A\uDC50-\uDC59\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDE00-\uDE3E\uDE47\uDE50-\uDE83\uDE86-\uDE99\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6\uDD00-\uDD4A\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/;
/**
* @fileoverview A charactor code reader.
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/** @typedef {import("./typedefs.js").Location} Location */
//-----------------------------------------------------------------------------
// Data
//-----------------------------------------------------------------------------
const CHAR_CR = 13; // \r
const CHAR_LF = 10; // \n
//-----------------------------------------------------------------------------
// CharCodeReader
//-----------------------------------------------------------------------------
/**
* A reader that reads character codes from a string.
*/
class CharCodeReader {
/**
* The text to read from.
* @type {string}
*/
#text = "";
/**
* The current line number.
* @type {number}
*/
#line = 1;
/**
* The current column number.
* @type {number}
*/
#column = 0;
/**
* The current offset in the text.
* @type {number}
*/
#offset = -1;
/**
* Whether the last character read was a new line.
* @type {boolean}
*/
#newLine = false;
/**
* The last character code read.
* @type {number}
*/
#last = -1;
/**
* Whether the reader has ended.
* @type {boolean}
*/
#ended = false;
/**
* Creates a new instance.
* @param {string} text The text to read from
*/
constructor(text) {
this.#text = text;
}
/**
* Ends the reader.
* @returns {void}
*/
#end() {
if (this.#ended) {
return;
}
this.#column++;
this.#offset++;
this.#last = -1;
this.#ended = true;
}
/**
* Returns the current position of the reader.
* @returns {Location} An object with line, column, and offset properties.
*/
locate() {
return {
line: this.#line,
column: this.#column,
offset: this.#offset
};
}
/**
* Reads the next character code in the text.
* @returns {number} The next character code, or -1 if there are no more characters.
*/
next() {
if (this.#offset >= this.#text.length - 1) {
this.#end();
return -1;
}
this.#offset++;
const charCode = this.#text.charCodeAt(this.#offset);
if (this.#newLine) {
this.#line++;
this.#column = 1;
this.#newLine = false;
} else {
this.#column++;
}
if (charCode === CHAR_CR) {
this.#newLine = true;
// if we already see a \r, just ignore upcoming \n
if (this.peek() === CHAR_LF) {
this.#offset++;
}
} else if (charCode === CHAR_LF) {
this.#newLine = true;
}
this.#last = charCode;
return charCode;
}
/**
* Peeks at the next character code in the text.
* @returns {number} The next character code, or -1 if there are no more characters.
*/
peek() {
if (this.#offset === this.#text.length - 1) {
return -1;
}
return this.#text.charCodeAt(this.#offset + 1);
}
/**
* Determines if the next character code in the text matches a specific character code.
* @param {(number) => boolean} fn A function to call on the next character.
* @returns {boolean} True if the next character code matches, false if not.
*/
match(fn) {
if (fn(this.peek())) {
this.next();
return true;
}
return false;
}
/**
* Returns the last character code read.
* @returns {number} The last character code read.
*/
current() {
return this.#last;
}
}
/**
* @fileoverview JSON tokenizer
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Typedefs
//-----------------------------------------------------------------------------
/** @typedef {import("./typedefs.js").Location} Location */
/** @typedef {import("./typedefs.js").Range} Range */
/** @typedef {import("./typedefs.js").Token} Token */
/** @typedef {import("./typedefs.js").TokenType} TokenType */
/** @typedef {import("./typedefs.js").TokenizeOptions} TokenizeOptions */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const INFINITY = "Infinity";
const NAN = "NaN";
const keywordStarts = new Set([CHAR_LOWER_T, CHAR_LOWER_F, CHAR_LOWER_N]);
const whitespace = new Set([CHAR_SPACE, CHAR_TAB, CHAR_NEWLINE, CHAR_RETURN]);
const json5Whitespace = new Set([
...whitespace,
CHAR_VTAB,
CHAR_FORM_FEED,
CHAR_NBSP,
CHAR_LINE_SEPARATOR,
CHAR_PARAGRAPH_SEPARATOR,
CHAR_BOM,
CHAR_NON_BREAKING_SPACE,
CHAR_EN_QUAD,
CHAR_EM_QUAD,
CHAR_EN_SPACE,
CHAR_EM_SPACE,
CHAR_THREE_PER_EM_SPACE,
CHAR_FOUR_PER_EM_SPACE,
CHAR_SIX_PER_EM_SPACE,
CHAR_FIGURE_SPACE,
CHAR_PUNCTUATION_SPACE,
CHAR_THIN_SPACE,
CHAR_HAIR_SPACE,
CHAR_NARROW_NO_BREAK_SPACE,
CHAR_MEDIUM_MATHEMATICAL_SPACE,
CHAR_IDEOGRAPHIC_SPACE,
]);
/** @type {TokenizeOptions} */
const DEFAULT_OPTIONS$1 = {
mode: "json",
ranges: false
};
const jsonKeywords = new Set(["true", "false", "null"]);
const tt = {
EOF: 0,
Number: 1,
String: 2,
Boolean: 3,
Null: 4,
NaN: 5,
Infinity: 6,
Identifier: 7,
Colon: 20,
LBrace: 21,
RBrace: 22,
LBracket: 23,
RBracket: 24,
Comma: 25,
LineComment: 40,
BlockComment: 41
};
// #region Helpers
/**
* Determines if a given character is a decimal digit.
* @param {number} c The character to check.
* @returns {boolean} `true` if the character is a digit.
*/
function isDigit(c) {
return c >= CHAR_0 && c <= CHAR_9;
}
/**
* Determines if a given character is a hexadecimal digit.
* @param {number} c The character to check.
* @returns {boolean} `true` if the character is a hexadecimal digit.
*/
function isHexDigit(c) {
return isDigit(c) ||
c >= CHAR_UPPER_A && c <= CHAR_UPPER_F ||
c >= CHAR_LOWER_A && c <= CHAR_LOWER_F;
}
/**
* Determines if a given character is a positive digit (1-9).
* @param {number} c The character to check.
* @returns {boolean} `true` if the character is a positive digit.
*/
function isPositiveDigit(c) {
return c >= CHAR_1 && c <= CHAR_9;
}
/**
* Determines if a given character is the start of a keyword.
* @param {number} c The character to check.
* @returns {boolean} `true` if the character is the start of a keyword.
*/
function isKeywordStart(c) {
return keywordStarts.has(c);
}
/**
* Determines if a given character is the start of a number.
* @param {number} c The character to check.
* @returns {boolean} `true` if the character is the start of a number.
*/
function isNumberStart(c) {
return isDigit(c) || c === CHAR_DOT || c === CHAR_MINUS;
}
/**
* Determines if a given character is the start of a JSON5 number.
* @param {number} c The character to check.
* @returns {boolean} `true` if the character is the start of a JSON5 number.
*/
function isJSON5NumberStart(c) {
return isNumberStart(c) || c === CHAR_PLUS;
}
/**
* Determines if a given character is the start of a string.
* @param {number} c The character to check.
* @param {boolean} json5 `true` if JSON5 mode is enabled.
* @returns {boolean} `true` if the character is the start of a string.
*/
function isStringStart(c, json5) {
return c === CHAR_DOUBLE_QUOTE || (json5 && c === CHAR_SINGLE_QUOTE);
}
/**
* Tests that a given character is a valid first character of a
* JSON5 identifier
* @param {number} c The character to check.
* @returns {boolean} `true` if the character is a valid first character.
*/
function isJSON5IdentifierStart(c) {
// test simple cases first
if (c === CHAR_DOLLAR || c === CHAR_UNDERSCORE || c === CHAR_BACKSLASH) {
return true;
}
if (c >= CHAR_LOWER_A && c <= CHAR_LOWER_Z || c >= CHAR_UPPER_A && c <= CHAR_UPPER_Z) {
return true;
}
if (c === 0x200C || c === 0x200D) {
return true;
}
const ct = String.fromCharCode(c);
return ID_Start.test(ct);
}
/**
* Tests that a given character is a valid part of a JSON5 identifier.
* @param {number} c The character to check.
* @returns {boolean} `true` if the character is a valid part of an identifier.
*/
function isJSON5IdentifierPart(c) {
// fast path for simple cases
if (isJSON5IdentifierStart(c) || isDigit(c)) {
return true;
}
const ct = String.fromCharCode(c);
return ID_Continue.test(ct);
}
// #endregion
class Tokenizer {
/**
* Options for the tokenizer.
* @type {TokenizeOptions}
*/
#options;
/**
* The source text to tokenize.
* @type {string}
*/
#text;
/**
* The reader for the source text.
* @type {CharCodeReader}
*/
#reader;
/**
* Indicates if the tokenizer is in JSON5 mode.
* @type {boolean}
*/
#json5;
/**
* Indicates if comments are allowed.
* @type {boolean}
*/
#allowComments;
/**
* Indicates if ranges should be included in the tokens.
* @type {boolean}
*/
#ranges;
/**
* The last token type read.
* @type {Token}
*/
#token;
/**
* Determines if a character is an escaped character.
* @type {(c:number) => boolean}
*/
#isEscapedCharacter;
/**
* Determines if a character is a JSON5 line terminator.
* @type {(c:number) => boolean}
*/
#isJSON5LineTerminator;
/**
* Determines if a character is a JSON5 hex escape.
* @type {(c:number) => boolean}
*/
#isJSON5HexEscape;
/**
* Determines if a character is whitespace.
* @type {(c:number) => boolean}
*/
#isWhitespace;
/**
* Creates a new instance of the tokenizer.
* @param {string} text The source text
* @param {TokenizeOptions} [options] Options for the tokenizer.
*/
constructor(text, options) {
this.#text = text;
this.#options = {
...DEFAULT_OPTIONS$1,
...options
};
this.#reader = new CharCodeReader(text);
this.#json5 = this.#options.mode === "json5";
this.#allowComments = this.#options.mode !== "json";
this.#ranges = this.#options.ranges;
// TODO: Clean this up
this.#isEscapedCharacter = this.#json5 ? json5EscapeToChar.has.bind(json5EscapeToChar) : escapeToChar.has.bind(escapeToChar);
this.#isJSON5LineTerminator = this.#json5 ? json5LineTerminators.has.bind(json5LineTerminators) : () => false;
this.#isJSON5HexEscape = this.#json5 ? c => c === CHAR_LOWER_X : () => false;
this.#isWhitespace = this.#json5 ? json5Whitespace.has.bind(json5Whitespace) : whitespace.has.bind(whitespace);
}
// #region Errors
/**
* Convenience function for throwing unexpected character errors.
* @param {number} c The unexpected character.
* @param {Location} [loc] The location of the unexpected character.
* @returns {never}
* @throws {UnexpectedChar} always.
*/
#unexpected(c, loc = this.#reader.locate()) {
throw new UnexpectedChar(c, loc);
}
/**
* Convenience function for throwing unexpected identifier errors.
* @param {string} identifier The unexpected identifier.
* @param {Location} [loc] The location of the unexpected identifier.
* @returns {never}
* @throws {UnexpectedIdentifier} always.
*/
#unexpectedIdentifier(identifier, loc = this.#reader.locate()) {
throw new UnexpectedIdentifier(identifier, loc);
}
/**
* Convenience function for throwing unexpected EOF errors.
* @returns {never}
* @throws {UnexpectedEOF} always.
*/
#unexpectedEOF() {
throw new UnexpectedEOF(this.#reader.locate());
}
// #endregion
// #region Helpers
/**
* Creates a new token.
* @param {TokenType} tokenType The type of token to create.
* @param {number} length The length of the token.
* @param {Location} startLoc The start location for the token.
* @param {Location} [endLoc] The end location for the token.
* @returns {Token} The token.
*/
#createToken(tokenType, length, startLoc, endLoc) {
const endOffset = startLoc.offset + length;
let range = this.#options.ranges ? {
range: /** @type {Range} */ ([startLoc.offset, endOffset])
} : undefined;
return {
type: tokenType,
loc: {
start: startLoc,
end: endLoc || {
line: startLoc.line,
column: startLoc.column + length,
offset: endOffset
}
},
...range
};
}
/**
* Reads in a specific number of hex digits.
* @param {number} count The number of hex digits to read.
* @returns {string} The hex digits read.
*/
#readHexDigits(count) {
let value = "";
let c;
for (let i = 0; i < count; i++) {
c = this.#reader.peek();
if (isHexDigit(c)) {
this.#reader.next();
value += String.fromCharCode(c);
continue;
}
this.#unexpected(c);
}
return value;
}
/**
* Reads in a JSON5 identifier. Also used for JSON but we validate
* the identifier later.
* @param {number} c The first character of the identifier.
* @returns {string} The identifier read.
* @throws {UnexpectedChar} when the identifier cannot be read.
*/
#readIdentifier(c) {
let value = "";
do {
value += String.fromCharCode(c);
if (c === CHAR_BACKSLASH) {
c = this.#reader.next();
if (c !== CHAR_LOWER_U) {
this.#unexpected(c);
}
value += String.fromCharCode(c);
const hexDigits = this.#readHexDigits(4);
// check for a valid character code
const charCode = parseInt(hexDigits, 16);
if (value.length === 2 && !isJSON5IdentifierStart(charCode)) {
const loc = this.#reader.locate();
this.#unexpected(CHAR_BACKSLASH, { line: loc.line, column: loc.column - 5, offset: loc.offset - 5 });
} else if (!isJSON5IdentifierPart(charCode)) {
const loc = this.#reader.locate();
this.#unexpected(charCode, { line: loc.line, column: loc.column - 5, offset: loc.offset - 5 });
}
value += hexDigits;
}
c = this.#reader.peek();
if (!isJSON5IdentifierPart(c)) {
break;
}
this.#reader.next();
} while (true); // eslint-disable-line no-constant-condition
return value;
}
/**
* Reads in a string. Works for both JSON and JSON5.
* @param {number} c The first character of the string (either " or ').
* @returns {number} The length of the string.
* @throws {UnexpectedChar} when the string cannot be read.
* @throws {UnexpectedEOF} when EOF is reached before the string is finalized.
*/
#readString(c) {
const delimiter = c;
let length = 1;
c = this.#reader.peek();
while (c !== -1 && c !== delimiter) {
this.#reader.next();
length++;
// escapes
if (c === CHAR_BACKSLASH) {
c = this.#reader.peek();
if (this.#isEscapedCharacter(c) || this.#isJSON5LineTerminator(c)) {
this.#reader.next();
length++;
} else if (c === CHAR_LOWER_U) {
this.#reader.next();
length++;
const result = this.#readHexDigits(4);
length += result.length;
} else if (this.#isJSON5HexEscape(c)) {
this.#reader.next();
length++;
// hex escapes: \xHH
const result = this.#readHexDigits(2);
length += result.length;
} else if (this.#json5) { // JSON doesn't allow anything else
this.#reader.next();
length++;
} else {
this.#unexpected(c);
}
}
c = this.#reader.peek();
}
if (c === -1) {
this.#reader.next();
this.#unexpectedEOF();
}
// c is the delimiter
this.#reader.next();
length++;
return length;
}
/**
* Reads a number. Works for both JSON and JSON5.
* @param {number} c The first character of the number.
* @returns {number} The length of the number.
* @throws {UnexpectedChar} when the number cannot be read.
* @throws {UnexpectedEOF} when EOF is reached before the number is finalized.
*/
#readNumber(c) {
// we've already read the first character
let length = 1;
// JSON number may start with a minus but not a plus
// JSON5 allows a plus.
if (c === CHAR_MINUS || this.#json5 && c === CHAR_PLUS) {
c = this.#reader.peek();
/*
* JSON5 allows Infinity or NaN preceded by a sign.
* This blocks handles +Infinity, -Infinity, +NaN, and -NaN.
* Standalone Infinity and NaN are handled in `readJSON5Identifier()`
*/
if (this.#json5) {
if (c === CHAR_UPPER_I || c === CHAR_UPPER_N) {
this.#reader.next();
const identifier = this.#readIdentifier(c);
if (identifier !== INFINITY && identifier !== NAN) {
this.#unexpected(c);
}
return length + identifier.length;
}
}
// Next digit cannot be zero
if (!isDigit(c)) {
this.#unexpected(c);
}
// if we made it here, we need to continue on, so register the character
this.#reader.next();
length++;
}
/*
* In JSON, a zero must be followed by a decimal point or nothing.
* In JSON5, a zero can additionally be followed by an `x` indicating
* that it's a hexadecimal number.
*/
if (c === CHAR_0) {
// c = this.#reader.next();
// length++;
c = this.#reader.peek();
// check for a hex number
if (this.#json5 && (c === CHAR_LOWER_X || c === CHAR_UPPER_X)) {
this.#reader.next();
length++;
c = this.#reader.peek();
if (!isHexDigit(c)) {
this.#reader.next();
this.#unexpected(c);
}
do {
this.#reader.next();
length++;
c = this.#reader.peek();
} while (isHexDigit(c));
} else if (isDigit(c)) {
this.#unexpected(c);
}
} else {
// JSON5 allows leading decimal points
if (!this.#json5 || c !== CHAR_DOT) {
if (!isPositiveDigit(c)) {
this.#unexpected(c);
}
c = this.#reader.peek();
while (isDigit(c)) {
this.#reader.next();
length++;
c = this.#reader.peek();
}
}
}
/*
* In JSON, a decimal point must be followed by at least one digit.
* In JSON5, a decimal point need not be followed by any digits.
*/
if (c === CHAR_DOT) {
let digitCount = -1;
this.#reader.next();
length++;
digitCount++;
c = this.#reader.peek();
while (isDigit(c)) {
this.#reader.next();
length++;
digitCount++;
c = this.#reader.peek();
}
if (!this.#json5 && digitCount === 0) {
this.#reader.next();
if (c) {
this.#unexpected(c);
} else {
this.#unexpectedEOF();
}
}
}
// Exponent is always last
if (c === CHAR_LOWER_E || c === CHAR_UPPER_E) {
this.#reader.next();
length++;
c = this.#reader.peek();
if (c === CHAR_PLUS || c === CHAR_MINUS) {
this.#reader.next();
length++;
c = this.#reader.peek();
}
/*
* Must always have a digit in this position to avoid:
* 5e
* 12E+
* 42e-
*/
if (c === -1) {
this.#reader.next();
this.#unexpectedEOF();
}
if (!isDigit(c)) {
this.#reader.next();
this.#unexpected(c);
}
while (isDigit(c)) {
this.#reader.next();
length++;
c = this.#reader.peek();
}
}
return length;
}
/**
* Reads a comment. Works for both JSON and JSON5.
* @param {number} c The first character of the comment.
* @returns {{length: number, multiline: boolean}} The length of the comment, and whether the comment is multi-line.
* @throws {UnexpectedChar} when the comment cannot be read.
* @throws {UnexpectedEOF} when EOF is reached before the comment is finalized.
*/
#readComment(c) {
let length = 1;
// next character determines single- or multi-line
c = this.#reader.peek();
// single-line comments
if (c === CHAR_SLASH) {
do {
this.#reader.next();
length += 1;
c = this.#reader.peek();
} while (c > -1 && c !== CHAR_RETURN && c !== CHAR_NEWLINE);
return { length, multiline: false };
}
// multi-line comments
if (c === CHAR_STAR) {
this.#reader.next();
length += 1;
while (c > -1) {
c = this.#reader.peek();
// check for end of comment
if (c === CHAR_STAR) {
this.#reader.next();
length += 1;
c = this.#reader.peek();
//end of comment
if (c === CHAR_SLASH) {
this.#reader.next();
length += 1;
return { length, multiline: true };
}
} else {
this.#reader.next();
length += 1;
}
}
this.#reader.next();
this.#unexpectedEOF();
}
// if we've made it here, there's an invalid character
this.#reader.next();
this.#unexpected(c);
}
// #endregion
/**
* Returns the next token in the source text.
* @returns {number} The code for the next token.
*/
next() {
let c = this.#reader.next();
while (this.#isWhitespace(c)) {
c = this.#reader.next();
}
if (c === -1) {
return tt.EOF;
}
const start = this.#reader.locate();
const ct = String.fromCharCode(c);
// check for JSON5 syntax only
if (this.#json5) {
if (knownJSON5TokenTypes.has(ct)) {
this.#token = this.#createToken(knownJSON5TokenTypes.get(ct), 1, start);
} else if (isJSON5IdentifierStart(c)) {
const value = this.#readIdentifier(c);
if (knownJSON5TokenTypes.has(value)) {
this.#token = this.#createToken(knownJSON5TokenTypes.get(value), value.length, start);
} else {
this.#token = this.#createToken("Identifier", value.length, start);
}
} else if (isJSON5NumberStart(c)) {
const result = this.#readNumber(c);
this.#token = this.#createToken("Number", result, start);
} else if (isStringStart(c, this.#json5)) {
const result = this.#readString(c);
const lastCharLoc = this.#reader.locate();
this.#token = this.#createToken("String", result, start, {
line: lastCharLoc.line,
column: lastCharLoc.column + 1,
offset: lastCharLoc.offset + 1
});
} else if (c === CHAR_SLASH && this.#allowComments) {
const result = this.#readComment(c);
const lastCharLoc = this.#reader.locate();
this.#token = this.#createToken(!result.multiline ? "LineComment" : "BlockComment", result.length, start, {
line: lastCharLoc.line,
column: lastCh