UNPKG

@hashgraph/solo

Version:

An opinionated CLI tool to deploy and manage private Hedera Networks.

287 lines 10.8 kB
// SPDX-License-Identifier: Apache-2.0 import { ConfigKeyFormatter } from '../config-key-formatter.js'; import { LexerInternalNode } from './lexer-internal-node.js'; import { LexerLeafNode } from './lexer-leaf-node.js'; import { KeyName } from '../key-name.js'; import { ConfigKeyError } from '../config-key-error.js'; import { IllegalArgumentError } from '../../../business/errors/illegal-argument-error.js'; import { FlatKeyMapper } from '../../mapper/impl/flat-key-mapper.js'; export class Lexer { tokens; formatter; _roots = new Map(); flatMapper; _rendered = false; constructor(tokens, formatter = ConfigKeyFormatter.instance()) { this.tokens = tokens; this.formatter = formatter; if (!this.tokens) { throw new ConfigKeyError('tokens must be provided'); } if (!this.formatter) { throw new ConfigKeyError('formatter must be provided'); } this.flatMapper = new FlatKeyMapper(this.formatter); } get rendered() { return this._rendered; } set rendered(rendered) { this._rendered = rendered; } get rootNodes() { if (!this.rendered) { this.renderTrees(); } return [...this._roots.values()]; } get tree() { if (!this.rendered) { this.renderTrees(); } return this._roots; } nodeFor(key) { if (!key) { throw new IllegalArgumentError('key must not be null or undefined'); } const segments = this.formatter.split(key); if (segments.length === 0 || segments[0].trim().length === 0) { throw new IllegalArgumentError('key must not be empty'); } let currentNode = this.tree.get(segments[0]); if (!currentNode) { return null; } for (let index = 1; index < segments.length; index++) { const segment = segments[index]; if (currentNode.isLeaf()) { return null; } const inode = currentNode; const nextNode = inode.children.find((n) => n.name === segment); if (!nextNode) { return null; } currentNode = nextNode; } return currentNode; } addValue(key, value) { if (!key) { throw new IllegalArgumentError('key must not be null or undefined'); } const segments = this.formatter.split(this.formatter.normalize(key)); const rootNode = this._roots.has(segments[0]) ? this._roots.get(segments[0]) : this.rootNodeFor(segments); this.processSegments(rootNode, value, segments); this.tokens.set(key, value); } addOrReplaceObject(key, value) { if (!key) { throw new IllegalArgumentError('key must not be null or undefined'); } const normalizedKey = this.formatter.normalize(key); this.addOrReplaceValue(normalizedKey, null); if (!value) { return; } const flatMap = this.flatMapper.flatten(value); for (const [k, v] of flatMap.entries()) { this.addOrReplaceValue(this.formatter.join(normalizedKey, k), v); } } addOrReplaceArray(key, values) { if (!key) { throw new IllegalArgumentError('key must not be null or undefined'); } const normalizedKey = this.formatter.normalize(key); const node = this.nodeFor(normalizedKey); if (node && !node.isArray()) { if (node.isRoot()) { this._roots.delete(node.name); this.tokens.delete(node.name); } else { const parent = node.parent; parent.remove(node); this.tokens.delete(node.path()); } } if (!values) { return; } for (const [index, value] of values.entries()) { this.addOrReplaceArrayElement(normalizedKey, index, value); } } addOrReplaceArrayElement(normalizedKey, index, value) { if (value === null || value === undefined) { this.addOrReplaceValue(this.formatter.join(normalizedKey, index.toString()), null); } const valueType = typeof value; if (valueType === 'string' || valueType === 'number' || valueType === 'boolean' || valueType === 'bigint') { this.addOrReplaceValue(this.formatter.join(normalizedKey, index.toString()), value.toString()); } else { const flatMap = this.flatMapper.flatten(value); for (const [k, value_] of flatMap.entries()) { this.addOrReplaceValue(this.formatter.join(normalizedKey, index.toString(), k), value_); } } } addOrReplaceValue(key, value) { if (!key) { throw new IllegalArgumentError('key must not be null or undefined'); } const node = this.nodeFor(key); if (node) { this.replaceValue(node, value); } else { this.addValue(key, value); } } replaceValue(node, value) { if (!node.isLeaf()) { throw new ConfigKeyError('key must be a leaf node'); } if (node.isRoot()) { this._roots.set(node.name, new LexerLeafNode(null, node.name, value, this.formatter)); this.tokens.set(node.name, value); } else { this.tokens.set(node.path(), value); node.parent.replaceValue(node, value); } } /** * Parses the token map and returns all the root nodes. * * @returns {Node[]} The root nodes. */ renderTrees() { if (this.tokens.size === 0 || this.rendered) { return; } const keys = [...this.tokens.keys()]; // Sort the keys so that we can process them in order. keys.sort(); this.processKeys(keys); this.rendered = true; } processKeys(keys) { for (const k of keys) { const key = this.formatter.normalize(k); const segments = this.formatter.split(key); const root = this.rootNodeFor(segments); if (!root.isLeaf()) { this.processSegments(root, this.tokens.get(key), segments); } } } rootNodeFor(keyParts) { const rootName = keyParts[0]; if (this._roots.has(rootName)) { return this._roots.get(rootName); } let array = false; let root; if (keyParts.length >= 2) { const nextSegment = keyParts[1]; if (KeyName.isArraySegment(nextSegment)) { array = true; } root = new LexerInternalNode(null, rootName, [], array, false, this.formatter); } else { root = new LexerLeafNode(null, rootName, this.tokens.get(rootName), this.formatter); } this._roots.set(rootName, root); return root; } processSegments(root, value, segments) { let currentRoot = root; for (let index = 1; index < segments.length; index++) { const segment = segments[index]; let node; if (KeyName.isArraySegment(segment)) { node = this.processArraySegment(currentRoot, segment, value, index, segments); } else if (index >= segments.length - 1) { node = this.processLeafNode(currentRoot, segment, value); } else { node = this.processIntermediateSegment(currentRoot, segment, index, segments); } if (node.isInternal()) { currentRoot = node; } } } /** * Processes an array segment. This method will create the necessary node to represent the array index. * * @param root {LexerInternalNode} the root node of this segment. * @param value {string} the value of the key. * @param segment {string} the segment to process. * @param index {number} the index of the segment in the array. * @param segments {string[]} the array of segments. * @return {Node} the new root node which should be used as the current root or null if no intermediate/leaf node was * created. * @private */ processArraySegment(root, segment, value, index, segments) { let node = root.children.find((n) => n.name === segment); if (node) { if (node.isLeaf()) { throw new ConfigKeyError(`Cannot add a leaf node to another leaf node [ parent = '${root.path()}', child = '${segment}' ]`); } return node; } // Case where the array segment points at a value. Eg: LeafNode node = index >= segments.length - 1 ? new LexerLeafNode(root, segment, value, this.formatter) : new LexerInternalNode(root, segment, [], false, true, this.formatter); root.add(node); return node; } processIntermediateSegment(root, segment, index, segments) { const existingNode = root.children.find((n) => n.name === segment); if (existingNode) { if (existingNode.isLeaf()) { throw new ConfigKeyError('Cannot add a leaf node to another leaf node'); } return existingNode; } let node; // root.arrVal.0 = string|number (not handled by this case) // root.arrVal.0.scalar = string|number (handles this case) if (root.isArray()) { node = new LexerInternalNode(root, segment, [], false, true, this.formatter); } if (index < segments.length - 1) { const nextSegment = segments[index + 1]; if (KeyName.isArraySegment(nextSegment)) { node = new LexerInternalNode(root, segment, [], true, false, this.formatter); } } if (!node) { node = new LexerInternalNode(root, segment, [], false, false, this.formatter); } root.add(node); return node; } processLeafNode(root, segment, value) { if (root.isArray()) { throw new ConfigKeyError(`Cannot add a leaf node to an array node [ parent: '${root.path()}', child: '${segment}' ]`); } if (root.children.some((n) => n.name === segment)) { throw new ConfigKeyError(`Cannot add a leaf node to another leaf node [ parent: '${root.path()}', child: '${segment}' ]`); } const node = new LexerLeafNode(root, segment, value, this.formatter); root.add(node); return node; } } //# sourceMappingURL=lexer.js.map