fastparselite
Version:
Super simple & fast DSL parser for lightweight config-like data using custom syntax.
226 lines (191 loc) • 6.21 kB
JavaScript
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);