ucl-parser
Version:
A Universal Configuration Language (UCL) parser and configuration library.
901 lines (888 loc) • 29.3 kB
JavaScript
// src/ucl-parser.ts
import { readFileSync, existsSync } from "fs";
import { resolve, dirname } from "path";
// src/errors.ts
class UCLError extends Error {
constructor(message) {
super(message);
this.name = "UCLError";
}
}
class UCLSyntaxError extends UCLError {
constructor(message) {
super(message);
this.name = "UCLSyntaxError";
}
}
class UCLReferenceError extends UCLError {
constructor(message) {
super(message);
this.name = "UCLReferenceError";
}
}
class UCLTypeError extends UCLError {
constructor(message) {
super(message);
this.name = "UCLTypeError";
}
}
// src/utils/string-parser.ts
function parseString(s) {
const escapeSequences = {
n: `
`,
t: "\t",
r: "\r",
"\\": "\\",
'"': '"',
"'": "'"
};
let result = "";
let i = 0;
while (i < s.length) {
const char = s[i];
if (char === "\\" && i + 1 < s.length) {
const nextChar = s[i + 1];
if (nextChar !== undefined && escapeSequences[nextChar] !== undefined) {
result += escapeSequences[nextChar];
i += 2;
} else {
result += char;
i++;
}
} else {
result += char;
i++;
}
}
return result;
}
function parseArray(arrayStr, parseValue) {
const content = arrayStr.substring(1, arrayStr.length - 1).trim();
if (!content) {
return [];
}
const cleanedContent = content.replace(/\s*\n\s*/g, " ");
const elements = [];
let currentElement = "";
let bracketCount = 0;
let braceCount = 0;
let inString = false;
let quoteChar = null;
let i = 0;
while (i < cleanedContent.length) {
const char = cleanedContent[i];
if (!inString && (char === '"' || char === "'")) {
inString = true;
quoteChar = char;
currentElement += char;
} else if (inString && char === quoteChar) {
if (i > 0 && cleanedContent[i - 1] === "\\") {
currentElement += char;
} else {
inString = false;
quoteChar = null;
currentElement += char;
}
} else if (!inString) {
if (char === "[") {
bracketCount++;
currentElement += char;
} else if (char === "]") {
bracketCount--;
currentElement += char;
} else if (char === "{") {
braceCount++;
currentElement += char;
} else if (char === "}") {
braceCount--;
currentElement += char;
} else if (char === "," && bracketCount === 0 && braceCount === 0) {
if (currentElement.trim() !== "") {
elements.push(parseValue(currentElement.trim()));
}
currentElement = "";
} else {
currentElement += char;
}
} else {
currentElement += char;
}
i++;
}
if (currentElement.trim() !== "") {
elements.push(parseValue(currentElement.trim()));
}
return elements;
}
function removeComments(content) {
content = content.replace(/\/\*.*?\*\//gs, "");
const lines = content.split(`
`);
const cleanedLines = [];
for (let line of lines) {
let inString = false;
let quoteChar = null;
let i = 0;
while (i < line.length) {
const char = line[i];
if (!inString && (char === '"' || char === "'")) {
inString = true;
quoteChar = char;
} else if (inString && char === quoteChar && line[i - 1] !== "\\") {
inString = false;
quoteChar = null;
} else if (!inString && char === "/" && i + 1 < line.length && line[i + 1] === "/") {
line = line.substring(0, i);
break;
}
i++;
}
cleanedLines.push(line);
}
return cleanedLines.join(`
`);
}
// src/utils/expression-evaluator.ts
function tokenizeExpression(expr) {
const tokens = [];
let currentToken = "";
let inString = false;
let quoteChar = null;
const operatorsAndParentheses = ["+", "-", "*", "/", "%", "(", ")"];
for (let i = 0;i < expr.length; i++) {
const char = expr[i];
if (!inString && (char === '"' || char === "'")) {
if (currentToken.trim() !== "") {
tokens.push(currentToken.trim());
currentToken = "";
}
currentToken = char;
inString = true;
quoteChar = char;
} else if (inString && char === quoteChar) {
if (i > 0 && expr[i - 1] === "\\") {
currentToken += char;
} else {
currentToken += char;
tokens.push(currentToken);
currentToken = "";
inString = false;
quoteChar = null;
}
} else if (inString) {
currentToken += char;
} else if (char !== undefined && operatorsAndParentheses.includes(char)) {
if (currentToken.trim() !== "") {
tokens.push(currentToken.trim());
currentToken = "";
}
tokens.push(char);
} else if (char !== undefined && char.trim() === "") {
if (currentToken.trim() !== "") {
tokens.push(currentToken.trim());
currentToken = "";
}
} else {
currentToken += char;
}
}
if (currentToken.trim() !== "") {
tokens.push(currentToken.trim());
}
return tokens;
}
function isOperator(token) {
return ["+", "-", "*", "/", "%"].includes(token);
}
function toNumber(value) {
if (typeof value === "number") {
return value;
}
if (typeof value === "string") {
const num = Number(value);
if (!isNaN(num) && isFinite(num)) {
return num;
}
}
if (value === null) {
return 0;
}
throw new UCLTypeError(`Cannot convert '${value}' to number`);
}
function containsOperators(valueStr) {
let inString = false;
let quoteChar = null;
for (let i = 0;i < valueStr.length; i++) {
const char = valueStr[i];
if (!inString && (char === '"' || char === "'")) {
inString = true;
quoteChar = char;
} else if (inString && char === quoteChar && valueStr[i - 1] !== "\\") {
inString = false;
quoteChar = null;
} else if (!inString && char !== undefined && ["+", "-", "*", "/", "%"].includes(char)) {
return true;
}
}
return false;
}
// src/utils/type-converter.ts
function convertType(value, targetType) {
if (targetType === "int") {
if (typeof value === "number")
return Math.floor(value);
if (typeof value === "string") {
const num = Number(value);
if (!isNaN(num) && isFinite(num)) {
return Math.floor(num);
}
}
if (value === null)
return 0;
throw new UCLTypeError(`Cannot convert '${value}' to int`);
} else if (targetType === "float") {
if (typeof value === "number")
return value;
if (typeof value === "string") {
const num = Number(value);
if (!isNaN(num) && isFinite(num)) {
return num;
}
}
if (value === null)
return 0;
throw new UCLTypeError(`Cannot convert '${value}' to float`);
} else if (targetType === "string") {
if (typeof value === "boolean")
return String(value).toLowerCase();
if (value === null)
return "null";
return String(value);
} else if (targetType === "bool") {
if (typeof value === "boolean")
return value;
if (typeof value === "number")
return value !== 0;
if (typeof value === "string") {
const lowerVal = value.toLowerCase();
if (["true", "yes", "1"].includes(lowerVal)) {
return true;
}
if (["false", "no", "0"].includes(lowerVal)) {
return false;
}
throw new UCLTypeError(`Cannot convert string '${value}' to bool`);
}
if (value === null)
return false;
throw new UCLTypeError(`Cannot convert '${value}' to bool`);
} else {
throw new UCLTypeError(`Unknown target type: ${targetType}`);
}
}
// src/utils/reference-resolver.ts
function isVariableReference(valueStr) {
return /^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*(\[\s*(?:'[^']*'|"[^"]*"|(?:\d+))\s*\])*$/.test(valueStr);
}
function resolveSimpleReference(ref, config, currentSection) {
const parts = ref.split(".");
let current = config;
for (const part of parts) {
if (typeof current === "object" && current !== null && !Array.isArray(current) && current.hasOwnProperty(part)) {
const resolvedValue = current[part];
if (resolvedValue === undefined) {
throw new UCLReferenceError(`Cannot resolve reference: ${ref}`);
}
current = resolvedValue;
} else {
if (currentSection.length > 0) {
const potentialRelativePathParts = [...currentSection];
let tempCurrent = config;
try {
for (const sectionPart of potentialRelativePathParts) {
if (typeof tempCurrent === "object" && tempCurrent !== null && !Array.isArray(tempCurrent) && tempCurrent.hasOwnProperty(sectionPart)) {
const nextValue = tempCurrent[sectionPart];
if (nextValue === undefined) {
throw new Error("Invalid section path for relative lookup");
}
tempCurrent = nextValue;
} else {
throw new Error("Invalid section path for relative lookup");
}
}
for (const refPart of parts) {
if (typeof tempCurrent === "object" && tempCurrent !== null && !Array.isArray(tempCurrent) && tempCurrent.hasOwnProperty(refPart)) {
const nextValue = tempCurrent[refPart];
if (nextValue === undefined) {
throw new Error("Relative reference part not found");
}
tempCurrent = nextValue;
} else {
throw new Error("Relative reference part not found");
}
}
return tempCurrent;
} catch (e) {}
}
throw new UCLReferenceError(`Cannot resolve reference: ${ref}`);
}
}
return current;
}
function resolveComplexReference(ref, config, currentSection, resolveReference) {
const refParts = ref.match(/([^[.]+)|(\.[^[.]+)|(\[[^\]]+\])/g);
if (!refParts || refParts.length === 0) {
throw new UCLSyntaxError(`Invalid complex reference format: ${ref}`);
}
let current = null;
let baseRefHandled = false;
for (const part of refParts) {
if (!part) {
continue;
}
if (!part.startsWith("[")) {
if (!baseRefHandled) {
current = resolveReference(part.replace(/^\./, ""));
baseRefHandled = true;
} else if (typeof current === "object" && current !== null && !Array.isArray(current)) {
const key = part.replace(/^\./, "");
if (current.hasOwnProperty(key)) {
const resolvedValue = current[key];
if (resolvedValue === undefined) {
throw new UCLReferenceError(`Object key '${key}' resolved to undefined in reference part '${part}' of '${ref}'`);
}
current = resolvedValue;
} else {
throw new UCLReferenceError(`Object key not found: '${key}' in reference part '${part}' of '${ref}'`);
}
} else {
throw new UCLReferenceError(`Attempted to access key on a non-object value: '${part}' in '${ref}' (current value: ${JSON.stringify(current)})`);
}
} else {
const accessor = part.substring(1, part.length - 1).trim();
if (typeof current !== "object" || current === null) {
throw new UCLReferenceError(`Attempted to access property on non-object/non-array value: '${part}' in '${ref}' (current value: ${JSON.stringify(current)})`);
}
const numIndex = Number(accessor);
if (!isNaN(numIndex) && String(numIndex) === accessor) {
if (Array.isArray(current)) {
if (numIndex >= 0 && numIndex < current.length) {
const resolvedValue = current[numIndex];
if (resolvedValue === undefined) {
throw new UCLReferenceError(`Array index ${numIndex} resolved to undefined in '${ref}'`);
}
current = resolvedValue;
} else {
throw new UCLReferenceError(`Array index out of bounds: ${numIndex} in '${ref}'`);
}
} else {
throw new UCLReferenceError(`Attempted to index a non-array value with index ${numIndex} in '${ref}' (current value: ${JSON.stringify(current)})`);
}
} else {
const key = accessor.replace(/^['"]|['"]$/g, "");
if (typeof current === "object" && !Array.isArray(current)) {
if (current.hasOwnProperty(key)) {
const resolvedValue = current[key];
if (resolvedValue === undefined) {
throw new UCLReferenceError(`Object key '${key}' resolved to undefined in '${ref}' (current value: ${JSON.stringify(current)})`);
}
current = resolvedValue;
} else {
throw new UCLReferenceError(`Object key not found: '${key}' in '${ref}' (current value: ${JSON.stringify(current)})`);
}
} else {
throw new UCLReferenceError(`Attempted to access key on a non-object value with key '${key}' in '${ref}' (current value: ${JSON.stringify(current)})`);
}
}
}
}
return current;
}
// src/utils/value-parser.ts
function isSimpleLiteral(valueStr) {
valueStr = valueStr.trim();
if (valueStr.startsWith('"') && valueStr.endsWith('"') || valueStr.startsWith("'") && valueStr.endsWith("'")) {
return !_containsOperators(valueStr);
}
if (valueStr.startsWith("[") && valueStr.endsWith("]") || valueStr.startsWith("{") && valueStr.endsWith("}")) {
try {
JSON.parse(valueStr);
return true;
} catch (e) {
return false;
}
}
if (["true", "false", "null"].includes(valueStr.toLowerCase())) {
return true;
}
if (/^-?\d+(\.\d+)?$/.test(valueStr)) {
return !isNaN(parseFloat(valueStr)) && isFinite(Number(valueStr));
}
return false;
}
function _containsOperators(valueStr) {
const operators = ["+", "-", "*", "/", "%", "=", ">", "<", "&", "|", "^", "~", "!", "?", ":"];
const quoteChar = valueStr[0];
const inner = valueStr.substring(1, valueStr.length - 1);
return operators.some((op) => inner.includes(op));
}
function parseSimpleValue(valueStr, parseValueCallback) {
valueStr = valueStr.trim();
if (valueStr.toLowerCase() === "null") {
return null;
}
if (["true", "false"].includes(valueStr.toLowerCase())) {
return valueStr.toLowerCase() === "true";
}
if (valueStr.startsWith('"') && valueStr.endsWith('"') || valueStr.startsWith("'") && valueStr.endsWith("'")) {
return parseString(valueStr.substring(1, valueStr.length - 1));
}
if (valueStr.startsWith("[") && valueStr.endsWith("]")) {
return parseArray(valueStr, parseValueCallback);
}
if (valueStr.startsWith("{") && valueStr.endsWith("}")) {
try {
return JSON.parse(valueStr);
} catch (e) {
throw new UCLSyntaxError(`Invalid JSON object: ${e.message} in '${valueStr}'`);
}
}
const num = Number(valueStr);
if (!isNaN(num) && isFinite(num)) {
return num;
}
return valueStr;
}
// src/ucl-parser.ts
class UCLParser {
config = {};
currentSection = [];
defaults = {};
envVars = { ...process.env };
basePath = process.cwd();
constructor() {}
parseFile(filepath) {
const fullPath = resolve(filepath);
this.basePath = dirname(fullPath);
if (!existsSync(fullPath)) {
throw new Error(`File not found: ${filepath}`);
}
const content = readFileSync(fullPath, "utf-8");
return this.parseString(content);
}
parseString(content) {
this.config = {};
this.currentSection = [];
this.defaults = {};
content = removeComments(content);
let lines = content.split(`
`);
lines = this._processIncludes(lines);
this._parseLines(lines);
this._applyDefaults();
return this.config;
}
_processIncludes(lines) {
const processedLines = [];
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith("include ")) {
const match = trimmedLine.match(/^include\s+["']([^"']+)["']$/);
if (match) {
const includePath = match[1];
const fullPath = resolve(this.basePath, includePath);
if (existsSync(fullPath)) {
const includeContent = readFileSync(fullPath, "utf-8");
const cleanedIncludeContent = removeComments(includeContent);
const includeLines = cleanedIncludeContent.split(`
`);
processedLines.push(...this._processIncludes(includeLines));
} else {
throw new UCLError(`Include file not found: ${includePath}`);
}
} else {
throw new UCLSyntaxError(`Invalid include syntax: ${line}`);
}
} else {
processedLines.push(line);
}
}
return processedLines;
}
_parseLines(lines) {
let i = 0;
while (i < lines.length) {
const line = (lines[i] ?? "").trim();
if (!line) {
i++;
continue;
}
if (line.startsWith("[") && line.endsWith("]")) {
const sectionName = line.substring(1, line.length - 1).trim();
if (sectionName.toLowerCase() === "defaults") {
i = this._parseDefaultsSection(lines, i + 1);
break;
} else {
this.currentSection = sectionName.split(".");
i++;
}
} else {
i = this._parseKeyValue(lines, i);
}
}
}
_parseDefaultsSection(lines, startIdx) {
let i = startIdx;
while (i < lines.length) {
const line = (lines[i] ?? "").trim();
if (!line) {
i++;
continue;
}
if (line.startsWith("[") && line.endsWith("]")) {
throw new UCLSyntaxError("Defaults section must be at the end of the file");
}
if (line.includes("=")) {
const [key, valuePart] = this._splitKeyValue(line);
this.defaults[key] = this._parseValue(valuePart);
}
i++;
}
return i;
}
_parseKeyValue(lines, startIdx) {
const line = (lines[startIdx] ?? "").trim();
if (!line.includes("=")) {
if (line && !line.startsWith("[") && !line.endsWith("]") && !["{", "}"].includes(line)) {
if (!/[\[\]\{\}\,\"\']/.test(line)) {
throw new UCLSyntaxError(`Invalid syntax: line without equals sign: ${line}`);
}
}
return startIdx + 1;
}
const [key, valuePart] = this._splitKeyValue(line);
if (valuePart.trim().startsWith("{") || valuePart.trim().startsWith("[")) {
const [valueStr, endIdx] = this._parseMultilineValue(lines, startIdx, valuePart);
const value = this._parseValue(valueStr);
this._setNestedValue(key, value);
return endIdx + 1;
} else {
const value = this._parseValue(valuePart);
this._setNestedValue(key, value);
return startIdx + 1;
}
}
_splitKeyValue(line) {
let inString = false;
let quoteChar = null;
for (let i = 0;i < line.length; i++) {
const char = line[i];
if (!inString && (char === '"' || char === "'")) {
inString = true;
quoteChar = char;
} else if (inString && char === quoteChar && line[i - 1] !== "\\") {
inString = false;
quoteChar = null;
} else if (!inString && char === "=") {
const key = line.substring(0, i).trim();
const value = line.substring(i + 1).trim();
return [key, value];
}
}
throw new UCLSyntaxError(`Invalid key-value syntax: ${line}`);
}
_parseMultilineValue(lines, startIdx, initialValue) {
let valueStr = initialValue.trim();
let i = startIdx + 1;
if (valueStr.startsWith("{")) {
let braceCount = (valueStr.match(/{/g)?.length || 0) - (valueStr.match(/}/g)?.length || 0);
while (i < lines.length && braceCount > 0) {
const line = (lines[i] ?? "").trim();
if (line) {
valueStr += `
` + line;
braceCount += (line.match(/{/g)?.length || 0) - (line.match(/}/g)?.length || 0);
}
i++;
}
return [valueStr, i - 1];
} else if (valueStr.startsWith("[")) {
let bracketCount = (valueStr.match(/\[/g)?.length || 0) - (valueStr.match(/\]/g)?.length || 0);
while (i < lines.length && bracketCount > 0) {
const line = (lines[i] ?? "").trim();
if (line) {
valueStr += `
` + line;
bracketCount += (line.match(/\[/g)?.length || 0) - (line.match(/\]/g)?.length || 0);
}
i++;
}
return [valueStr, i - 1];
}
return [initialValue, startIdx];
}
_parseValue(valueStr) {
valueStr = valueStr.trim();
if (!valueStr) {
return null;
}
const envMatch = valueStr.match(/^\$ENV\{([^}]+)\}$/);
if (envMatch) {
const envVar = envMatch[1];
if (typeof envVar === "string") {
return this.envVars[envVar] ?? null;
}
return null;
}
if (valueStr.includes(".") && !isSimpleLiteral(valueStr)) {
const parts = valueStr.split(".");
if (parts.length > 1) {
const typeSuffix = parts[parts.length - 1];
const baseValueStr = parts.slice(0, parts.length - 1).join(".");
if (typeof typeSuffix === "string" && ["int", "float", "string", "bool"].includes(typeSuffix.toLowerCase())) {
const baseValue = this._parseValue(baseValueStr);
return convertType(baseValue, typeSuffix.toLowerCase());
}
}
}
if (containsOperators(valueStr) && !isSimpleLiteral(valueStr)) {
return this._evaluateExpression(valueStr);
}
if (!isSimpleLiteral(valueStr) && isVariableReference(valueStr)) {
return this._resolveReference(valueStr);
}
return parseSimpleValue(valueStr, (v) => this._parseValue(v));
}
_evaluateExpression(expr) {
while (expr.includes("(")) {
const start = expr.lastIndexOf("(");
if (start === -1) {
break;
}
let end = -1;
let openCount = 0;
for (let i = start;i < expr.length; i++) {
if (expr[i] === "(") {
openCount++;
} else if (expr[i] === ")") {
openCount--;
}
if (openCount === 0 && expr[i] === ")") {
end = i;
break;
}
}
if (end === -1) {
throw new UCLSyntaxError(`Mismatched parentheses in expression: ${expr}`);
}
const innerExpr = expr.substring(start + 1, end);
const result = this._evaluateSimpleExpression(innerExpr);
expr = expr.substring(0, start) + String(result) + expr.substring(end + 1);
}
return this._evaluateSimpleExpression(expr);
}
_evaluateSimpleExpression(expr) {
const rawTokens = tokenizeExpression(expr);
let evaluatedTokens = [];
for (const rawToken of rawTokens) {
if (isOperator(rawToken)) {
evaluatedTokens.push(rawToken);
} else {
evaluatedTokens.push(this._resolveOperand(rawToken));
}
}
let processedTokens = [];
for (let i = 0;i < evaluatedTokens.length; i++) {
const token = evaluatedTokens[i];
if (token === "*" || token === "/" || token === "%") {
const leftToken = processedTokens.pop();
if (leftToken === undefined) {
throw new UCLSyntaxError("Missing left operand for operator '" + token + "'");
}
const left = toNumber(leftToken);
const rightToken = evaluatedTokens[++i];
if (rightToken === undefined) {
throw new UCLSyntaxError("Missing right operand for operator '" + token + "'");
}
const right = toNumber(rightToken);
let result;
if (token === "*") {
result = left * right;
} else if (token === "/") {
if (right === 0) {
throw new UCLTypeError("Division by zero");
}
result = left / right;
} else {
if (right === 0) {
throw new UCLTypeError("Modulo by zero");
}
result = left % right;
}
processedTokens.push(result);
} else {
if (token !== undefined) {
processedTokens.push(token);
}
}
}
evaluatedTokens = processedTokens;
processedTokens = [];
for (let i = 0;i < evaluatedTokens.length; i++) {
const token = evaluatedTokens[i];
if (token === "+" || token === "-") {
const left = processedTokens.pop();
const right = evaluatedTokens[++i];
let result;
if (token === "+") {
if (typeof left === "string" || typeof right === "string") {
result = String(left) + String(right);
} else {
if (left === undefined || right === undefined) {
throw new UCLTypeError("Operand is undefined in addition");
}
result = toNumber(left) + toNumber(right);
}
} else {
if (left === undefined || right === undefined) {
throw new UCLTypeError("Operand is undefined in subtraction");
}
result = toNumber(left) - toNumber(right);
}
processedTokens.push(result);
} else {
if (token !== undefined) {
processedTokens.push(token);
}
}
}
evaluatedTokens = processedTokens;
return evaluatedTokens[0] !== undefined ? evaluatedTokens[0] : null;
}
_resolveOperand(operandStr) {
operandStr = operandStr.trim();
if (!operandStr) {
return null;
}
if (operandStr.startsWith('"') && operandStr.endsWith('"') || operandStr.startsWith("'") && operandStr.endsWith("'")) {
return operandStr.substring(1, operandStr.length - 1);
}
const envMatch = operandStr.match(/^\$ENV\{([^}]+)\}$/);
if (envMatch) {
const envVar = envMatch[1];
if (typeof envVar === "string") {
return this.envVars[envVar] ?? null;
}
return null;
}
if (isVariableReference(operandStr)) {
return this._resolveReference(operandStr);
}
return parseSimpleValue(operandStr, (v) => this._parseValue(v));
}
_resolveReference(ref) {
if (ref.includes("[") || ref.includes("]")) {
return resolveComplexReference(ref, this.config, this.currentSection, (r) => this._resolveReference(r));
}
return resolveSimpleReference(ref, this.config, this.currentSection);
}
_setNestedValue(key, value) {
const fullPath = [...this.currentSection, key];
let current = this.config;
for (let i = 0;i < fullPath.length - 1; i++) {
const part = fullPath[i];
if (typeof part !== "string") {
throw new UCLSyntaxError(`Invalid path part '${part}' while setting nested value`);
}
if (typeof current[part] !== "object" || current[part] === null || Array.isArray(current[part])) {
current[part] = {};
}
current = current[part];
}
const lastKey = fullPath[fullPath.length - 1];
if (typeof lastKey !== "string") {
throw new UCLSyntaxError(`Invalid key '${lastKey}' while setting nested value`);
}
current[lastKey] = value;
}
_getNestedValue(path) {
const parts = path.split(".");
let current = this.config;
for (const part of parts) {
if (typeof current === "object" && current !== null && !Array.isArray(current) && current.hasOwnProperty(part)) {
const resolvedValue = current[part];
if (resolvedValue === undefined) {
throw new UCLReferenceError(`Path not found: ${path}`);
}
current = resolvedValue;
} else {
throw new UCLReferenceError(`Path not found: ${path}`);
}
}
return current;
}
_applyDefaults() {
for (const path in this.defaults) {
if (this.defaults.hasOwnProperty(path)) {
try {
const currentValue = this._getNestedValue(path);
if (currentValue === null) {
const defaultValue = this.defaults[path];
if (defaultValue !== undefined) {
this._setNestedValueByPath(path, defaultValue);
}
}
} catch (e) {
if (e instanceof UCLReferenceError) {
const defaultValue = this.defaults[path];
if (defaultValue !== undefined) {
this._setNestedValueByPath(path, defaultValue);
}
} else {
throw e;
}
}
}
}
}
_setNestedValueByPath(path, value) {
const parts = path.split(".");
let current = this.config;
for (let i = 0;i < parts.length - 1; i++) {
const part = parts[i];
if (typeof part !== "string") {
throw new UCLSyntaxError(`Invalid path part '${part}' while setting nested value by path`);
}
if (typeof current[part] !== "object" || current[part] === null || Array.isArray(current[part])) {
current[part] = {};
}
current = current[part];
}
const lastPart = parts[parts.length - 1];
if (typeof lastPart !== "string") {
throw new UCLSyntaxError(`Invalid key '${lastPart}' while setting nested value by path`);
}
current[lastPart] = value;
}
}
// src/index.ts
function parseUclFile(filepath) {
const parser = new UCLParser;
return parser.parseFile(filepath);
}
function parseUclString(content) {
const parser = new UCLParser;
return parser.parseString(content);
}
export {
parseUclString,
parseUclFile,
UCLTypeError,
UCLSyntaxError,
UCLReferenceError,
UCLParser,
UCLError
};
//# debugId=36361B55A23721BD64756E2164756E21
//# sourceMappingURL=index.js.map