UNPKG

fastparselite

Version:

Super simple & fast DSL parser for lightweight config-like data using custom syntax.

226 lines (191 loc) 6.21 kB
const fs = require('fs'); class LiteDataParser { /** * Parses a .ld file from disk * @param {string} filePath Path to the .ld file * @returns {object} Parsed data structure */ static parseFile(filePath) { try { const fileContent = fs.readFileSync(filePath, 'utf8'); return this.parse(fileContent); } catch (error) { console.error(`Error reading or parsing file ${filePath}:`, error); return {}; } } /** * Parses .ld format text * @param {string} text The .ld content to parse * @returns {object} Parsed data structure */ static parse(text) { const cleanText = this._removeComments(text); const result = {}; const blocks = cleanText.split('@').slice(1); blocks.forEach(block => { const { name, content } = this._extractBlock(block); if (name && content) { result[name] = this._parseBlockContent(content); } }); return result; } static _removeComments(text) { return text.replace(/(#|\/\/).*$/gm, ''); } static _extractBlock(block) { const firstBrace = block.indexOf('{'); const lastBrace = block.lastIndexOf('}'); if (firstBrace === -1 || lastBrace === -1) return {}; return { name: block.substring(0, firstBrace).trim(), content: block.substring(firstBrace + 1, lastBrace).trim() }; } static _parseBlockContent(content) { const obj = {}; let pos = 0; const len = content.length; while (pos < len) { pos = this._skipWhitespace(content, pos); if (pos >= len) break; const { key, newPos } = this._extractKey(content, pos); if (!key) break; pos = newPos; pos = this._skipWhitespace(content, pos); if (pos >= len) break; const { value, endPos } = this._extractValue(content, pos); obj[key] = value; pos = endPos; } return obj; } static _skipWhitespace(content, pos) { while (pos < content.length && this._isWhitespace(content[pos])) { pos++; } return pos; } static _isWhitespace(char) { return char === ' ' || char === '\n' || char === '\r' || char === '\t'; } static _extractKey(content, pos) { const keyMatch = content.slice(pos).match(/^([a-zA-Z_]\w*):/); if (!keyMatch) return { key: null, newPos: pos }; return { key: keyMatch[1], newPos: pos + keyMatch[0].length }; } static _extractValue(content, pos) { const char = content[pos]; if (char === '{') { return this._parseObject(content, pos); } else if (char === '[') { return this._parseArray(content, pos); } else { return this._parsePrimitive(content, pos); } } static _parseObject(content, pos) { let depth = 1; let endPos = pos + 1; while (endPos < content.length && depth > 0) { if (content[endPos] === '{') depth++; if (content[endPos] === '}') depth--; endPos++; } const innerContent = content.substring(pos + 1, endPos - 1); return { value: this._parseBlockContent(innerContent), endPos: endPos }; } static _parseArray(content, pos) { let endPos = pos + 1; while (endPos < content.length && content[endPos] !== ']') { endPos++; } const arrayContent = content.substring(pos + 1, endPos); return { value: arrayContent.split(',').map(item => this._convertValue(item.trim())), endPos: endPos + 1 }; } static _parsePrimitive(content, pos) { let endPos = pos; while (endPos < content.length && !this._isWhitespace(content[endPos]) && content[endPos] !== '}') { endPos++; } const value = content.substring(pos, endPos).trim(); return { value: this._convertValue(value), endPos: endPos }; } static _convertValue(value) { if (value === 'true') return true; if (value === 'false') return false; if (!isNaN(value)) return Number(value); if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { return value.slice(1, -1); } return value; } /** * Flattens a nested object structure * @param {object} obj The object to flatten * @param {string} prefix Optional prefix for keys * @param {object} result Internal use for recursion * @returns {object} Flattened object */ static flatten(obj, prefix = '', result = {}) { for (const key in obj) { if (obj.hasOwnProperty(key)) { const newKey = prefix ? `${prefix}_${key}` : key; if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { this.flatten(obj[key], newKey, result); } else { result[newKey] = obj[key]; } } } return result; } } module.exports = LiteDataParser; // ===== USAGE EXAMPLES ===== // // Example 1: Parse from file // const parsedFromFile = LiteDataParser.parseFile('sample.ld'); // console.log("Parsed from file:"); // console.log(JSON.stringify(parsedFromFile, null, 2)); // // Example 2: Parse from string // const inputString = ` // @user { // name: "Alice" // age: 25 // address: { // city: "Chennai" // location: { lat: 13.08, long: 80.27 } // description: "something" // } // active: true // }`; // const parsedFromString = LiteDataParser.parse(inputString); // console.log("\nParsed from string:"); // console.log(JSON.stringify(parsedFromString, null, 2)); // // Example 3: Flatten the structure // const flatUser = LiteDataParser.flatten(parsedFromString.user); // console.log("\nFlattened structure:"); // console.log(flatUser); // // Example 4: Access specific values // console.log("\nSpecific values:"); // console.log("Name:", flatUser.name); // console.log("Age:", flatUser.age); // console.log("City:", flatUser.address_city); // console.log("Description:", flatUser.address_description); // console.log("Latitude:", flatUser.address_location_lat);