UNPKG

yini-parser

Version:

Node.js parser for YINI — a clean, structured INI alternative with types, simple section nesting, comments, and strict mode.

142 lines (140 loc) 5.9 kB
"use strict"; /** * This file contains specific YINI helper functions (utils). * @note More general helper functions should go into the dir "src/utils/". */ Object.defineProperty(exports, "__esModule", { value: true }); exports.isValidBacktickedIdent = exports.isValidSimpleIdent = exports.stripCommentsAndAfter = exports.isMarkerCharacter = void 0; const print_1 = require("./utils/print"); const string_1 = require("./utils/string"); const SECTION_MARKER1 = '^'; const SECTION_MARKER2 = '<'; const SECTION_MARKER3 = '\u00A7'; // Section sign §. const SECTION_MARKER4 = '\u20AC'; // Euro sign €. /** * Check if the character is a section marker character. * @param character A character in a string. * @note The string must be of length 1. * @throws Will throw if not exactly of length 1. */ const isMarkerCharacter = (character) => { if (character.length !== 1) { throw Error('Argument into function isMarkerCharacter(..) is not of length 1'); } const ch = character; if (ch === SECTION_MARKER1 || ch === SECTION_MARKER2 || ch === SECTION_MARKER3 || ch === SECTION_MARKER4) { return true; } return false; }; exports.isMarkerCharacter = isMarkerCharacter; /** * @returns Returns the beginning up to (but not including) any comments * starting with //, #, ; or --. * @throws Will throw if consisting more than 1 lines. */ const stripCommentsAndAfter = (line) => { if ((0, string_1.splitLines)(line).length > 1) { throw new Error('Internal error: Detected several row lines in line: >>>' + line + '<<<'); } let idx1 = line.indexOf('//'); let idx2 = line.indexOf('# '); // NOTE: (!) Hash comments requires a WS after the hash! let idx3 = line.indexOf('#\t'); // NOTE: (!) Hash comments requires a WS after the hash! let idx4 = line.indexOf(';'); let idx5 = line.indexOf('--'); if (idx1 < 0) idx1 = Number.MAX_SAFE_INTEGER; if (idx2 < 0) idx2 = Number.MAX_SAFE_INTEGER; if (idx3 < 0) idx3 = Number.MAX_SAFE_INTEGER; if (idx4 < 0) idx4 = Number.MAX_SAFE_INTEGER; if (idx5 < 0) idx5 = Number.MAX_SAFE_INTEGER; // debugPrint('stripCommentsAndAfter(..): idx1 = ' + idx1) // debugPrint('stripCommentsAndAfter(..): idx2 = ' + idx2) // debugPrint('stripCommentsAndAfter(..): idx3 = ' + idx3) // debugPrint('stripCommentsAndAfter(..): idx4 = ' + idx4) // debugPrint('stripCommentsAndAfter(..): idx5 = ' + idx5) const idx = Math.min(idx1, idx2, idx3, idx4, idx5); const resultLine = idx === Number.MAX_SAFE_INTEGER ? line : line.substring(0, idx); (0, print_1.debugPrint)('stripCommentsAndAfter(..), line: >>>' + line + '<<<'); (0, print_1.debugPrint)('stripCommentsAndAfter(..), resultLine: >>>' + resultLine + '<<<'); return resultLine; }; exports.stripCommentsAndAfter = stripCommentsAndAfter; /** * Checks if a string conforms to the identifier rules for section headers and * member key names as defined by the YINI specification, following * the ANTLR4 lexer rule: * IDENT: ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '0'..'9' | '_')* * @throws Will only throw if blank string. * * @satisfies Should satisfy YINI spec 7, chapter: 3.4. Identifiers, Form 1: * Identifier of Simple Form. * @link https://github.com/YINI-lang/YINI-spec/blob/develop/YINI-Specification.md#34-identifiers */ const isValidSimpleIdent = (str) => { if (!str.trim()) { throw Error('Internal error: isValidSimpleIdent(..) received an empty string.'); } // Regex: ^[a-zA-Z_][a-zA-Z0-9_]*$ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(str); }; exports.isValidSimpleIdent = isValidSimpleIdent; /** * Checks if a string is a valid backticked phrase/identifier: * - Wrapped in backticks. * - No raw tabs, newlines, or control characters (U+0000–U+001F), except as * escaped sequences (e.g., \n). * - May contain ordinary spaces. * @note Empty is allowed: ``, as in spec (due to conform with the JSON empty key ""). * @throws Will only throw if missing enclosed backtick(s). * * @satisfies Should satisfy YINI spec 7, chapter: 3.4. Identifiers, Form 2: * Backticked Identifier. * @link https://github.com/YINI-lang/YINI-spec/blob/develop/YINI-Specification.md#34-identifiers */ const isValidBacktickedIdent = (str) => { // if (str.length >= 2 && str.startsWith('`') && str.endsWith('`')) { // // OK, and will let possible raw newlines and tabs pass so they // // are checked further down. // } else { if (!(0, string_1.isEnclosedInBackticks)(str)) { // NOTE: Only missing backtick(s) should throw error! throw Error('Internal error: isValidBacktickedIdent(..) is missing backtick(s) "`".'); } // Get the contents inside backticks. const content = str.slice(1, -1); // Allowed escapes: \n, \r, \t, \\, \` const allowedEscapes = ['n', 'r', 't', '\\', '`']; for (let i = 0; i < content.length; i++) { const ch = content[i]; const code = content.charCodeAt(i); if (ch === '\\') { // If this is an escape, check next char. i++; if (i >= content.length) return false; // Trailing backslash is not valid. const nextCh = content[i]; if (!allowedEscapes.includes(nextCh)) return false; continue; } // Allow space (U+0020), but not other control chars (U+0000–U+001F). if (code < 0x20 && code !== 0x20) return false; // Disallow raw newlines, tabs, carriage returns, and backtick. if (ch === '\n' || ch === '\r' || ch === '\t' || ch === '`') // Raw newlines and tabs will yield false. return false; } return true; }; exports.isValidBacktickedIdent = isValidBacktickedIdent;