diff options
Diffstat (limited to 'sdnr/wt/odlux/apps/configurationApp/src/yang')
-rw-r--r-- | sdnr/wt/odlux/apps/configurationApp/src/yang/whenParser.ts | 235 | ||||
-rw-r--r-- | sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts | 844 |
2 files changed, 692 insertions, 387 deletions
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/whenParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/whenParser.ts new file mode 100644 index 000000000..fa2968c9c --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/whenParser.ts @@ -0,0 +1,235 @@ +enum WhenTokenType { + AND = 'AND', + OR = 'OR', + NOT = 'NOT', + EQUALS = 'EQUALS', + COMMA = 'COMMA', + STRING = 'STRING', + FUNCTION = 'FUNCTION', + IDENTIFIER = 'IDENTIFIER', + OPEN_PAREN = 'OPEN_PAREN', + CLOSE_PAREN = 'CLOSE_PAREN', + EXPRESSION = 'EXPRESSION', +} + +type Token = { + type: WhenTokenType; + value: string; +}; + +const isAlpha = (char: string) => /[a-z]/i.test(char); + +const isAlphaNumeric = (char: string) => /[A-Za-z0-9_\-/:\.]/i.test(char); + +const lex = (input: string) : Token[] => { + let tokens = [] as any[]; + let current = 0; + + while (current < input.length) { + let char = input[current]; + + if (char === ' ') { + current++; + continue; + } + + if (char === '(') { + tokens.push({ type: WhenTokenType.OPEN_PAREN, value: char }); + current++; + continue; + } + + if (char === ')') { + tokens.push({ type: WhenTokenType.CLOSE_PAREN, value: char }); + current++; + continue; + } + + if (char === '=') { + tokens.push({ type: WhenTokenType.EQUALS, value: char }); + current++; + continue; + } + + if (char === ',') { + tokens.push({ type: WhenTokenType.COMMA, value: char }); + current++; + continue; + } + + if (char === '\"' || char === '\'') { + let value = ''; + let start = current; + current++; + + while (current < input.length) { + let innerChar = input[current]; + if (innerChar === '\\') { + value += input[current] + input[current + 1]; + current += 2; + } else if (innerChar === input[start]) { + current++; + break; + } else { + value += innerChar; + current++; + } + } + + tokens.push({ type: WhenTokenType.STRING, value }); + continue; + } + + if (isAlpha(char)) { + let value = ''; + while (isAlpha(char)) { + value += char; + char = input[++current]; + } + + switch (value) { + case 'and': + tokens.push({ type: WhenTokenType.AND }); + break; + case 'or': + tokens.push({ type: WhenTokenType.OR }); + break; + case 'not': + tokens.push({ type: WhenTokenType.NOT }); + break; + case 'eq': + tokens.push({ type: WhenTokenType.EQUALS }); + break; + default: + while (isAlphaNumeric(char)) { + value += char; + char = input[++current]; + } + tokens.push({ type: WhenTokenType.IDENTIFIER, value }); + } + + continue; + } + if (isAlphaNumeric(char)) { + let value = ''; + while (isAlphaNumeric(char)) { + value += char; + char = input[++current]; + } + + tokens.push({ type: WhenTokenType.IDENTIFIER, value }); + continue; + } + throw new TypeError(`I don't know what this character is: ${char}`); + } + return tokens; +}; + +type WhenAST = { + type: WhenTokenType; + left?: WhenAST; + right?: WhenAST; + value?: string | WhenAST; + name?: string; + args?: WhenAST[]; +}; + +const precedence : { [index: string] : number } = { + 'EQUALS': 4, + 'NOT': 3, + 'AND': 2, + 'OR': 1, +}; + +const parseWhen = (whenExpression: string) => { + const tokens = lex(whenExpression); + let current = 0; + + const walk = (precedenceLevel = 0) : WhenAST => { + let token = tokens[current]; + let node: WhenAST | null = null; + + if (token.type === WhenTokenType.OPEN_PAREN) { + token = tokens[++current]; + let innerNode: WhenAST = { type: WhenTokenType.EXPRESSION, value: walk() }; + token = tokens[current]; + + while (token.type !== WhenTokenType.CLOSE_PAREN) { + innerNode = { + type: token.type, + value: token.value, + left: innerNode, + right: walk(), + }; + token = tokens[current]; + } + current++; + return innerNode; + } + + if (token.type === WhenTokenType.STRING ) { + current++; + node = { type: token.type, value: token.value }; + } + + if (token.type === WhenTokenType.NOT) { + token = tokens[++current]; + node = { type: WhenTokenType.NOT, value: token.value, right: walk() }; + } + + if (token.type === WhenTokenType.IDENTIFIER) { + const nextToken = tokens[current + 1]; + if (nextToken.type === WhenTokenType.OPEN_PAREN) { + let name = token.value; + token = tokens[++current]; + + let args = []; + token = tokens[++current]; + + while (token.type !== WhenTokenType.CLOSE_PAREN) { + if (token.type === WhenTokenType.COMMA) { + current++; + } else { + args.push(walk()); + } + token = tokens[current]; + } + + current++; + node = { type: WhenTokenType.FUNCTION, name, args }; + } else { + current++; + node = { type: WhenTokenType.IDENTIFIER, value: token.value }; + } + } + + if (!node) throw new TypeError('Unexpected token: ' + token.type); + + token = tokens[current]; + while (current < tokens.length && precedence[token.type] >= precedenceLevel) { + console.log(current, tokens[current], tokens[current].type, precedenceLevel, precedence[token.type]); + token = tokens[current]; + if (token.type === WhenTokenType.EQUALS || token.type === WhenTokenType.AND || token.type === WhenTokenType.OR) { + current++; + node = { + type: token.type, + left: node, + right: walk(precedence[token.type]), + }; + } else { + break; + } + } + + return node; + + }; + + return walk(); +}; + +export { + parseWhen, + WhenAST, + WhenTokenType, +};
\ No newline at end of file diff --git a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts index e8e636f9b..cc2520100 100644 --- a/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts +++ b/sdnr/wt/odlux/apps/configurationApp/src/yang/yangParser.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-loss-of-precision */ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/* eslint-disable @typescript-eslint/naming-convention */ /** * ============LICENSE_START======================================================================== * ONAP : ccsdk feature sdnr wt odlux @@ -15,14 +18,30 @@ * the License. * ============LICENSE_END========================================================================== */ -import { Token, Statement, Module, Identity, ModuleState } from "../models/yang"; +import { Token, Statement, Module, Identity, ModuleState } from '../models/yang'; import { - ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase, - isViewElementReference, ViewElementChoise, ViewElementBinary, ViewElementString, isViewElementString, - isViewElementNumber, ViewElementNumber, Expression, YangRange, ViewElementUnion, ViewElementRpc, isViewElementRpc, ResolveFunction, ViewElementDate -} from "../models/uiModels"; -import { yangService } from "../services/yangService"; - + Expression, + ViewElement, + ViewElementBase, + ViewSpecification, + ViewElementNumber, + ViewElementString, + ViewElementChoice, + ViewElementUnion, + ViewElementRpc, + isViewElementObjectOrList, + isViewElementNumber, + isViewElementString, + isViewElementRpc, + ResolveFunction, + YangRange, +} from '../models/uiModels'; +import { yangService } from '../services/yangService'; + +const LOGLEVEL = +(localStorage.getItem('log.odlux.app.configuration.yang.yangParser') || 0); + +import { LogLevel } from '../../../../framework/src/utilities/logLevel'; +import { parseWhen, WhenAST, WhenTokenType } from './whenParser'; export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => { const pathParts: RegExpMatchArray[] = []; @@ -32,21 +51,22 @@ export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray if (partMatch) { pathParts.push(partMatch); } - } while (partMatch) + } while (partMatch); return pathParts; -} +}; class YangLexer { private pos: number = 0; - private buf: string = ""; + + private buf: string = ''; constructor(input: string) { this.pos = 0; this.buf = input; } - private _optable: { [key: string]: string } = { + private _opTable: { [key: string]: string } = { ';': 'SEMI', '{': 'L_BRACE', '}': 'R_BRACE', @@ -66,7 +86,7 @@ class YangLexer { private _isAlpha(char: string): boolean { return (char >= 'a' && char <= 'z') || - (char >= 'A' && char <= 'Z') + (char >= 'A' && char <= 'Z'); } private _isAlphanum(char: string): boolean { @@ -74,7 +94,7 @@ class YangLexer { char === '_' || char === '-' || char === '.'; } - private _skipNontokens() { + private _skipNonTokens() { while (this.pos < this.buf.length) { const char = this.buf.charAt(this.pos); if (this._isWhitespace(char)) { @@ -90,11 +110,11 @@ class YangLexer { let end_index = this.pos + 1; while (end_index < this.buf.length) { const char = this.buf.charAt(end_index); - if (char === "\\") { + if (char === '\\') { end_index += 2; continue; - }; - if (terminator === null && (this._isWhitespace(char) || this._optable[char] !== undefined) || char === terminator) { + } + if (terminator === null && (this._isWhitespace(char) || this._opTable[char] !== undefined) || char === terminator) { break; } end_index++; @@ -109,7 +129,7 @@ class YangLexer { name: 'STRING', value: this.buf.substring(start, end), start, - end + end, }; this.pos = terminator ? end + 1 : end; return tok; @@ -122,8 +142,8 @@ class YangLexer { ++endpos; } - let name = 'IDENTIFIER' - if (this.buf.charAt(endpos) === ":") { + let name = 'IDENTIFIER'; + if (this.buf.charAt(endpos) === ':') { name = 'IDENTIFIERREF'; ++endpos; while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) { @@ -135,7 +155,7 @@ class YangLexer { name: name, value: this.buf.substring(this.pos, endpos), start: this.pos, - end: endpos + end: endpos, }; this.pos = endpos; @@ -153,7 +173,7 @@ class YangLexer { name: 'NUMBER', value: this.buf.substring(this.pos, endpos), start: this.pos, - end: endpos + end: endpos, }; this.pos = endpos; return tok; @@ -171,7 +191,7 @@ class YangLexer { private _processBlockComment() { var endpos = this.pos + 2; // Skip until the end of the line - while (endpos < this.buf.length && !((this.buf.charAt(endpos) === "/" && this.buf.charAt(endpos - 1) === "*"))) { + while (endpos < this.buf.length && !((this.buf.charAt(endpos) === '/' && this.buf.charAt(endpos - 1) === '*'))) { endpos++; } this.pos = endpos + 1; @@ -179,87 +199,87 @@ class YangLexer { public tokenize(): Token[] { const result: Token[] = []; - this._skipNontokens(); + this._skipNonTokens(); while (this.pos < this.buf.length) { const char = this.buf.charAt(this.pos); - const op = this._optable[char]; + const op = this._opTable[char]; if (op !== undefined) { result.push({ name: op, value: char, start: this.pos, end: ++this.pos }); } else if (this._isAlpha(char)) { result.push(this._processIdentifier()); - this._skipNontokens(); + this._skipNonTokens(); const peekChar = this.buf.charAt(this.pos); - if (this._optable[peekChar] === undefined) { - result.push((peekChar !== "'" && peekChar !== '"') + if (this._opTable[peekChar] === undefined) { + result.push((peekChar !== '\'' && peekChar !== '"') ? this._processString(null) : this._processString(peekChar)); } - } else if (char === '/' && this.buf.charAt(this.pos + 1) === "/") { + } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') { this._processLineComment(); - } else if (char === '/' && this.buf.charAt(this.pos + 1) === "*") { + } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') { this._processBlockComment(); } else { - throw Error('Token error at ' + this.pos + " " + this.buf[this.pos]); + throw Error('Token error at ' + this.pos + ' ' + this.buf[this.pos]); } - this._skipNontokens(); + this._skipNonTokens(); } return result; } public tokenize2(): Statement { - let stack: Statement[] = [{ key: "ROOT", sub: [] }]; + let stack: Statement[] = [{ key: 'ROOT', sub: [] }]; let current: Statement | null = null; - this._skipNontokens(); + this._skipNonTokens(); while (this.pos < this.buf.length) { const char = this.buf.charAt(this.pos); - const op = this._optable[char]; + const op = this._opTable[char]; if (op !== undefined) { - if (op === "L_BRACE") { + if (op === 'L_BRACE') { current && stack.unshift(current); current = null; - } else if (op === "R_BRACE") { + } else if (op === 'R_BRACE') { current = stack.shift() || null; } this.pos++; - } else if (this._isAlpha(char) || char === "_") { + } else if (this._isAlpha(char) || char === '_') { const key = this._processIdentifier().value; - this._skipNontokens(); + this._skipNonTokens(); let peekChar = this.buf.charAt(this.pos); let arg = undefined; - if (this._optable[peekChar] === undefined) { - arg = (peekChar === '"' || peekChar === "'") + if (this._opTable[peekChar] === undefined) { + arg = (peekChar === '"' || peekChar === '\'') ? this._processString(peekChar).value : this._processString(null).value; } do { - this._skipNontokens(); + this._skipNonTokens(); peekChar = this.buf.charAt(this.pos); - if (peekChar !== "+") break; + if (peekChar !== '+') break; this.pos++; - this._skipNontokens(); + this._skipNonTokens(); peekChar = this.buf.charAt(this.pos); - arg += (peekChar === '"' || peekChar === "'") + arg += (peekChar === '"' || peekChar === '\'') ? this._processString(peekChar).value : this._processString(null).value; } while (true); current = { key, arg, sub: [] }; stack[0].sub!.push(current); - } else if (char === '/' && this.buf.charAt(this.pos + 1) === "/") { + } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') { this._processLineComment(); - } else if (char === '/' && this.buf.charAt(this.pos + 1) === "*") { + } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') { this._processBlockComment(); } else { - throw Error('Token error at ' + this.pos + " " + this.buf.slice(this.pos - 10, this.pos + 10)); + throw Error('Token error at ' + this.pos + ' ' + this.buf.slice(this.pos - 10, this.pos + 10)); } - this._skipNontokens(); + this._skipNonTokens(); } - if (stack[0].key !== "ROOT" || !stack[0].sub![0]) { - throw new Error("Internal Perser Error"); + if (stack[0].key !== 'ROOT' || !stack[0].sub![0]) { + throw new Error('Internal Perser Error'); } return stack[0].sub![0]; } @@ -269,25 +289,33 @@ export class YangParser { private _groupingsToResolve: ViewSpecification[] = []; private _identityToResolve: (() => void)[] = []; + private _unionsToResolve: (() => void)[] = []; + private _modulesToResolve: (() => void)[] = []; private _modules: { [name: string]: Module } = {}; + private _views: ViewSpecification[] = [{ - id: "0", - name: "root", - language: "en-US", + id: '0', + name: 'root', + language: 'en-US', canEdit: false, config: true, - parentView: "0", - title: "root", + parentView: '0', + title: 'root', elements: {}, }]; - public static ResolveStack = Symbol("ResolveStack"); + public static ResolveStack = Symbol('ResolveStack'); + + constructor( + private nodeId: string, + private _capabilityRevisionMap: { [capability: string]: string } = {}, + private _unavailableCapabilities: { failureReason: string; capability: string }[] = [], + private _importOnlyModules: { name: string; revision: string }[] = [], + ) { - constructor(private _unavailableCapabilities: { failureReason: string; capability: string; }[] = [], private _importOnlyModules: { name: string; revision: string; }[] = [], private nodeId: string) { - } public get modules() { @@ -300,8 +328,12 @@ export class YangParser { public async addCapability(capability: string, version?: string, parentImportOnlyModule?: boolean) { // do not add twice - if (this._modules[capability]) { - // console.warn(`Skipped capability: ${capability} since already contained.` ); + const existingCapability = this._modules[capability]; + const latestVersionExisting = existingCapability && Object.keys(existingCapability.revisions).sort().reverse()[0]; + if ((latestVersionExisting && version) && (version <= latestVersionExisting)) { + if (LOGLEVEL == LogLevel.Warning) { + console.warn(`Skipped capability: ${capability}:${version || ''} since already contained.`); + } return; } @@ -310,14 +342,15 @@ export class YangParser { // // console.warn(`Skipped capability: ${capability} since it is marked as unavailable.` ); // return; // } + const data = await yangService.getCapability(capability, this.nodeId, version); if (!data) { - throw new Error(`Could not load yang file for ${capability}.`); + throw new Error(`Could not load yang file for ${capability}:${version || ''}.`); } const rootStatement = new YangLexer(data).tokenize2(); - if (rootStatement.key !== "module") { + if (rootStatement.key !== 'module') { throw new Error(`Root element of ${capability} is not a module.`); } if (rootStatement.arg !== capability) { @@ -326,10 +359,32 @@ export class YangParser { const isUnavailable = this._unavailableCapabilities.some(c => c.capability === capability); const isImportOnly = parentImportOnlyModule === true || this._importOnlyModules.some(c => c.name === capability); + + // extract revisions + const revisions = this.extractNodes(rootStatement, 'revision').reduce<{ [version: string]: {} }>((acc, revision) => { + if (!revision.arg) { + throw new Error(`Module [${rootStatement.arg}] has a version w/o version number.`); + } + const description = this.extractValue(revision, 'description'); + const reference = this.extractValue(revision, 'reference'); + acc[revision.arg] = { + description, + reference, + }; + return acc; + }, {}); + + const latestVersionLoaded = Object.keys(revisions).sort().reverse()[0]; + if (existingCapability && latestVersionExisting >= latestVersionLoaded) { + if (LOGLEVEL == LogLevel.Warning) { + console.warn(`Skipped capability: ${capability}:${latestVersionLoaded} since ${capability}:${latestVersionExisting} already contained.`); + } + return; + } const module = this._modules[capability] = { name: rootStatement.arg, - revisions: {}, + revisions, imports: {}, features: {}, identities: {}, @@ -339,10 +394,10 @@ export class YangParser { views: {}, elements: {}, state: isUnavailable - ? ModuleState.unavailable - : isImportOnly - ? ModuleState.importOnly - : ModuleState.stable, + ? ModuleState.unavailable + : isImportOnly + ? ModuleState.importOnly + : ModuleState.stable, }; await this.handleModule(module, rootStatement, capability); @@ -351,84 +406,66 @@ export class YangParser { private async handleModule(module: Module, rootStatement: Statement, capability: string) { // extract namespace && prefix - module.namespace = this.extractValue(rootStatement, "namespace"); - module.prefix = this.extractValue(rootStatement, "prefix"); + module.namespace = this.extractValue(rootStatement, 'namespace'); + module.prefix = this.extractValue(rootStatement, 'prefix'); if (module.prefix) { module.imports[module.prefix] = capability; } - // extract revisions - const revisions = this.extractNodes(rootStatement, "revision"); - module.revisions = { - ...module.revisions, - ...revisions.reduce<{ [version: string]: {} }>((acc, version) => { - if (!version.arg) { - throw new Error(`Module [${module.name}] has a version w/o version number.`); - } - const description = this.extractValue(version, "description"); - const reference = this.extractValue(version, "reference"); - acc[version.arg] = { - description, - reference, - }; - return acc; - }, {}) - }; - // extract features - const features = this.extractNodes(rootStatement, "feature"); + const features = this.extractNodes(rootStatement, 'feature'); module.features = { ...module.features, ...features.reduce<{ [version: string]: {} }>((acc, feature) => { if (!feature.arg) { throw new Error(`Module [${module.name}] has a feature w/o name.`); } - const description = this.extractValue(feature, "description"); + const description = this.extractValue(feature, 'description'); acc[feature.arg] = { description, }; return acc; - }, {}) + }, {}), }; // extract imports - const imports = this.extractNodes(rootStatement, "import"); + const imports = this.extractNodes(rootStatement, 'import'); module.imports = { ...module.imports, ...imports.reduce<{ [key: string]: string }>((acc, imp) => { - const prefix = imp.sub && imp.sub.filter(s => s.key === "prefix"); + const prefix = imp.sub && imp.sub.filter(s => s.key === 'prefix'); if (!imp.arg) { throw new Error(`Module [${module.name}] has an import with neither name nor prefix.`); } acc[prefix && prefix.length === 1 && prefix[0].arg || imp.arg] = imp.arg; return acc; - }, {}) + }, {}), }; // import all required files and set module state if (imports) for (let ind = 0; ind < imports.length; ++ind) { - const moduleName = imports[ind].arg!; + const moduleName = imports[ind].arg!; - //TODO: Fix imports getting loaded without revision - await this.addCapability(moduleName, undefined, module.state === ModuleState.importOnly); + const revision = this._capabilityRevisionMap[moduleName] || undefined; + await this.addCapability(moduleName, revision, module.state === ModuleState.importOnly); const importedModule = this._modules[imports[ind].arg!]; if (importedModule && importedModule.state > ModuleState.stable) { - module.state = Math.max(module.state, ModuleState.instable); + module.state = Math.max(module.state, ModuleState.instable); } } - this.extractTypeDefinitions(rootStatement, module, ""); + this.extractTypeDefinitions(rootStatement, module, ''); - this.extractIdentities(rootStatement, 0, module, ""); + this.extractIdentities(rootStatement, 0, module, ''); - const groupings = this.extractGroupings(rootStatement, 0, module, ""); + const groupings = this.extractGroupings(rootStatement, 0, module, ''); this._views.push(...groupings); - const augments = this.extractAugments(rootStatement, 0, module, ""); + const augments = this.extractAugments(rootStatement, 0, module, ''); this._views.push(...augments); // the default for config on module level is config = true; - const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, ""); + const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, ''); this._views.push(currentView, ...subViews); // create the root elements for this module @@ -443,7 +480,7 @@ export class YangParser { const viewIdIndex = Number(viewElement.viewId); module.views[key] = this._views[viewIdIndex]; } - + // add only the UI View if the module is available if (module.state === ModuleState.stable || module.state === ModuleState.instable) this._views[0].elements[key] = module.elements[key]; }); @@ -462,7 +499,7 @@ export class YangParser { // process all groupings this._groupingsToResolve.filter(vs => vs.uses && vs.uses[ResolveFunction]).forEach(vs => { - try { vs.uses![ResolveFunction] !== undefined && vs.uses![ResolveFunction]!("|"); } catch (error) { + try { vs.uses![ResolveFunction] !== undefined && vs.uses![ResolveFunction]!('|'); } catch (error) { console.warn(`Error resolving: [${vs.name}] [${error.message}]`); } }); @@ -471,16 +508,16 @@ export class YangParser { * This is to fix the issue for sequential execution of modules based on their child and parent relationship * We are sorting the module object based on their augment status */ - Object.keys(this.modules) + Object.keys(this.modules) .map(elem => { - if(this.modules[elem].augments && Object.keys(this.modules[elem].augments).length > 0) { - const {augments, ...rest} = this.modules[elem]; - const partsOfKeys = Object.keys(augments).map((key) => (key.split("/").length - 1)) - this.modules[elem].executionOrder= Math.max(...partsOfKeys) - } else { - this.modules[elem].executionOrder=0; - } - }) + if (this.modules[elem].augments && Object.keys(this.modules[elem].augments).length > 0) { + const { augments, ..._rest } = this.modules[elem]; + const partsOfKeys = Object.keys(augments).map((key) => (key.split('/').length - 1)); + this.modules[elem].executionOrder = Math.max(...partsOfKeys); + } else { + this.modules[elem].executionOrder = 0; + } + }); // process all augmentations / sort by namespace changes to ensure proper order Object.keys(this.modules).sort((a, b) => this.modules[a].executionOrder! - this.modules[b].executionOrder!).forEach(modKey => { @@ -489,8 +526,8 @@ export class YangParser { const pathParts = splitVPath(key, /(?:(?:([^\/\:]+):)?([^\/]+))/g); // 1 = opt: namespace / 2 = property let nameSpaceChangeCounter = 0; let currentNS = module.name; // init namespace - pathParts.forEach(([ns, _])=> { - if (ns === currentNS){ + pathParts.forEach(([ns, _]) => { + if (ns === currentNS) { currentNS = ns; nameSpaceChangeCounter++; } @@ -498,11 +535,11 @@ export class YangParser { return { key, nameSpaceChangeCounter, - } + }; }); - + const augmentKeys = augmentKeysWithCounter - .sort((a,b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1 ) + .sort((a, b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1) .map((a) => a.key); augmentKeys.forEach(augKey => { @@ -512,11 +549,23 @@ export class YangParser { if (augments && viewSpec) { augments.forEach(augment => Object.keys(augment.elements).forEach(key => { const elm = augment.elements[key]; + + const when = elm.when && augment.when + ? { + type: WhenTokenType.AND, + left: elm.when, + right: augment.when, + } + : elm.when || augment.when; + + const ifFeature = elm.ifFeature + ? `(${augment.ifFeature}) and (${elm.ifFeature})` + : augment.ifFeature; + viewSpec.elements[key] = { ...augment.elements[key], - - when: elm.when ? `(${augment.when}) and (${elm.when})` : augment.when, - ifFeature: elm.ifFeature ? `(${augment.ifFeature}) and (${elm.ifFeature})` : augment.ifFeature, + when, + ifFeature, }; })); } @@ -534,7 +583,7 @@ export class YangParser { } } return result; - } + }; const baseIdentities: Identity[] = []; Object.keys(this.modules).forEach(modKey => { @@ -565,30 +614,31 @@ export class YangParser { } }); - // resolve readOnly - const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => { - - // update view config - view.config = view.config && parentConfig; - - Object.keys(view.elements).forEach((key) => { - const elm = view.elements[key]; - - // update element config - elm.config = elm.config && view.config; - - // update all sub-elements of objects - if (elm.uiType === "object") { - resolveReadOnly(this.views[+elm.viewId], elm.config); - } + // // resolve readOnly + // const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => { - }) - } + // // update view config + // view.config = view.config && parentConfig; - const dump = resolveReadOnly(this.views[0], true); - }; + // Object.keys(view.elements).forEach((key) => { + // const elm = view.elements[key]; + + // // update element config + // elm.config = elm.config && view.config; + + // // update all sub-elements of objects + // if (elm.uiType === 'object') { + // resolveReadOnly(this.views[+elm.viewId], elm.config); + // } + + // }); + // }; + + // const dump = resolveReadOnly(this.views[0], true); + } private _nextId = 1; + private get nextId() { return this._nextId++; } @@ -608,7 +658,7 @@ export class YangParser { } private extractTypeDefinitions(statement: Statement, module: Module, currentPath: string): void { - const typedefs = this.extractNodes(statement, "typedef"); + const typedefs = this.extractNodes(statement, 'typedef'); typedefs && typedefs.forEach(def => { if (!def.arg) { throw new Error(`Module: [${module.name}]. Found typefed without name.`); @@ -620,7 +670,7 @@ export class YangParser { /** Handles groupings like named Container */ private extractGroupings(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] { const subViews: ViewSpecification[] = []; - const groupings = this.extractNodes(statement, "grouping"); + const groupings = this.extractNodes(statement, 'grouping'); if (groupings && groupings.length > 0) { subViews.push(...groupings.reduce<ViewSpecification[]>((acc, cur) => { if (!cur.arg) { @@ -629,9 +679,9 @@ export class YangParser { const grouping = cur.arg; // the default for config on module level is config = true; - const [currentView, subViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath); + const [currentView, currentSubViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath); grouping && (module.groupings[grouping] = currentView); - acc.push(currentView, ...subViews); + acc.push(currentView, ...currentSubViews); return acc; }, [])); } @@ -642,7 +692,7 @@ export class YangParser { /** Handles augments also like named container */ private extractAugments(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] { const subViews: ViewSpecification[] = []; - const augments = this.extractNodes(statement, "augment"); + const augments = this.extractNodes(statement, 'augment'); if (augments && augments.length > 0) { subViews.push(...augments.reduce<ViewSpecification[]>((acc, cur) => { if (!cur.arg) { @@ -651,12 +701,12 @@ export class YangParser { const augment = this.resolveReferencePath(cur.arg, module); // the default for config on module level is config = true; - const [currentView, subViews] = this.extractSubViews(cur, parentId, module, currentPath); + const [currentView, currentSubViews] = this.extractSubViews(cur, parentId, module, currentPath); if (augment) { module.augments[augment] = module.augments[augment] || []; module.augments[augment].push(currentView); } - acc.push(currentView, ...subViews); + acc.push(currentView, ...currentSubViews); return acc; }, [])); } @@ -666,109 +716,109 @@ export class YangParser { /** Handles identities */ private extractIdentities(statement: Statement, parentId: number, module: Module, currentPath: string) { - const identities = this.extractNodes(statement, "identity"); + const identities = this.extractNodes(statement, 'identity'); module.identities = identities.reduce<{ [name: string]: Identity }>((acc, cur) => { if (!cur.arg) { - throw new Error(`Module: [${module.name}][${currentPath}]. Found identiy without name.`); + throw new Error(`Module: [${module.name}][${currentPath}]. Found identity without name.`); } acc[cur.arg] = { id: `${module.name}:${cur.arg}`, label: cur.arg, - base: this.extractValue(cur, "base"), - description: this.extractValue(cur, "description"), - reference: this.extractValue(cur, "reference"), - children: [] - } + base: this.extractValue(cur, 'base'), + description: this.extractValue(cur, 'description'), + reference: this.extractValue(cur, 'reference'), + children: [], + }; return acc; }, {}); } - // Hint: use 0 as parentId for rootElements and -1 for rootGroupings. + // Hint: use 0 as parentId for rootElements and -1 for rootGroupings. private extractSubViews(statement: Statement, parentId: number, module: Module, currentPath: string): [ViewSpecification, ViewSpecification[]] { // used for scoped definitions const context: Module = { ...module, typedefs: { - ...module.typedefs - } + ...module.typedefs, + }, }; const currentId = this.nextId; const subViews: ViewSpecification[] = []; let elements: ViewElement[] = []; - const configValue = this.extractValue(statement, "config"); - const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false"; + const configValue = this.extractValue(statement, 'config'); + const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false'; // extract conditions - const ifFeature = this.extractValue(statement, "if-feature"); - const whenCondition = this.extractValue(statement, "when"); - if (whenCondition) console.warn("Found in [" + context.name + "]" + currentPath + " when: " + whenCondition); + const ifFeature = this.extractValue(statement, 'if-feature'); + const whenCondition = this.extractValue(statement, 'when'); + if (whenCondition) console.warn('Found in [' + context.name + ']' + currentPath + ' when: ' + whenCondition); // extract all scoped typedefs this.extractTypeDefinitions(statement, context, currentPath); // extract all scoped groupings subViews.push( - ...this.extractGroupings(statement, parentId, context, currentPath) + ...this.extractGroupings(statement, parentId, context, currentPath), ); // extract all container - const container = this.extractNodes(statement, "container"); + const container = this.extractNodes(statement, 'container'); if (container && container.length > 0) { subViews.push(...container.reduce<ViewSpecification[]>((acc, cur) => { if (!cur.arg) { throw new Error(`Module: [${context.name}]${currentPath}. Found container without name.`); } - const [currentView, subViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`); + const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`); elements.push({ id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg, label: cur.arg, path: currentPath, module: context.name || module.name || '', - uiType: "object", + uiType: 'object', viewId: currentView.id, config: currentView.config, }); - acc.push(currentView, ...subViews); + acc.push(currentView, ...currentSubViews); return acc; }, [])); } // process all lists // a list is a list of containers with the leafs contained in the list - const lists = this.extractNodes(statement, "list"); + const lists = this.extractNodes(statement, 'list'); if (lists && lists.length > 0) { subViews.push(...lists.reduce<ViewSpecification[]>((acc, cur) => { let elmConfig = config; if (!cur.arg) { throw new Error(`Module: [${context.name}]${currentPath}. Found list without name.`); } - const key = this.extractValue(cur, "key") || undefined; + const key = this.extractValue(cur, 'key') || undefined; if (elmConfig && !key) { console.warn(`Module: [${context.name}]${currentPath}. Found configurable list without key. Assume config shell be false.`); elmConfig = false; } - const [currentView, subViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`); + const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`); elements.push({ id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg, label: cur.arg, path: currentPath, module: context.name || module.name || '', isList: true, - uiType: "object", + uiType: 'object', viewId: currentView.id, key: key, config: elmConfig && currentView.config, }); - acc.push(currentView, ...subViews); + acc.push(currentView, ...currentSubViews); return acc; }, [])); } // process all leaf-lists // a leaf-list is a list of some type - const leafLists = this.extractNodes(statement, "leaf-list"); + const leafLists = this.extractNodes(statement, 'leaf-list'); if (leafLists && leafLists.length > 0) { elements.push(...leafLists.reduce<ViewElement[]>((acc, cur) => { const element = this.getViewElement(cur, context, parentId, currentPath, true); @@ -779,7 +829,7 @@ export class YangParser { // process all leafs // a leaf is mainly a property of an object - const leafs = this.extractNodes(statement, "leaf"); + const leafs = this.extractNodes(statement, 'leaf'); if (leafs && leafs.length > 0) { elements.push(...leafs.reduce<ViewElement[]>((acc, cur) => { const element = this.getViewElement(cur, context, parentId, currentPath, false); @@ -789,92 +839,92 @@ export class YangParser { } - const choiceStms = this.extractNodes(statement, "choice"); + const choiceStms = this.extractNodes(statement, 'choice'); if (choiceStms && choiceStms.length > 0) { - elements.push(...choiceStms.reduce<ViewElementChoise[]>((accChoise, curChoise) => { - if (!curChoise.arg) { + elements.push(...choiceStms.reduce<ViewElementChoice[]>((accChoice, curChoice) => { + if (!curChoice.arg) { throw new Error(`Module: [${context.name}]${currentPath}. Found choise without name.`); } // extract all cases like containers - const cases: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[] = []; - const caseStms = this.extractNodes(curChoise, "case"); + const cases: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[] = []; + const caseStms = this.extractNodes(curChoice, 'case'); if (caseStms && caseStms.length > 0) { cases.push(...caseStms.reduce((accCase, curCase) => { if (!curCase.arg) { - throw new Error(`Module: [${context.name}]${currentPath}/${curChoise.arg}. Found case without name.`); + throw new Error(`Module: [${context.name}]${currentPath}/${curChoice.arg}. Found case without name.`); } - const description = this.extractValue(curCase, "description") || undefined; - const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, context, `${currentPath}/${context.name}:${curChoise.arg}`); + const description = this.extractValue(curCase, 'description') || undefined; + const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`); subViews.push(caseView, ...caseSubViews); - const caseDef: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } = { + const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = { id: parentId === 0 ? `${context.name}:${curCase.arg}` : curCase.arg, label: curCase.arg, description: description, - elements: caseView.elements + elements: caseView.elements, }; accCase.push(caseDef); return accCase; - }, [] as { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[])); + }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[])); } // extract all simple cases (one case per leaf, container, etc.) - const [choiseView, choiseSubViews] = this.extractSubViews(curChoise, parentId, context, `${currentPath}/${context.name}:${curChoise.arg}`); - subViews.push(choiseView, ...choiseSubViews); - cases.push(...Object.keys(choiseView.elements).reduce((accElm, curElm) => { - const elm = choiseView.elements[curElm]; - const caseDef: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } = { + const [choiceView, choiceSubViews] = this.extractSubViews(curChoice, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`); + subViews.push(choiceView, ...choiceSubViews); + cases.push(...Object.keys(choiceView.elements).reduce((accElm, curElm) => { + const elm = choiceView.elements[curElm]; + const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = { id: elm.id, label: elm.label, description: elm.description, - elements: { [elm.id]: elm } + elements: { [elm.id]: elm }, }; accElm.push(caseDef); return accElm; - }, [] as { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } }[])); + }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[])); - const description = this.extractValue(curChoise, "description") || undefined; - const configValue = this.extractValue(curChoise, "config"); - const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false"; + const choiceDescription = this.extractValue(curChoice, 'description') || undefined; + const choiceConfigValue = this.extractValue(curChoice, 'config'); + const choiceConfig = choiceConfigValue == null ? true : choiceConfigValue.toLocaleLowerCase() !== 'false'; - const mandatory = this.extractValue(curChoise, "mandatory") === "true" || false; + const mandatory = this.extractValue(curChoice, 'mandatory') === 'true' || false; - const element: ViewElementChoise = { - uiType: "choise", - id: parentId === 0 ? `${context.name}:${curChoise.arg}` : curChoise.arg, - label: curChoise.arg, + const element: ViewElementChoice = { + uiType: 'choice', + id: parentId === 0 ? `${context.name}:${curChoice.arg}` : curChoice.arg, + label: curChoice.arg, path: currentPath, module: context.name || module.name || '', - config: config, + config: choiceConfig, mandatory: mandatory, - description: description, + description: choiceDescription, cases: cases.reduce((acc, cur) => { acc[cur.id] = cur; return acc; - }, {} as { [name: string]: { id: string, label: string, description?: string, elements: { [name: string]: ViewElement } } }) + }, {} as { [name: string]: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } }), }; - accChoise.push(element); - return accChoise; + accChoice.push(element); + return accChoice; }, [])); } - const rpcStms = this.extractNodes(statement, "rpc"); + const rpcStms = this.extractNodes(statement, 'rpc'); if (rpcStms && rpcStms.length > 0) { elements.push(...rpcStms.reduce<ViewElementRpc[]>((accRpc, curRpc) => { if (!curRpc.arg) { throw new Error(`Module: [${context.name}]${currentPath}. Found rpc without name.`); } - const description = this.extractValue(curRpc, "description") || undefined; - const configValue = this.extractValue(curRpc, "config"); - const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false"; + const rpcDescription = this.extractValue(curRpc, 'description') || undefined; + const rpcConfigValue = this.extractValue(curRpc, 'config'); + const rpcConfig = rpcConfigValue == null ? true : rpcConfigValue.toLocaleLowerCase() !== 'false'; let inputViewId: string | undefined = undefined; let outputViewId: string | undefined = undefined; - const input = this.extractNodes(curRpc, "input") || undefined; - const output = this.extractNodes(curRpc, "output") || undefined; + const input = this.extractNodes(curRpc, 'input') || undefined; + const output = this.extractNodes(curRpc, 'output') || undefined; if (input && input.length > 0) { const [inputView, inputSubViews] = this.extractSubViews(input[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`); @@ -889,13 +939,13 @@ export class YangParser { } const element: ViewElementRpc = { - uiType: "rpc", + uiType: 'rpc', id: parentId === 0 ? `${context.name}:${curRpc.arg}` : curRpc.arg, label: curRpc.arg, path: currentPath, module: context.name || module.name || '', - config: config, - description: description, + config: rpcConfig, + description: rpcDescription, inputViewId: inputViewId, outputViewId: outputViewId, }; @@ -906,9 +956,16 @@ export class YangParser { }, [])); } - // if (!statement.arg) { - // throw new Error(`Module: [${context.name}]. Found statement without name.`); - // } + if (!statement.arg) { + console.error(new Error(`Module: [${context.name}]. Found statement without name.`)); + } + + let whenParsed: WhenAST | undefined = undefined; + try { + whenParsed = whenCondition && parseWhen(whenCondition) || undefined; + } catch (e) { + console.error(new Error(`Module: [${context.name}]. Found invalid when condition: ${whenCondition}`)); + } const viewSpec: ViewSpecification = { id: String(currentId), @@ -916,11 +973,11 @@ export class YangParser { ns: context.name, name: statement.arg != null ? statement.arg : undefined, title: statement.arg != null ? statement.arg : undefined, - language: "en-us", + language: 'en-us', canEdit: false, config: config, ifFeature: ifFeature, - when: whenCondition, + when: whenParsed, elements: elements.reduce<{ [name: string]: ViewElement }>((acc, cur) => { acc[cur.id] = cur; return acc; @@ -928,21 +985,21 @@ export class YangParser { }; // evaluate canEdit depending on all conditions - Object.defineProperty(viewSpec, "canEdit", { + Object.defineProperty(viewSpec, 'canEdit', { get: () => { return Object.keys(viewSpec.elements).some(key => { const elm = viewSpec.elements[key]; return (!isViewElementObjectOrList(elm) && elm.config); }); - } + }, }); // merge in all uses references and resolve groupings - const usesRefs = this.extractNodes(statement, "uses"); + const usesRefs = this.extractNodes(statement, 'uses'); if (usesRefs && usesRefs.length > 0) { viewSpec.uses = (viewSpec.uses || []); - const resolveFunctions : ((parentElementPath: string)=>void)[] = []; + const resolveFunctions: ((parentElementPath: string) => void)[] = []; for (let i = 0; i < usesRefs.length; ++i) { const groupingName = usesRefs[i].arg; @@ -951,7 +1008,7 @@ export class YangParser { } viewSpec.uses.push(this.resolveReferencePath(groupingName, context)); - + resolveFunctions.push((parentElementPath: string) => { const groupingViewSpec = this.resolveGrouping(groupingName, context); if (groupingViewSpec) { @@ -963,10 +1020,22 @@ export class YangParser { Object.keys(groupingViewSpec.elements).forEach(key => { const elm = groupingViewSpec.elements[key]; // a useRef on root level need a namespace + const resolvedWhen = elm.when && groupingViewSpec.when + ? { + type: WhenTokenType.AND, + left: elm.when, + right: groupingViewSpec.when, + } + : elm.when || groupingViewSpec.when; + + const resolvedIfFeature = elm.ifFeature + ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})` + : groupingViewSpec.ifFeature; + viewSpec.elements[parentId === 0 ? `${module.name}:${key}` : key] = { ...elm, - when: elm.when ? `(${groupingViewSpec.when}) and (${elm.when})` : groupingViewSpec.when, - ifFeature: elm.ifFeature ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})` : groupingViewSpec.ifFeature, + when: resolvedWhen, + ifFeature: resolvedIfFeature, }; }); } @@ -974,19 +1043,19 @@ export class YangParser { } viewSpec.uses[ResolveFunction] = (parentElementPath: string) => { - const currentElementPath = `${parentElementPath} -> ${viewSpec.ns}:${viewSpec.name}`; + const currentElementPath = `${parentElementPath} -> ${viewSpec.ns}:${viewSpec.name}`; resolveFunctions.forEach(resolve => { - try { - resolve(currentElementPath); - } catch (error) { - console.error(error); - } + try { + resolve(currentElementPath); + } catch (error) { + console.error(error); + } }); // console.log("Resolved "+currentElementPath, viewSpec); if (viewSpec?.uses) { viewSpec.uses[ResolveFunction] = undefined; } - } + }; this._groupingsToResolve.push(viewSpec); } @@ -1020,28 +1089,28 @@ export class YangParser { /** Extracts the UI View from the type in the cur statement. */ private getViewElement(cur: Statement, module: Module, parentId: number, currentPath: string, isList: boolean): ViewElement { - const type = this.extractValue(cur, "type"); - const defaultVal = this.extractValue(cur, "default") || undefined; - const description = this.extractValue(cur, "description") || undefined; + const type = this.extractValue(cur, 'type'); + const defaultVal = this.extractValue(cur, 'default') || undefined; + const description = this.extractValue(cur, 'description') || undefined; - const configValue = this.extractValue(cur, "config"); - const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false"; + const configValue = this.extractValue(cur, 'config'); + const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false'; - const extractRange = (min: number, max: number, property: string = "range"): { expression: Expression<YangRange> | undefined, min: number, max: number } => { - const ranges = this.extractValue(this.extractNodes(cur, "type")[0]!, property) || undefined; - const range = ranges ?.replace(/min/i, String(min)).replace(/max/i, String(max)).split("|").map(r => { + const extractRange = (min: number, max: number, property: string = 'range'): { expression: Expression<YangRange> | undefined; min: number; max: number } => { + const ranges = this.extractValue(this.extractNodes(cur, 'type')[0]!, property) || undefined; + const range = ranges?.replace(/min/i, String(min)).replace(/max/i, String(max)).split('|').map(r => { let minValue: number; let maxValue: number; - + if (r.indexOf('..') > -1) { - const [minStr, maxStr] = r.split('..'); - minValue = Number(minStr); - maxValue = Number(maxStr); - } else if (!isNaN(maxValue = Number(r && r.trim() )) ) { - minValue = maxValue; + const [minStr, maxStr] = r.split('..'); + minValue = Number(minStr); + maxValue = Number(maxStr); + } else if (!isNaN(maxValue = Number(r && r.trim()))) { + minValue = maxValue; } else { - minValue = min, - maxValue = max; + minValue = min, + maxValue = max; } if (minValue > min) min = minValue; @@ -1049,7 +1118,7 @@ export class YangParser { return { min: minValue, - max: maxValue + max: maxValue, }; }); return { @@ -1058,21 +1127,22 @@ export class YangParser { expression: range && range.length === 1 ? range[0] : range && range.length > 1 - ? { operation: "OR", arguments: range } - : undefined - } + ? { operation: 'OR', arguments: range } + : undefined, + }; }; const extractPattern = (): Expression<RegExp> | undefined => { - const pattern = this.extractNodes(this.extractNodes(cur, "type")[0]!, "pattern").map(p => p.arg!).filter(p => !!p).map(p => `^${p.replace(/(?:\\(.))/g, '$1')}$`); + // 2023.01.26 decision MF & SKO: we will no longer remove the backslashes from the pattern, seems to be a bug in the original code + const pattern = this.extractNodes(this.extractNodes(cur, 'type')[0]!, 'pattern').map(p => p.arg!).filter(p => !!p).map(p => `^${p/*.replace(/(?:\\(.))/g, '$1')*/}$`); return pattern && pattern.length == 1 ? new RegExp(pattern[0]) : pattern && pattern.length > 1 - ? { operation: "AND", arguments: pattern.map(p => new RegExp(p)) } + ? { operation: 'AND', arguments: pattern.map(p => new RegExp(p)) } : undefined; - } + }; - const mandatory = this.extractValue(cur, "mandatory") === "true" || false; + const mandatory = this.extractValue(cur, 'mandatory') === 'true' || false; if (!cur.arg) { throw new Error(`Module: [${module.name}]. Found element without name.`); @@ -1084,159 +1154,159 @@ export class YangParser { const element: ViewElementBase = { id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg, - label: cur.arg, + label: cur.arg, path: currentPath, - module: module.name || "", + module: module.name || '', config: config, mandatory: mandatory, isList: isList, default: defaultVal, - description: description + description: description, }; - if (type === "string") { - const length = extractRange(0, +18446744073709551615, "length"); + if (type === 'string') { + const length = extractRange(0, +18446744073709551615, 'length'); return ({ ...element, - uiType: "string", + uiType: 'string', length: length.expression, pattern: extractPattern(), }); - } else if (type === "boolean") { + } else if (type === 'boolean') { return ({ ...element, - uiType: "boolean" + uiType: 'boolean', }); - } else if (type === "uint8") { + } else if (type === 'uint8') { const range = extractRange(0, +255); return ({ ...element, - uiType: "number", + uiType: 'number', range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "uint16") { + } else if (type === 'uint16') { const range = extractRange(0, +65535); return ({ ...element, - uiType: "number", + uiType: 'number', range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "uint32") { + } else if (type === 'uint32') { const range = extractRange(0, +4294967295); return ({ ...element, - uiType: "number", + uiType: 'number', range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "uint64") { + } else if (type === 'uint64') { const range = extractRange(0, +18446744073709551615); return ({ ...element, - uiType: "number", + uiType: 'number', range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "int8") { + } else if (type === 'int8') { const range = extractRange(-128, +127); return ({ ...element, - uiType: "number", + uiType: 'number', range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "int16") { + } else if (type === 'int16') { const range = extractRange(-32768, +32767); return ({ ...element, - uiType: "number", + uiType: 'number', range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "int32") { + } else if (type === 'int32') { const range = extractRange(-2147483648, +2147483647); return ({ ...element, - uiType: "number", + uiType: 'number', range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "int64") { + } else if (type === 'int64') { const range = extractRange(-9223372036854775808, +9223372036854775807); return ({ ...element, - uiType: "number", + uiType: 'number', range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "decimal64") { + } else if (type === 'decimal64') { // decimalRange - const fDigits = Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1; + const fDigits = Number(this.extractValue(this.extractNodes(cur, 'type')[0]!, 'fraction-digits')) || -1; if (fDigits === -1) { throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found decimal64 with invalid fraction-digits.`); } const range = extractRange(YangParser.decimalRange[fDigits].min, YangParser.decimalRange[fDigits].max); return ({ ...element, - uiType: "number", + uiType: 'number', fDigits: fDigits, range: range.expression, min: range.min, max: range.max, - units: this.extractValue(cur, "units") || undefined, - format: this.extractValue(cur, "format") || undefined, + units: this.extractValue(cur, 'units') || undefined, + format: this.extractValue(cur, 'format') || undefined, }); - } else if (type === "enumeration") { - const typeNode = this.extractNodes(cur, "type")[0]!; - const enumNodes = this.extractNodes(typeNode, "enum"); + } else if (type === 'enumeration') { + const typeNode = this.extractNodes(cur, 'type')[0]!; + const enumNodes = this.extractNodes(typeNode, 'enum'); return ({ ...element, - uiType: "selection", + uiType: 'selection', options: enumNodes.reduce<{ key: string; value: string; description?: string }[]>((acc, enumNode) => { if (!enumNode.arg) { throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found option without name.`); } - const ifClause = this.extractValue(enumNode, "if-feature"); - const value = this.extractValue(enumNode, "value"); + // const ifClause = this.extractValue(enumNode, 'if-feature'); + const value = this.extractValue(enumNode, 'value'); const enumOption = { key: enumNode.arg, value: value != null ? value : enumNode.arg, - description: this.extractValue(enumNode, "description") || undefined + description: this.extractValue(enumNode, 'description') || undefined, }; // todo: ❗ handle the if clause ⚡ acc.push(enumOption); return acc; - }, []) + }, []), }); - } else if (type === "leafref") { - const typeNode = this.extractNodes(cur, "type")[0]!; - const vPath = this.extractValue(typeNode, "path"); + } else if (type === 'leafref') { + const typeNode = this.extractNodes(cur, 'type')[0]!; + const vPath = this.extractValue(typeNode, 'path'); if (!vPath) { throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found leafref without path.`); } @@ -1244,11 +1314,11 @@ export class YangParser { const resolve = this.resolveReference.bind(this); const res: ViewElement = { ...element, - uiType: "reference", + uiType: 'reference', referencePath: refPath, - ref(this: ViewElement, currentPath: string) { - const elementPath = `${currentPath}/${cur.arg}`; - + ref(this: ViewElement, basePath: string) { + const elementPath = `${basePath}/${cur.arg}`; + const result = resolve(refPath, elementPath); if (!result) return undefined; @@ -1262,20 +1332,20 @@ export class YangParser { isList: this.isList, default: this.default, description: this.description, - } as ViewElement , resolvedPath] || undefined; - } + } as ViewElement, resolvedPath] || undefined; + }, }; return res; - } else if (type === "identityref") { - const typeNode = this.extractNodes(cur, "type")[0]!; - const base = this.extractValue(typeNode, "base"); + } else if (type === 'identityref') { + const typeNode = this.extractNodes(cur, 'type')[0]!; + const base = this.extractValue(typeNode, 'base'); if (!base) { throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found identityref without base.`); } const res: ViewElement = { ...element, - uiType: "selection", - options: [] + uiType: 'selection', + options: [], }; this._identityToResolve.push(() => { const identity: Identity = this.resolveIdentity(base, module); @@ -1288,29 +1358,29 @@ export class YangParser { res.options = identity.values.map(val => ({ key: val.id, value: val.id, - description: val.description + description: val.description, })); }); return res; - } else if (type === "empty") { + } else if (type === 'empty') { // todo: ❗ handle empty ⚡ /* 9.11. The empty Built-In Type The empty built-in type represents a leaf that does not have any value, it conveys information by its presence or absence. */ return { ...element, - uiType: "empty", + uiType: 'empty', }; - } else if (type === "union") { + } else if (type === 'union') { // todo: ❗ handle union ⚡ /* 9.12. The union Built-In Type */ - const typeNode = this.extractNodes(cur, "type")[0]!; - const typeNodes = this.extractNodes(typeNode, "type"); + const typeNode = this.extractNodes(cur, 'type')[0]!; + const typeNodes = this.extractNodes(typeNode, 'type'); const resultingElement = { ...element, - uiType: "union", - elements: [] + uiType: 'union', + elements: [], } as ViewElementUnion; const resolveUnion = () => { @@ -1318,13 +1388,13 @@ export class YangParser { const stm: Statement = { ...cur, sub: [ - ...(cur.sub ?.filter(s => s.key !== "type") || []), - node - ] + ...(cur.sub?.filter(s => s.key !== 'type') || []), + node, + ], }; return { ...this.getViewElement(stm, module, parentId, currentPath, isList), - id: node.arg! + id: node.arg!, }; })); }; @@ -1332,34 +1402,34 @@ export class YangParser { this._unionsToResolve.push(resolveUnion); return resultingElement; - } else if (type === "bits") { - const typeNode = this.extractNodes(cur, "type")[0]!; - const bitNodes = this.extractNodes(typeNode, "bit"); + } else if (type === 'bits') { + const typeNode = this.extractNodes(cur, 'type')[0]!; + const bitNodes = this.extractNodes(typeNode, 'bit'); return { ...element, - uiType: "bits", - flags: bitNodes.reduce<{ [name: string]: number | undefined; }>((acc, bitNode) => { + uiType: 'bits', + flags: bitNodes.reduce<{ [name: string]: number | undefined }>((acc, bitNode) => { if (!bitNode.arg) { throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found bit without name.`); } - const ifClause = this.extractValue(bitNode, "if-feature"); - const pos = Number(this.extractValue(bitNode, "position")); + // const ifClause = this.extractValue(bitNode, 'if-feature'); + const pos = Number(this.extractValue(bitNode, 'position')); acc[bitNode.arg] = pos === pos ? pos : undefined; return acc; - }, {}) + }, {}), }; - } else if (type === "binary") { + } else if (type === 'binary') { return { ...element, - uiType: "binary", - length: extractRange(0, +18446744073709551615, "length"), + uiType: 'binary', + length: extractRange(0, +18446744073709551615, 'length'), }; - } else if (type === "instance-identifier") { + } else if (type === 'instance-identifier') { // https://tools.ietf.org/html/rfc7950#page-168 return { ...element, - uiType: "string", - length: extractRange(0, +18446744073709551615, "length"), + uiType: 'string', + length: extractRange(0, +18446744073709551615, 'length'), }; } else { // not a build in type, need to resolve type @@ -1374,13 +1444,13 @@ export class YangParser { } // spoof date type here from special string type - if ((type === 'date-and-time' || type.endsWith(':date-and-time') ) && typeRef.module === "ietf-yang-types") { - return { - ...typeRef, - ...element, - description: description, - uiType: "date", - }; + if ((type === 'date-and-time' || type.endsWith(':date-and-time')) && typeRef.module === 'ietf-yang-types') { + return { + ...typeRef, + ...element, + description: description, + uiType: 'date', + }; } return ({ @@ -1391,27 +1461,27 @@ export class YangParser { } } - private resolveStringType(parentElement: ViewElementString, pattern: Expression<RegExp> | undefined, length: { expression: Expression<YangRange> | undefined, min: number, max: number }) { + private resolveStringType(parentElement: ViewElementString, pattern: Expression<RegExp> | undefined, length: { expression: Expression<YangRange> | undefined; min: number; max: number }) { return { ...parentElement, pattern: pattern != null && parentElement.pattern - ? { operation: "AND", arguments: [pattern, parentElement.pattern] } + ? { operation: 'AND', arguments: [pattern, parentElement.pattern] } : parentElement.pattern ? parentElement.pattern : pattern, length: length.expression != null && parentElement.length - ? { operation: "AND", arguments: [length.expression, parentElement.length] } + ? { operation: 'AND', arguments: [length.expression, parentElement.length] } : parentElement.length ? parentElement.length - : length ?.expression, + : length?.expression, } as ViewElementString; } - private resolveNumberType(parentElement: ViewElementNumber, range: { expression: Expression<YangRange> | undefined, min: number, max: number }) { + private resolveNumberType(parentElement: ViewElementNumber, range: { expression: Expression<YangRange> | undefined; min: number; max: number }) { return { ...parentElement, range: range.expression != null && parentElement.range - ? { operation: "AND", arguments: [range.expression, parentElement.range] } + ? { operation: 'AND', arguments: [range.expression, parentElement.range] } : parentElement.range ? parentElement.range : range, @@ -1421,7 +1491,7 @@ export class YangParser { } private resolveReferencePath(vPath: string, module: Module) { - const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g // 1 = opt: namespace / 2 = property + const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g; // 1 = opt: namespace / 2 = property return vPath.replace(vPathParser, (_, ns, property) => { const nameSpace = ns && module.imports[ns] || module.name; return `${nameSpace}:${property}`; @@ -1429,20 +1499,20 @@ export class YangParser { } private resolveReference(vPath: string, currentPath: string) { - const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath + const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath let element: ViewElement | null = null; - let moduleName = ""; + let moduleName = ''; const vPathParts = splitVPath(vPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] })); - const resultPathParts = !vPath.startsWith("/") - ? splitVPath(currentPath, vPathParser).map(p => { moduleName = p[1] || moduleName ; return { ns: moduleName, property: p[2], ind: p[3] } }) + const resultPathParts = !vPath.startsWith('/') + ? splitVPath(currentPath, vPathParser).map(p => { moduleName = p[1] || moduleName; return { ns: moduleName, property: p[2], ind: p[3] }; }) : []; for (let i = 0; i < vPathParts.length; ++i) { const vPathPart = vPathParts[i]; - if (vPathPart.property === "..") { + if (vPathPart.property === '..') { resultPathParts.pop(); - } else if (vPathPart.property !== ".") { + } else if (vPathPart.property !== '.') { resultPathParts.push(vPathPart); } } @@ -1453,30 +1523,30 @@ export class YangParser { if (j === 0) { moduleName = pathPart.ns; const rootModule = this._modules[moduleName]; - if (!rootModule) throw new Error("Could not resolve module [" + moduleName + "].\r\n" + vPath); + if (!rootModule) throw new Error('Could not resolve module [' + moduleName + '].\r\n' + vPath); element = rootModule.elements[`${pathPart.ns}:${pathPart.property}`]; } else if (element && isViewElementObjectOrList(element)) { const view: ViewSpecification = this._views[+element.viewId]; if (moduleName !== pathPart.ns) { moduleName = pathPart.ns; - } + } element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`]; } else { - throw new Error("Could not resolve reference.\r\n" + vPath); + throw new Error('Could not resolve reference.\r\n' + vPath); } - if (!element) throw new Error("Could not resolve path [" + pathPart.property + "] in [" + currentPath + "] \r\n" + vPath); + if (!element) throw new Error('Could not resolve path [' + pathPart.property + '] in [' + currentPath + '] \r\n' + vPath); } - moduleName = ""; // create the vPath for the resolved element, do not add the element itself this will be done later in the res(...) function - return [element, resultPathParts.slice(0,-1).map(p => `${moduleName !== p.ns ? `${moduleName=p.ns}:` : ""}${p.property}${p.ind || ''}`).join("/")]; + moduleName = ''; // create the vPath for the resolved element, do not add the element itself this will be done later in the res(...) function + return [element, resultPathParts.slice(0, -1).map(p => `${moduleName !== p.ns ? `${moduleName = p.ns}:` : ''}${p.property}${p.ind || ''}`).join('/')]; } private resolveView(vPath: string) { - const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath + const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath let element: ViewElement | null = null; let partMatch: RegExpExecArray | null; let view: ViewSpecification | null = null; - let moduleName = ""; + let moduleName = ''; if (vPath) do { partMatch = vPathParser.exec(vPath); if (partMatch) { @@ -1498,13 +1568,13 @@ export class YangParser { } if (!element) return null; } - } while (partMatch) + } while (partMatch); return element && isViewElementObjectOrList(element) && this._views[+element.viewId] || null; } private resolveType(type: string, module: Module) { - const collonInd = type.indexOf(":"); - const preFix = collonInd > -1 ? type.slice(0, collonInd) : ""; + const collonInd = type.indexOf(':'); + const preFix = collonInd > -1 ? type.slice(0, collonInd) : ''; const typeName = collonInd > -1 ? type.slice(collonInd + 1) : type; const res = preFix @@ -1514,8 +1584,8 @@ export class YangParser { } private resolveGrouping(grouping: string, module: Module) { - const collonInd = grouping.indexOf(":"); - const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : ""; + const collonInd = grouping.indexOf(':'); + const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : ''; const groupingName = collonInd > -1 ? grouping.slice(collonInd + 1) : grouping; return preFix @@ -1525,8 +1595,8 @@ export class YangParser { } private resolveIdentity(identity: string, module: Module) { - const collonInd = identity.indexOf(":"); - const preFix = collonInd > -1 ? identity.slice(0, collonInd) : ""; + const collonInd = identity.indexOf(':'); + const preFix = collonInd > -1 ? identity.slice(0, collonInd) : ''; const identityName = collonInd > -1 ? identity.slice(collonInd + 1) : identity; return preFix |