Create wt-odlux directory
[ccsdk/features.git] / sdnr / wt-odlux / odlux / apps / configurationApp / src / yang / yangParser.ts
1 /* eslint-disable @typescript-eslint/no-loss-of-precision */
2 /* eslint-disable @typescript-eslint/no-unused-expressions */
3 /* eslint-disable @typescript-eslint/naming-convention */
4 /**
5  * ============LICENSE_START========================================================================
6  * ONAP : ccsdk feature sdnr wt odlux
7  * =================================================================================================
8  * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
9  * =================================================================================================
10  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
11  * in compliance with the License. You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software distributed under the License
16  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
17  * or implied. See the License for the specific language governing permissions and limitations under
18  * the License.
19  * ============LICENSE_END==========================================================================
20  */
21 import { Token, Statement, Module, Identity, ModuleState } from '../models/yang';
22 import {
23   Expression,
24   ViewElement,
25   ViewElementBase,
26   ViewSpecification,
27   ViewElementNumber,
28   ViewElementString,
29   ViewElementChoice,
30   ViewElementUnion,
31   ViewElementRpc,
32   isViewElementObjectOrList,
33   isViewElementNumber,
34   isViewElementString,
35   isViewElementRpc,
36   ResolveFunction,
37   YangRange,
38 } from '../models/uiModels';
39 import { yangService } from '../services/yangService';
40
41 const LOGLEVEL = +(localStorage.getItem('log.odlux.app.configuration.yang.yangParser') || 0);
42
43 import { LogLevel } from '../../../../framework/src/utilities/logLevel';
44 import { parseWhen, WhenAST, WhenTokenType } from './whenParser';
45
46 export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => {
47   const pathParts: RegExpMatchArray[] = [];
48   let partMatch: RegExpExecArray | null;
49   if (vPath) do {
50     partMatch = vPathParser.exec(vPath);
51     if (partMatch) {
52       pathParts.push(partMatch);
53     }
54   } while (partMatch);
55   return pathParts;
56 };
57
58 class YangLexer {
59
60   private pos: number = 0;
61
62   private buf: string = '';
63
64   constructor(input: string) {
65     this.pos = 0;
66     this.buf = input;
67   }
68
69   private _opTable: { [key: string]: string } = {
70     ';': 'SEMI',
71     '{': 'L_BRACE',
72     '}': 'R_BRACE',
73   };
74
75   private _isNewline(char: string): boolean {
76     return char === '\r' || char === '\n';
77   }
78
79   private _isWhitespace(char: string): boolean {
80     return char === ' ' || char === '\t' || this._isNewline(char);
81   }
82
83   private _isDigit(char: string): boolean {
84     return char >= '0' && char <= '9';
85   }
86
87   private _isAlpha(char: string): boolean {
88     return (char >= 'a' && char <= 'z') ||
89       (char >= 'A' && char <= 'Z');
90   }
91
92   private _isAlphanum(char: string): boolean {
93     return this._isAlpha(char) || this._isDigit(char) ||
94       char === '_' || char === '-' || char === '.';
95   }
96
97   private _skipNonTokens() {
98     while (this.pos < this.buf.length) {
99       const char = this.buf.charAt(this.pos);
100       if (this._isWhitespace(char)) {
101         this.pos++;
102       } else {
103         break;
104       }
105     }
106   }
107
108   private _processString(terminator: string | null): Token {
109     // this.pos points at the opening quote. Find the ending quote.
110     let end_index = this.pos + 1;
111     while (end_index < this.buf.length) {
112       const char = this.buf.charAt(end_index);
113       if (char === '\\') {
114         end_index += 2;
115         continue;
116       }
117       if (terminator === null && (this._isWhitespace(char) || this._opTable[char] !== undefined) || char === terminator) {
118         break;
119       }
120       end_index++;
121     }
122
123     if (end_index >= this.buf.length) {
124       throw Error('Unterminated quote at ' + this.pos);
125     } else {
126       const start = this.pos + (terminator ? 1 : 0);
127       const end = end_index;
128       const tok = {
129         name: 'STRING',
130         value: this.buf.substring(start, end),
131         start,
132         end,
133       };
134       this.pos = terminator ? end + 1 : end;
135       return tok;
136     }
137   }
138
139   private _processIdentifier(): Token {
140     let endpos = this.pos + 1;
141     while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) {
142       ++endpos;
143     }
144
145     let name = 'IDENTIFIER';
146     if (this.buf.charAt(endpos) === ':') {
147       name = 'IDENTIFIERREF';
148       ++endpos;
149       while (endpos < this.buf.length && this._isAlphanum(this.buf.charAt(endpos))) {
150         ++endpos;
151       }
152     }
153
154     const tok = {
155       name: name,
156       value: this.buf.substring(this.pos, endpos),
157       start: this.pos,
158       end: endpos,
159     };
160
161     this.pos = endpos;
162     return tok;
163   }
164
165   private _processNumber(): Token {
166     let endpos = this.pos + 1;
167     while (endpos < this.buf.length &&
168       this._isDigit(this.buf.charAt(endpos))) {
169       endpos++;
170     }
171
172     const tok = {
173       name: 'NUMBER',
174       value: this.buf.substring(this.pos, endpos),
175       start: this.pos,
176       end: endpos,
177     };
178     this.pos = endpos;
179     return tok;
180   }
181
182   private _processLineComment() {
183     var endpos = this.pos + 2;
184     // Skip until the end of the line
185     while (endpos < this.buf.length && !this._isNewline(this.buf.charAt(endpos))) {
186       endpos++;
187     }
188     this.pos = endpos + 1;
189   }
190
191   private _processBlockComment() {
192     var endpos = this.pos + 2;
193     // Skip until the end of the line
194     while (endpos < this.buf.length && !((this.buf.charAt(endpos) === '/' && this.buf.charAt(endpos - 1) === '*'))) {
195       endpos++;
196     }
197     this.pos = endpos + 1;
198   }
199
200   public tokenize(): Token[] {
201     const result: Token[] = [];
202     this._skipNonTokens();
203     while (this.pos < this.buf.length) {
204
205       const char = this.buf.charAt(this.pos);
206       const op = this._opTable[char];
207
208       if (op !== undefined) {
209         result.push({ name: op, value: char, start: this.pos, end: ++this.pos });
210       } else if (this._isAlpha(char)) {
211         result.push(this._processIdentifier());
212         this._skipNonTokens();
213         const peekChar = this.buf.charAt(this.pos);
214         if (this._opTable[peekChar] === undefined) {
215           result.push((peekChar !== '\'' && peekChar !== '"')
216             ? this._processString(null)
217             : this._processString(peekChar));
218         }
219       } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') {
220         this._processLineComment();
221       } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') {
222         this._processBlockComment();
223       } else {
224         throw Error('Token error at ' + this.pos + ' ' + this.buf[this.pos]);
225       }
226       this._skipNonTokens();
227     }
228     return result;
229   }
230
231   public tokenize2(): Statement {
232     let stack: Statement[] = [{ key: 'ROOT', sub: [] }];
233     let current: Statement | null = null;
234
235     this._skipNonTokens();
236     while (this.pos < this.buf.length) {
237
238       const char = this.buf.charAt(this.pos);
239       const op = this._opTable[char];
240
241       if (op !== undefined) {
242         if (op === 'L_BRACE') {
243           current && stack.unshift(current);
244           current = null;
245         } else if (op === 'R_BRACE') {
246           current = stack.shift() || null;
247         }
248         this.pos++;
249       } else if (this._isAlpha(char) || char === '_') {
250         const key = this._processIdentifier().value;
251         this._skipNonTokens();
252         let peekChar = this.buf.charAt(this.pos);
253         let arg = undefined;
254         if (this._opTable[peekChar] === undefined) {
255           arg = (peekChar === '"' || peekChar === '\'')
256             ? this._processString(peekChar).value
257             : this._processString(null).value;
258         }
259         do {
260           this._skipNonTokens();
261           peekChar = this.buf.charAt(this.pos);
262           if (peekChar !== '+') break;
263           this.pos++;
264           this._skipNonTokens();
265           peekChar = this.buf.charAt(this.pos);
266           arg += (peekChar === '"' || peekChar === '\'')
267             ? this._processString(peekChar).value
268             : this._processString(null).value;
269         } while (true);
270         current = { key, arg, sub: [] };
271         stack[0].sub!.push(current);
272       } else if (char === '/' && this.buf.charAt(this.pos + 1) === '/') {
273         this._processLineComment();
274       } else if (char === '/' && this.buf.charAt(this.pos + 1) === '*') {
275         this._processBlockComment();
276       } else {
277         throw Error('Token error at ' + this.pos + ' ' + this.buf.slice(this.pos - 10, this.pos + 10));
278       }
279       this._skipNonTokens();
280     }
281     if (stack[0].key !== 'ROOT' || !stack[0].sub![0]) {
282       throw new Error('Internal Perser Error');
283     }
284     return stack[0].sub![0];
285   }
286 }
287
288 export class YangParser {
289   private _groupingsToResolve: ViewSpecification[] = [];
290
291   private _typeRefToResolve: (() => void)[] = [];
292
293   private _identityToResolve: (() => void)[] = [];
294
295   private _unionsToResolve: (() => void)[] = [];
296
297   private _modulesToResolve: (() => void)[] = [];
298
299   private _modules: { [name: string]: Module } = {};
300
301   private _views: ViewSpecification[] = [{
302     id: '0',
303     name: 'root',
304     language: 'en-US',
305     canEdit: false,
306     config: true,
307     parentView: '0',
308     title: 'root',
309     elements: {},
310   }];
311
312   public static ResolveStack = Symbol('ResolveStack');
313
314   constructor(
315     private nodeId: string,
316     private _capabilityRevisionMap: { [capability: string]: string } = {},
317     private _unavailableCapabilities: { failureReason: string; capability: string }[] = [],
318     private _importOnlyModules: { name: string; revision: string }[] = [],
319   ) {
320
321   }
322
323   public get modules() {
324     return this._modules;
325   }
326
327   public get views() {
328     return this._views;
329   }
330
331   public async addCapability(capability: string, version?: string, parentImportOnlyModule?: boolean) {
332     // do not add twice
333     const existingCapability = this._modules[capability];
334     const latestVersionExisting = existingCapability && Object.keys(existingCapability.revisions).sort().reverse()[0];
335     if ((latestVersionExisting && version) && (version <= latestVersionExisting)) {
336       if (LOGLEVEL == LogLevel.Warning) {
337         console.warn(`Skipped capability: ${capability}:${version || ''} since already contained.`);
338       }
339       return;
340     }
341
342     // // do not add unavailable capabilities
343     // if (this._unavailableCapabilities.some(c => c.capability === capability)) {
344     //   // console.warn(`Skipped capability: ${capability} since it is marked as unavailable.` );
345     //   return;
346     // }
347
348     const data = await yangService.getCapability(capability, this.nodeId, version);
349     if (!data) {
350       throw new Error(`Could not load yang file for ${capability}:${version || ''}.`);
351     }
352
353     const rootStatement = new YangLexer(data).tokenize2();
354
355     if (rootStatement.key !== 'module') {
356       throw new Error(`Root element of ${capability} is not a module.`);
357     }
358     if (rootStatement.arg !== capability) {
359       throw new Error(`Root element capability ${rootStatement.arg} does not requested ${capability}.`);
360     }
361
362     const isUnavailable = this._unavailableCapabilities.some(c => c.capability === capability);
363     const isImportOnly = parentImportOnlyModule === true || this._importOnlyModules.some(c => c.name === capability);
364
365     // extract revisions
366     const revisions = this.extractNodes(rootStatement, 'revision').reduce<{ [version: string]: {} }>((acc, revision) => {
367       if (!revision.arg) {
368         throw new Error(`Module [${rootStatement.arg}] has a version w/o version number.`);
369       }
370       const description = this.extractValue(revision, 'description');
371       const reference = this.extractValue(revision, 'reference');
372       acc[revision.arg] = {
373         description,
374         reference,
375       };
376       return acc;
377     }, {});
378
379     const latestVersionLoaded = Object.keys(revisions).sort().reverse()[0];
380     if (existingCapability && latestVersionExisting >= latestVersionLoaded) {
381       if (LOGLEVEL == LogLevel.Warning) {
382         console.warn(`Skipped capability: ${capability}:${latestVersionLoaded} since ${capability}:${latestVersionExisting} already contained.`);
383       }
384       return;
385     }
386     
387     const module = this._modules[capability] = {
388       name: rootStatement.arg,
389       revisions,
390       imports: {},
391       features: {},
392       identities: {},
393       augments: {},
394       groupings: {},
395       typedefs: {},
396       views: {},
397       elements: {},
398       state: isUnavailable
399         ? ModuleState.unavailable
400         : isImportOnly
401           ? ModuleState.importOnly
402           : ModuleState.stable,
403     };
404
405     await this.handleModule(module, rootStatement, capability);
406   }
407
408   private async handleModule(module: Module, rootStatement: Statement, capability: string) {
409
410     // extract namespace && prefix
411     module.namespace = this.extractValue(rootStatement, 'namespace');
412     module.prefix = this.extractValue(rootStatement, 'prefix');
413     if (module.prefix) {
414       module.imports[module.prefix] = capability;
415     }
416
417     // extract features
418     const features = this.extractNodes(rootStatement, 'feature');
419     module.features = {
420       ...module.features,
421       ...features.reduce<{ [version: string]: {} }>((acc, feature) => {
422         if (!feature.arg) {
423           throw new Error(`Module [${module.name}] has a feature w/o name.`);
424         }
425         const description = this.extractValue(feature, 'description');
426         acc[feature.arg] = {
427           description,
428         };
429         return acc;
430       }, {}),
431     };
432
433     // extract imports
434     const imports = this.extractNodes(rootStatement, 'import');
435     module.imports = {
436       ...module.imports,
437       ...imports.reduce<{ [key: string]: string }>((acc, imp) => {
438         const prefix = imp.sub && imp.sub.filter(s => s.key === 'prefix');
439         if (!imp.arg) {
440           throw new Error(`Module [${module.name}] has an import with neither name nor prefix.`);
441         }
442         acc[prefix && prefix.length === 1 && prefix[0].arg || imp.arg] = imp.arg;
443         return acc;
444       }, {}),
445     };
446
447     // import all required files and set module state 
448     if (imports) for (let ind = 0; ind < imports.length; ++ind) {
449       const moduleName = imports[ind].arg!;
450
451       const revision = this._capabilityRevisionMap[moduleName] || undefined;
452       await this.addCapability(moduleName, revision, module.state === ModuleState.importOnly);
453       const importedModule = this._modules[imports[ind].arg!];
454       if (importedModule && importedModule.state > ModuleState.stable) {
455         module.state = Math.max(module.state, ModuleState.instable);
456       }
457     }
458
459     this.extractTypeDefinitions(rootStatement, module, '');
460
461     this.extractIdentities(rootStatement, 0, module, '');
462
463     const groupings = this.extractGroupings(rootStatement, 0, module, '');
464     this._views.push(...groupings);
465
466     const augments = this.extractAugments(rootStatement, 0, module, '');
467     this._views.push(...augments);
468
469     // the default for config on module level is config = true;
470     const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, '');
471     this._views.push(currentView, ...subViews);
472
473     // create the root elements for this module
474     module.elements = currentView.elements;
475     this._modulesToResolve.push(() => {
476       Object.keys(module.elements).forEach(key => {
477         const viewElement = module.elements[key];
478         if (!(isViewElementObjectOrList(viewElement) || isViewElementRpc(viewElement))) {
479           console.error(new Error(`Module: [${module}]. Only Object, List or RPC are allowed on root level.`));
480         }
481         if (isViewElementObjectOrList(viewElement)) {
482           const viewIdIndex = Number(viewElement.viewId);
483           module.views[key] = this._views[viewIdIndex];
484         }
485
486         // add only the UI View if the module is available
487         if (module.state === ModuleState.stable || module.state === ModuleState.instable) this._views[0].elements[key] = module.elements[key];
488       });
489     });
490     return module;
491   }
492
493   public postProcess() {
494
495     // execute all post processes like resolving in proper order
496     this._unionsToResolve.forEach(cb => {
497       try { cb(); } catch (error) {
498         console.warn(error.message);
499       }
500     });
501
502     // process all groupings
503     this._groupingsToResolve.filter(vs => vs.uses && vs.uses[ResolveFunction]).forEach(vs => {
504       try { vs.uses![ResolveFunction] !== undefined && vs.uses![ResolveFunction]!('|'); } catch (error) {
505         console.warn(`Error resolving: [${vs.name}] [${error.message}]`);
506       }
507     });
508
509     /**
510      * This is to fix the issue for sequential execution of modules based on their child and parent relationship
511      * We are sorting the module object based on their augment status
512      */
513     Object.keys(this.modules)
514       .map(elem => {
515         if (this.modules[elem].augments && Object.keys(this.modules[elem].augments).length > 0) {
516           const { augments, ..._rest } = this.modules[elem];
517           const partsOfKeys = Object.keys(augments).map((key) => (key.split('/').length - 1));
518           this.modules[elem].executionOrder = Math.max(...partsOfKeys);
519         } else {
520           this.modules[elem].executionOrder = 0;
521         }
522       });
523
524     // process all augmentations / sort by namespace changes to ensure proper order 
525     Object.keys(this.modules).sort((a, b) => this.modules[a].executionOrder! - this.modules[b].executionOrder!).forEach(modKey => {
526       const module = this.modules[modKey];
527       const augmentKeysWithCounter = Object.keys(module.augments).map((key) => {
528         const pathParts = splitVPath(key, /(?:(?:([^\/\:]+):)?([^\/]+))/g);  // 1 = opt: namespace / 2 = property 
529         let nameSpaceChangeCounter = 0;
530         let currentNS = module.name; // init namespace
531         pathParts.forEach(([ns, _]) => {
532           if (ns === currentNS) {
533             currentNS = ns;
534             nameSpaceChangeCounter++;
535           }
536         });
537         return {
538           key,
539           nameSpaceChangeCounter,
540         };
541       });
542
543       const augmentKeys = augmentKeysWithCounter
544         .sort((a, b) => a.nameSpaceChangeCounter > b.nameSpaceChangeCounter ? 1 : a.nameSpaceChangeCounter === b.nameSpaceChangeCounter ? 0 : -1)
545         .map((a) => a.key);
546
547       augmentKeys.forEach(augKey => {
548         const augments = module.augments[augKey];
549         const viewSpec = this.resolveView(augKey);
550         if (!viewSpec) console.warn(`Could not find view to augment [${augKey}] in [${module.name}].`);
551         if (augments && viewSpec) {
552           augments.forEach(augment => Object.keys(augment.elements).forEach(key => {
553             const elm = augment.elements[key];
554             
555             const when = elm.when && augment.when
556               ? {
557                 type: WhenTokenType.AND,
558                 left: elm.when,
559                 right: augment.when,
560               }
561               : elm.when || augment.when;
562             
563             const ifFeature = elm.ifFeature
564               ? `(${augment.ifFeature}) and (${elm.ifFeature})`
565               : augment.ifFeature;
566             
567             viewSpec.elements[key] = {
568               ...augment.elements[key],
569               when,
570               ifFeature,
571             };
572           }));
573         }
574       });
575     });
576
577     // process Identities
578     const traverseIdentity = (identities: Identity[]) => {
579       const result: Identity[] = [];
580       for (let identity of identities) {
581         if (identity.children && identity.children.length > 0) {
582           result.push(...traverseIdentity(identity.children));
583         } else {
584           result.push(identity);
585         }
586       }
587       return result;
588     };
589
590     const baseIdentities: Identity[] = [];
591     Object.keys(this.modules).forEach(modKey => {
592       const module = this.modules[modKey];
593       Object.keys(module.identities).forEach(idKey => {
594         const identity = module.identities[idKey];
595         if (identity.base != null) {
596           const base = this.resolveIdentity(identity.base, module);
597           base.children?.push(identity);
598         } else {
599           baseIdentities.push(identity);
600         }
601       });
602     });
603     baseIdentities.forEach(identity => {
604       identity.values = identity.children && traverseIdentity(identity.children) || [];
605     });
606
607     this._identityToResolve.forEach(cb => {
608       try { cb(); } catch (error) {
609         console.warn(error.message);
610       }
611     });
612
613     this._typeRefToResolve.forEach(cb => {
614       try { cb(); } catch (error) {
615         console.warn(error.message);
616       }
617     });
618
619     this._modulesToResolve.forEach(cb => {
620       try { cb(); } catch (error) {
621         console.warn(error.message);
622       }
623     });
624
625     // resolve readOnly
626     const resolveReadOnly = (view: ViewSpecification, parentConfig: boolean) => {
627
628       // update view config
629       view.config = view.config && parentConfig;
630
631       Object.keys(view.elements).forEach((key) => {
632         const elm = view.elements[key];
633
634         // update element config
635         elm.config = elm.config && view.config;
636
637         // update all sub-elements of objects
638         if (elm.uiType === 'object') {
639           resolveReadOnly(this.views[+elm.viewId], elm.config);
640         }
641
642       });
643     };
644
645     const dump = resolveReadOnly(this.views[0], true);
646     if (LOGLEVEL > 2) {
647       console.log('Resolved views:', dump);
648     }
649   }
650
651   private _nextId = 1;
652
653   private get nextId() {
654     return this._nextId++;
655   }
656
657   private extractNodes(statement: Statement, key: string): Statement[] {
658     return statement.sub && statement.sub.filter(s => s.key === key) || [];
659   }
660
661   private extractValue(statement: Statement, key: string): string | undefined;
662   private extractValue(statement: Statement, key: string, parser: RegExp): RegExpExecArray | undefined;
663   private extractValue(statement: Statement, key: string, parser?: RegExp): string | RegExpExecArray | undefined {
664     const typeNodes = this.extractNodes(statement, key);
665     const rawValue = typeNodes.length > 0 && typeNodes[0].arg || undefined;
666     return parser
667       ? rawValue && parser.exec(rawValue) || undefined
668       : rawValue;
669   }
670
671   private extractTypeDefinitions(statement: Statement, module: Module, currentPath: string): void {
672     const typedefs = this.extractNodes(statement, 'typedef');
673     typedefs && typedefs.forEach(def => {
674       if (!def.arg) {
675         throw new Error(`Module: [${module.name}]. Found typedef without name.`);
676       }
677       module.typedefs[def.arg] = this.getViewElement(def, module, 0, currentPath, false);
678     });
679   }
680
681   /** Handles groupings like named Container */
682   private extractGroupings(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
683     const subViews: ViewSpecification[] = [];
684     const groupings = this.extractNodes(statement, 'grouping');
685     if (groupings && groupings.length > 0) {
686       subViews.push(...groupings.reduce<ViewSpecification[]>((acc, cur) => {
687         if (!cur.arg) {
688           throw new Error(`Module: [${module.name}][${currentPath}]. Found grouping without name.`);
689         }
690         const grouping = cur.arg;
691
692         // the default for config on module level is config = true;
693         const [currentView, currentSubViews] = this.extractSubViews(cur, /* parentId */ -1, module, currentPath);
694         grouping && (module.groupings[grouping] = currentView);
695         acc.push(currentView, ...currentSubViews);
696         return acc;
697       }, []));
698     }
699
700     return subViews;
701   }
702
703   /** Handles augments also like named container */
704   private extractAugments(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
705     const subViews: ViewSpecification[] = [];
706     const augments = this.extractNodes(statement, 'augment');
707     if (augments && augments.length > 0) {
708       subViews.push(...augments.reduce<ViewSpecification[]>((acc, cur) => {
709         if (!cur.arg) {
710           throw new Error(`Module: [${module.name}][${currentPath}]. Found augment without path.`);
711         }
712         const augment = this.resolveReferencePath(cur.arg, module);
713
714         // the default for config on module level is config = true;
715         const [currentView, currentSubViews] = this.extractSubViews(cur, parentId, module, currentPath);
716         if (augment) {
717           module.augments[augment] = module.augments[augment] || [];
718           module.augments[augment].push(currentView);
719         }
720         acc.push(currentView, ...currentSubViews);
721         return acc;
722       }, []));
723     }
724
725     return subViews;
726   }
727
728   /** Handles identities  */
729   private extractIdentities(statement: Statement, parentId: number, module: Module, currentPath: string) {
730     const identities = this.extractNodes(statement, 'identity');
731     module.identities = identities.reduce<{ [name: string]: Identity }>((acc, cur) => {
732       if (!cur.arg) {
733         throw new Error(`Module: [${module.name}][${currentPath}]. Found identity without name.`);
734       }
735       acc[cur.arg] = {
736         id: `${module.name}:${cur.arg}`,
737         label: cur.arg,
738         base: this.extractValue(cur, 'base'),
739         description: this.extractValue(cur, 'description'),
740         reference: this.extractValue(cur, 'reference'),
741         children: [],
742       };
743       return acc;
744     }, {});
745   }
746
747   // Hint: use 0 as parentId for rootElements and -1 for rootGroupings.
748   private extractSubViews(statement: Statement, parentId: number, module: Module, currentPath: string): [ViewSpecification, ViewSpecification[]] {
749     // used for scoped definitions
750     const context: Module = {
751       ...module,
752       typedefs: {
753         ...module.typedefs,
754       },
755     };
756
757     const currentId = this.nextId;
758     const subViews: ViewSpecification[] = [];
759     let elements: ViewElement[] = [];
760
761     const configValue = this.extractValue(statement, 'config');
762     const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false';
763
764     // extract conditions
765     const ifFeature = this.extractValue(statement, 'if-feature');
766     const whenCondition = this.extractValue(statement, 'when');
767     if (whenCondition) console.warn('Found in [' + context.name + ']' + currentPath + ' when: ' + whenCondition);
768
769     // extract all scoped typedefs
770     this.extractTypeDefinitions(statement, context, currentPath);
771
772     // extract all scoped groupings
773     subViews.push(
774       ...this.extractGroupings(statement, parentId, context, currentPath),
775     );
776
777     // extract all container
778     const container = this.extractNodes(statement, 'container');
779     if (container && container.length > 0) {
780       subViews.push(...container.reduce<ViewSpecification[]>((acc, cur) => {
781         if (!cur.arg) {
782           throw new Error(`Module: [${context.name}]${currentPath}. Found container without name.`);
783         }
784         const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
785         elements.push({
786           id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
787           label: cur.arg,
788           path: currentPath,
789           module: context.name || module.name || '',
790           uiType: 'object',
791           viewId: currentView.id,
792           config: currentView.config,
793         });
794         acc.push(currentView, ...currentSubViews);
795         return acc;
796       }, []));
797     }
798
799     // process all lists
800     // a list is a list of containers with the leafs contained in the list
801     const lists = this.extractNodes(statement, 'list');
802     if (lists && lists.length > 0) {
803       subViews.push(...lists.reduce<ViewSpecification[]>((acc, cur) => {
804         let elmConfig = config;
805         if (!cur.arg) {
806           throw new Error(`Module: [${context.name}]${currentPath}. Found list without name.`);
807         }
808         const key = this.extractValue(cur, 'key') || undefined;
809         if (elmConfig && !key) {
810           console.warn(`Module: [${context.name}]${currentPath}. Found configurable list without key. Assume config shell be false.`);
811           elmConfig = false;
812         }
813         const [currentView, currentSubViews] = this.extractSubViews(cur, currentId, context, `${currentPath}/${context.name}:${cur.arg}`);
814         elements.push({
815           id: parentId === 0 ? `${context.name}:${cur.arg}` : cur.arg,
816           label: cur.arg,
817           path: currentPath,
818           module: context.name || module.name || '',
819           isList: true,
820           uiType: 'object',
821           viewId: currentView.id,
822           key: key,
823           config: elmConfig && currentView.config,
824         });
825         acc.push(currentView, ...currentSubViews);
826         return acc;
827       }, []));
828     }
829
830     // process all leaf-lists
831     // a leaf-list is a list of some type
832     const leafLists = this.extractNodes(statement, 'leaf-list');
833     if (leafLists && leafLists.length > 0) {
834       elements.push(...leafLists.reduce<ViewElement[]>((acc, cur) => {
835         const element = this.getViewElement(cur, context, parentId, currentPath, true);
836         element && acc.push(element);
837         return acc;
838       }, []));
839     }
840
841     // process all leafs
842     // a leaf is mainly a property of an object
843     const leafs = this.extractNodes(statement, 'leaf');
844     if (leafs && leafs.length > 0) {
845       elements.push(...leafs.reduce<ViewElement[]>((acc, cur) => {
846         const element = this.getViewElement(cur, context, parentId, currentPath, false);
847         element && acc.push(element);
848         return acc;
849       }, []));
850     }
851
852
853     const choiceStms = this.extractNodes(statement, 'choice');
854     if (choiceStms && choiceStms.length > 0) {
855       elements.push(...choiceStms.reduce<ViewElementChoice[]>((accChoice, curChoice) => {
856         if (!curChoice.arg) {
857           throw new Error(`Module: [${context.name}]${currentPath}. Found choise without name.`);
858         }
859         // extract all cases like containers
860         const cases: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[] = [];
861         const caseStms = this.extractNodes(curChoice, 'case');
862         if (caseStms && caseStms.length > 0) {
863           cases.push(...caseStms.reduce((accCase, curCase) => {
864             if (!curCase.arg) {
865               throw new Error(`Module: [${context.name}]${currentPath}/${curChoice.arg}. Found case without name.`);
866             }
867             const description = this.extractValue(curCase, 'description') || undefined;
868             const [caseView, caseSubViews] = this.extractSubViews(curCase, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`);
869             subViews.push(caseView, ...caseSubViews);
870
871             const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = {
872               id: parentId === 0 ? `${context.name}:${curCase.arg}` : curCase.arg,
873               label: curCase.arg,
874               description: description,
875               elements: caseView.elements,
876             };
877             accCase.push(caseDef);
878             return accCase;
879           }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[]));
880         }
881
882         // extract all simple cases (one case per leaf, container, etc.)
883         const [choiceView, choiceSubViews] = this.extractSubViews(curChoice, parentId, context, `${currentPath}/${context.name}:${curChoice.arg}`);
884         subViews.push(choiceView, ...choiceSubViews);
885         cases.push(...Object.keys(choiceView.elements).reduce((accElm, curElm) => {
886           const elm = choiceView.elements[curElm];
887           const caseDef: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } = {
888             id: elm.id,
889             label: elm.label,
890             description: elm.description,
891             elements: { [elm.id]: elm },
892           };
893           accElm.push(caseDef);
894           return accElm;
895         }, [] as { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } }[]));
896
897         const choiceDescription = this.extractValue(curChoice, 'description') || undefined;
898         const choiceConfigValue = this.extractValue(curChoice, 'config');
899         const choiceConfig = choiceConfigValue == null ? true : choiceConfigValue.toLocaleLowerCase() !== 'false';
900
901         const mandatory = this.extractValue(curChoice, 'mandatory') === 'true' || false;
902
903         const element: ViewElementChoice = {
904           uiType: 'choice',
905           id: parentId === 0 ? `${context.name}:${curChoice.arg}` : curChoice.arg,
906           label: curChoice.arg,
907           path: currentPath,
908           module: context.name || module.name || '',
909           config: choiceConfig,
910           mandatory: mandatory,
911           description: choiceDescription,
912           cases: cases.reduce((acc, cur) => {
913             acc[cur.id] = cur;
914             return acc;
915           }, {} as { [name: string]: { id: string; label: string; description?: string; elements: { [name: string]: ViewElement } } }),
916         };
917
918         accChoice.push(element);
919         return accChoice;
920       }, []));
921     }
922
923     const rpcStms = this.extractNodes(statement, 'rpc');
924     if (rpcStms && rpcStms.length > 0) {
925       elements.push(...rpcStms.reduce<ViewElementRpc[]>((accRpc, curRpc) => {
926         if (!curRpc.arg) {
927           throw new Error(`Module: [${context.name}]${currentPath}. Found rpc without name.`);
928         }
929
930         const rpcDescription = this.extractValue(curRpc, 'description') || undefined;
931         const rpcConfigValue = this.extractValue(curRpc, 'config');
932         const rpcConfig = rpcConfigValue == null ? true : rpcConfigValue.toLocaleLowerCase() !== 'false';
933
934         let inputViewId: string | undefined = undefined;
935         let outputViewId: string | undefined = undefined;
936
937         const input = this.extractNodes(curRpc, 'input') || undefined;
938         const output = this.extractNodes(curRpc, 'output') || undefined;
939
940         if (input && input.length > 0) {
941           const [inputView, inputSubViews] = this.extractSubViews(input[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
942           subViews.push(inputView, ...inputSubViews);
943           inputViewId = inputView.id;
944         }
945
946         if (output && output.length > 0) {
947           const [outputView, outputSubViews] = this.extractSubViews(output[0], parentId, context, `${currentPath}/${context.name}:${curRpc.arg}`);
948           subViews.push(outputView, ...outputSubViews);
949           outputViewId = outputView.id;
950         }
951
952         const element: ViewElementRpc = {
953           uiType: 'rpc',
954           id: parentId === 0 ? `${context.name}:${curRpc.arg}` : curRpc.arg,
955           label: curRpc.arg,
956           path: currentPath,
957           module: context.name || module.name || '',
958           config: rpcConfig,
959           description: rpcDescription,
960           inputViewId: inputViewId,
961           outputViewId: outputViewId,
962         };
963
964         accRpc.push(element);
965
966         return accRpc;
967       }, []));
968     }
969
970     if (!statement.arg) {
971       console.error(new Error(`Module: [${context.name}]. Found statement without name.`));
972     }
973
974     let whenParsed: WhenAST | undefined = undefined;
975     try {
976       whenParsed = whenCondition && parseWhen(whenCondition) || undefined;
977     } catch (e) {
978       console.error(new Error(`Module: [${context.name}]. Found invalid when condition: ${whenCondition}`));
979     }
980
981     const viewSpec: ViewSpecification = {
982       id: String(currentId),
983       parentView: String(parentId),
984       ns: context.name,
985       name: statement.arg != null ? statement.arg : undefined,
986       title: statement.arg != null ? statement.arg : undefined,
987       language: 'en-us',
988       canEdit: false,
989       config: config,
990       ifFeature: ifFeature,
991       when: whenParsed,
992       elements: elements.reduce<{ [name: string]: ViewElement }>((acc, cur) => {
993         acc[cur.id] = cur;
994         return acc;
995       }, {}),
996     };
997
998     // evaluate canEdit depending on all conditions
999     Object.defineProperty(viewSpec, 'canEdit', {
1000       get: () => {
1001         return Object.keys(viewSpec.elements).some(key => {
1002           const elm = viewSpec.elements[key];
1003           return (!isViewElementObjectOrList(elm) && elm.config);
1004         });
1005       },
1006     });
1007
1008     // merge in all uses references and resolve groupings
1009     const usesRefs = this.extractNodes(statement, 'uses');
1010     if (usesRefs && usesRefs.length > 0) {
1011
1012       viewSpec.uses = (viewSpec.uses || []);
1013       const resolveFunctions: ((parentElementPath: string) => void)[] = [];
1014
1015       for (let i = 0; i < usesRefs.length; ++i) {
1016         const groupingName = usesRefs[i].arg;
1017         if (!groupingName) {
1018           throw new Error(`Module: [${context.name}]. Found an uses statement without a grouping name.`);
1019         }
1020
1021         viewSpec.uses.push(this.resolveReferencePath(groupingName, context));
1022
1023         resolveFunctions.push((parentElementPath: string) => {
1024           const groupingViewSpec = this.resolveGrouping(groupingName, context);
1025           if (groupingViewSpec) {
1026
1027             // resolve recursive
1028             const resolveFunc = groupingViewSpec.uses && groupingViewSpec.uses[ResolveFunction];
1029             resolveFunc && resolveFunc(parentElementPath);
1030
1031             Object.keys(groupingViewSpec.elements).forEach(key => {
1032               const elm = groupingViewSpec.elements[key];
1033               // a useRef on root level need a namespace
1034               const resolvedWhen = elm.when && groupingViewSpec.when
1035                 ? {
1036                   type: WhenTokenType.AND,
1037                   left: elm.when,
1038                   right: groupingViewSpec.when,
1039                 }
1040                 : elm.when || groupingViewSpec.when;  
1041               
1042               const resolvedIfFeature = elm.ifFeature
1043                 ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})`
1044                 : groupingViewSpec.ifFeature;
1045
1046               viewSpec.elements[parentId === 0 ? `${module.name}:${key}` : key] = {
1047                 ...elm,
1048                 when: resolvedWhen,
1049                 ifFeature: resolvedIfFeature,
1050               };
1051             });
1052           }
1053         });
1054       }
1055
1056       viewSpec.uses[ResolveFunction] = (parentElementPath: string) => {
1057         const currentElementPath = `${parentElementPath} -> ${viewSpec.ns}:${viewSpec.name}`;
1058         resolveFunctions.forEach(resolve => {
1059           try {
1060             resolve(currentElementPath);
1061           } catch (error) {
1062             console.error(error);
1063           }
1064         });
1065         // console.log("Resolved "+currentElementPath, viewSpec);
1066         if (viewSpec?.uses) {
1067           viewSpec.uses[ResolveFunction] = undefined;
1068         }
1069       };
1070
1071       this._groupingsToResolve.push(viewSpec);
1072     }
1073
1074     return [viewSpec, subViews];
1075   }
1076
1077   // https://tools.ietf.org/html/rfc7950#section-9.3.4
1078   private static decimalRange = [
1079     { min: -9223372036854775808, max: 9223372036854775807 },
1080     { min: -922337203685477580.8, max: 922337203685477580.7 },
1081     { min: -92233720368547758.08, max: 92233720368547758.07 },
1082     { min: -9223372036854775.808, max: 9223372036854775.807 },
1083     { min: -922337203685477.5808, max: 922337203685477.5807 },
1084     { min: -92233720368547.75808, max: 92233720368547.75807 },
1085     { min: -9223372036854.775808, max: 9223372036854.775807 },
1086     { min: -922337203685.4775808, max: 922337203685.4775807 },
1087     { min: -92233720368.54775808, max: 92233720368.54775807 },
1088     { min: -9223372036.854775808, max: 9223372036.854775807 },
1089     { min: -922337203.6854775808, max: 922337203.6854775807 },
1090     { min: -92233720.36854775808, max: 92233720.36854775807 },
1091     { min: -9223372.036854775808, max: 9223372.036854775807 },
1092     { min: -922337.2036854775808, max: 922337.2036854775807 },
1093     { min: -92233.72036854775808, max: 92233.72036854775807 },
1094     { min: -9223.372036854775808, max: 9223.372036854775807 },
1095     { min: -922.3372036854775808, max: 922.3372036854775807 },
1096     { min: -92.23372036854775808, max: 92.23372036854775807 },
1097     { min: -9.223372036854775808, max: 9.223372036854775807 },
1098   ];
1099
1100   /** Extracts the UI View from the type in the cur statement. */
1101   private getViewElement(cur: Statement, module: Module, parentId: number, currentPath: string, isList: boolean): ViewElement {
1102
1103     const type = this.extractValue(cur, 'type');
1104     const defaultVal = this.extractValue(cur, 'default') || undefined;
1105     const description = this.extractValue(cur, 'description') || undefined;
1106
1107     const configValue = this.extractValue(cur, 'config');
1108     const config = configValue == null ? true : configValue.toLocaleLowerCase() !== 'false';
1109
1110     const extractRange = (min: number, max: number, property: string = 'range'): { expression: Expression<YangRange> | undefined; min: number; max: number } => {
1111       const ranges = this.extractValue(this.extractNodes(cur, 'type')[0]!, property) || undefined;
1112       const range = ranges?.replace(/min/i, String(min)).replace(/max/i, String(max)).split('|').map(r => {
1113         let minValue: number;
1114         let maxValue: number;
1115
1116         if (r.indexOf('..') > -1) {
1117           const [minStr, maxStr] = r.split('..');
1118           minValue = Number(minStr);
1119           maxValue = Number(maxStr);
1120         } else if (!isNaN(maxValue = Number(r && r.trim()))) {
1121           minValue = maxValue;
1122         } else {
1123           minValue = min,
1124           maxValue = max;
1125         }
1126
1127         if (minValue > min) min = minValue;
1128         if (maxValue < max) max = maxValue;
1129
1130         return {
1131           min: minValue,
1132           max: maxValue,
1133         };
1134       });
1135       return {
1136         min: min,
1137         max: max,
1138         expression: range && range.length === 1
1139           ? range[0]
1140           : range && range.length > 1
1141             ? { operation: 'OR', arguments: range }
1142             : undefined,
1143       };
1144     };
1145
1146     const extractPattern = (): Expression<RegExp> | undefined => {
1147     // 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
1148       const pattern = this.extractNodes(this.extractNodes(cur, 'type')[0]!, 'pattern').map(p => p.arg!).filter(p => !!p).map(p => `^${p/*.replace(/(?:\\(.))/g, '$1')*/}$`);
1149       return pattern && pattern.length == 1
1150         ? new RegExp(pattern[0])
1151         : pattern && pattern.length > 1
1152           ? { operation: 'AND', arguments: pattern.map(p => new RegExp(p)) }
1153           : undefined;
1154     };
1155
1156     const mandatory = this.extractValue(cur, 'mandatory') === 'true' || false;
1157
1158     if (!cur.arg) {
1159       throw new Error(`Module: [${module.name}]. Found element without name.`);
1160     }
1161
1162     if (!type) {
1163       throw new Error(`Module: [${module.name}].[${cur.arg}]. Found element without type.`);
1164     }
1165
1166     const element: ViewElementBase = {
1167       id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg,
1168       label: cur.arg,
1169       path: currentPath,
1170       module: module.name || '',
1171       config: config,
1172       mandatory: mandatory,
1173       isList: isList,
1174       default: defaultVal,
1175       description: description,
1176     };
1177
1178     if (type === 'string') {
1179       const length = extractRange(0, +18446744073709551615, 'length');
1180       return ({
1181         ...element,
1182         uiType: 'string',
1183         length: length.expression,
1184         pattern: extractPattern(),
1185       });
1186     } else if (type === 'boolean') {
1187       return ({
1188         ...element,
1189         uiType: 'boolean',
1190       });
1191     } else if (type === 'uint8') {
1192       const range = extractRange(0, +255);
1193       return ({
1194         ...element,
1195         uiType: 'number',
1196         range: range.expression,
1197         min: range.min,
1198         max: range.max,
1199         units: this.extractValue(cur, 'units') || undefined,
1200         format: this.extractValue(cur, 'format') || undefined,
1201       });
1202     } else if (type === 'uint16') {
1203       const range = extractRange(0, +65535);
1204       return ({
1205         ...element,
1206         uiType: 'number',
1207         range: range.expression,
1208         min: range.min,
1209         max: range.max,
1210         units: this.extractValue(cur, 'units') || undefined,
1211         format: this.extractValue(cur, 'format') || undefined,
1212       });
1213     } else if (type === 'uint32') {
1214       const range = extractRange(0, +4294967295);
1215       return ({
1216         ...element,
1217         uiType: 'number',
1218         range: range.expression,
1219         min: range.min,
1220         max: range.max,
1221         units: this.extractValue(cur, 'units') || undefined,
1222         format: this.extractValue(cur, 'format') || undefined,
1223       });
1224     } else if (type === 'uint64') {
1225       const range = extractRange(0, +18446744073709551615);
1226       return ({
1227         ...element,
1228         uiType: 'number',
1229         range: range.expression,
1230         min: range.min,
1231         max: range.max,
1232         units: this.extractValue(cur, 'units') || undefined,
1233         format: this.extractValue(cur, 'format') || undefined,
1234       });
1235     } else if (type === 'int8') {
1236       const range = extractRange(-128, +127);
1237       return ({
1238         ...element,
1239         uiType: 'number',
1240         range: range.expression,
1241         min: range.min,
1242         max: range.max,
1243         units: this.extractValue(cur, 'units') || undefined,
1244         format: this.extractValue(cur, 'format') || undefined,
1245       });
1246     } else if (type === 'int16') {
1247       const range = extractRange(-32768, +32767);
1248       return ({
1249         ...element,
1250         uiType: 'number',
1251         range: range.expression,
1252         min: range.min,
1253         max: range.max,
1254         units: this.extractValue(cur, 'units') || undefined,
1255         format: this.extractValue(cur, 'format') || undefined,
1256       });
1257     } else if (type === 'int32') {
1258       const range = extractRange(-2147483648, +2147483647);
1259       return ({
1260         ...element,
1261         uiType: 'number',
1262         range: range.expression,
1263         min: range.min,
1264         max: range.max,
1265         units: this.extractValue(cur, 'units') || undefined,
1266         format: this.extractValue(cur, 'format') || undefined,
1267       });
1268     } else if (type === 'int64') {
1269       const range = extractRange(-9223372036854775808, +9223372036854775807);
1270       return ({
1271         ...element,
1272         uiType: 'number',
1273         range: range.expression,
1274         min: range.min,
1275         max: range.max,
1276         units: this.extractValue(cur, 'units') || undefined,
1277         format: this.extractValue(cur, 'format') || undefined,
1278       });
1279     } else if (type === 'decimal64') {
1280       // decimalRange
1281       const fDigits = Number(this.extractValue(this.extractNodes(cur, 'type')[0]!, 'fraction-digits')) || -1;
1282       if (fDigits === -1) {
1283         throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found decimal64 with invalid fraction-digits.`);
1284       }
1285       const range = extractRange(YangParser.decimalRange[fDigits].min, YangParser.decimalRange[fDigits].max);
1286       return ({
1287         ...element,
1288         uiType: 'number',
1289         fDigits: fDigits,
1290         range: range.expression,
1291         min: range.min,
1292         max: range.max,
1293         units: this.extractValue(cur, 'units') || undefined,
1294         format: this.extractValue(cur, 'format') || undefined,
1295       });
1296     } else if (type === 'enumeration') {
1297       const typeNode = this.extractNodes(cur, 'type')[0]!;
1298       const enumNodes = this.extractNodes(typeNode, 'enum');
1299       return ({
1300         ...element,
1301         uiType: 'selection',
1302         options: enumNodes.reduce<{ key: string; value: string; description?: string }[]>((acc, enumNode) => {
1303           if (!enumNode.arg) {
1304             throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found option without name.`);
1305           }
1306           // const ifClause = this.extractValue(enumNode, 'if-feature');
1307           const value = this.extractValue(enumNode, 'value');
1308           const enumOption = {
1309             key: enumNode.arg,
1310             value: value != null ? value : enumNode.arg,
1311             description: this.extractValue(enumNode, 'description') || undefined,
1312           };
1313           // todo: ❗ handle the if clause ⚡
1314           acc.push(enumOption);
1315           return acc;
1316         }, []),
1317       });
1318     } else if (type === 'leafref') {
1319       const typeNode = this.extractNodes(cur, 'type')[0]!;
1320       const vPath = this.extractValue(typeNode, 'path');
1321       if (!vPath) {
1322         throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found leafref without path.`);
1323       }
1324       const refPath = this.resolveReferencePath(vPath, module);
1325       const resolve = this.resolveReference.bind(this);
1326       const res: ViewElement = {
1327         ...element,
1328         uiType: 'reference',
1329         referencePath: refPath,
1330         ref(this: ViewElement, basePath: string) {
1331           const elementPath = `${basePath}/${cur.arg}`;
1332
1333           const result = resolve(refPath, elementPath);
1334           if (!result) return undefined;
1335
1336           const [resolvedElement, resolvedPath] = result;
1337           return resolvedElement && [{
1338             ...resolvedElement,
1339             id: this.id,
1340             label: this.label,
1341             config: this.config,
1342             mandatory: this.mandatory,
1343             isList: this.isList,
1344             default: this.default,
1345             description: this.description,
1346           } as ViewElement, resolvedPath] || undefined;
1347         },
1348       };
1349       return res;
1350     } else if (type === 'identityref') {
1351       const typeNode = this.extractNodes(cur, 'type')[0]!;
1352       const base = this.extractValue(typeNode, 'base');
1353       if (!base) {
1354         throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found identityref without base.`);
1355       }
1356       const res: ViewElement = {
1357         ...element,
1358         uiType: 'selection',
1359         options: [],
1360       };
1361       this._identityToResolve.push(() => {
1362         const identity: Identity = this.resolveIdentity(base, module);
1363         if (!identity) {
1364           throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Could not resolve identity [${base}].`);
1365         }
1366         if (!identity.values || identity.values.length === 0) {
1367           throw new Error(`Identity: [${base}] has no values.`);
1368         }
1369         res.options = identity.values.map(val => ({
1370           key: val.id,
1371           value: val.id,
1372           description: val.description,
1373         }));
1374       });
1375       return res;
1376     } else if (type === 'empty') {
1377       // todo: ❗ handle empty ⚡
1378       /*  9.11.  The empty Built-In Type
1379           The empty built-in type represents a leaf that does not have any
1380           value, it conveys information by its presence or absence. */
1381       return {
1382         ...element,
1383         uiType: 'empty',
1384       };
1385     } else if (type === 'union') {
1386       // todo: ❗ handle union ⚡
1387       /* 9.12.  The union Built-In Type */
1388       const typeNode = this.extractNodes(cur, 'type')[0]!;
1389       const typeNodes = this.extractNodes(typeNode, 'type');
1390
1391       const resultingElement = {
1392         ...element,
1393         uiType: 'union',
1394         elements: [],
1395       } as ViewElementUnion;
1396
1397       const resolveUnion = () => {
1398         resultingElement.elements.push(...typeNodes.map(node => {
1399           const stm: Statement = {
1400             ...cur,
1401             sub: [
1402               ...(cur.sub?.filter(s => s.key !== 'type') || []),
1403               node,
1404             ],
1405           };
1406           return {
1407             ...this.getViewElement(stm, module, parentId, currentPath, isList),
1408             id: node.arg!,
1409           };
1410         }));
1411       };
1412
1413       this._unionsToResolve.push(resolveUnion);
1414
1415       return resultingElement;
1416     } else if (type === 'bits') {
1417       const typeNode = this.extractNodes(cur, 'type')[0]!;
1418       const bitNodes = this.extractNodes(typeNode, 'bit');
1419       return {
1420         ...element,
1421         uiType: 'bits',
1422         flags: bitNodes.reduce<{ [name: string]: number | undefined }>((acc, bitNode) => {
1423           if (!bitNode.arg) {
1424             throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found bit without name.`);
1425           }
1426           // const ifClause = this.extractValue(bitNode, 'if-feature');
1427           const pos = Number(this.extractValue(bitNode, 'position'));
1428           acc[bitNode.arg] = pos === pos ? pos : undefined;
1429           return acc;
1430         }, {}),
1431       };
1432     } else if (type === 'binary') {
1433       return {
1434         ...element,
1435         uiType: 'binary',
1436         length: extractRange(0, +18446744073709551615, 'length'),
1437       };
1438     } else if (type === 'instance-identifier') {
1439       // https://tools.ietf.org/html/rfc7950#page-168
1440       return {
1441         ...element,
1442         uiType: 'string',
1443         length: extractRange(0, +18446744073709551615, 'length'),
1444       };
1445     } else {
1446       // not a build in type, need to resolve type
1447       let typeRef = this.resolveType(type, module);
1448       if (typeRef == null) console.error(new Error(`Could not resolve type ${type} in [${module.name}][${currentPath}].`));
1449
1450       if (isViewElementString(typeRef)) {
1451         typeRef = this.resolveStringType(typeRef, extractPattern(), extractRange(0, +18446744073709551615));
1452       } else if (isViewElementNumber(typeRef)) {
1453         typeRef = this.resolveNumberType(typeRef, extractRange(typeRef.min, typeRef.max));
1454       }
1455
1456       const res = {
1457         id: element.id,
1458       } as ViewElement;
1459
1460       this._typeRefToResolve.push(() => {
1461         // spoof date type here from special string type
1462         if ((type === 'date-and-time' || type.endsWith(':date-and-time')) && typeRef.module === 'ietf-yang-types') {
1463           Object.assign(res, {
1464             ...typeRef,
1465             ...element,
1466             description: description,
1467             uiType: 'date',
1468           });
1469         } else {
1470           Object.assign(res, {
1471             ...typeRef,
1472             ...element,
1473             description: description,
1474           });
1475         }
1476       });
1477       
1478       return res;
1479     }
1480   }
1481
1482   private resolveStringType(parentElement: ViewElementString, pattern: Expression<RegExp> | undefined, length: { expression: Expression<YangRange> | undefined; min: number; max: number }) {
1483     return {
1484       ...parentElement,
1485       pattern: pattern != null && parentElement.pattern
1486         ? { operation: 'AND', arguments: [pattern, parentElement.pattern] }
1487         : parentElement.pattern
1488           ? parentElement.pattern
1489           : pattern,
1490       length: length.expression != null && parentElement.length
1491         ? { operation: 'AND', arguments: [length.expression, parentElement.length] }
1492         : parentElement.length
1493           ? parentElement.length
1494           : length?.expression,
1495     } as ViewElementString;
1496   }
1497
1498   private resolveNumberType(parentElement: ViewElementNumber, range: { expression: Expression<YangRange> | undefined; min: number; max: number }) {
1499     return {
1500       ...parentElement,
1501       range: range.expression != null && parentElement.range
1502         ? { operation: 'AND', arguments: [range.expression, parentElement.range] }
1503         : parentElement.range
1504           ? parentElement.range
1505           : range,
1506       min: range.min,
1507       max: range.max,
1508     } as ViewElementNumber;
1509   }
1510
1511   private resolveReferencePath(vPath: string, module: Module) {
1512     const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g; // 1 = opt: namespace / 2 = property
1513     return vPath.replace(vPathParser, (_, ns, property) => {
1514       const nameSpace = ns && module.imports[ns] || module.name;
1515       return `${nameSpace}:${property}`;
1516     });
1517   }
1518
1519   private resolveReference(vPath: string, currentPath: string) {
1520     const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
1521     let element: ViewElement | null = null;
1522     let moduleName = '';
1523
1524     const vPathParts = splitVPath(vPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] }));
1525     const resultPathParts = !vPath.startsWith('/')
1526       ? splitVPath(currentPath, vPathParser).map(p => { moduleName = p[1] || moduleName; return { ns: moduleName, property: p[2], ind: p[3] }; })
1527       : [];
1528
1529     for (let i = 0; i < vPathParts.length; ++i) {
1530       const vPathPart = vPathParts[i];
1531       if (vPathPart.property === '..') {
1532         resultPathParts.pop();
1533       } else if (vPathPart.property !== '.') {
1534         resultPathParts.push(vPathPart);
1535       }
1536     }
1537
1538     // resolve element by path
1539     for (let j = 0; j < resultPathParts.length; ++j) {
1540       const pathPart = resultPathParts[j];
1541       if (j === 0) {
1542         moduleName = pathPart.ns;
1543         const rootModule = this._modules[moduleName];
1544         if (!rootModule) throw new Error('Could not resolve module [' + moduleName + '].\r\n' + vPath);
1545         element = rootModule.elements[`${pathPart.ns}:${pathPart.property}`];
1546       } else if (element && isViewElementObjectOrList(element)) {
1547         const view: ViewSpecification = this._views[+element.viewId];
1548         if (moduleName !== pathPart.ns) {
1549           moduleName = pathPart.ns;
1550         }
1551         element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`];
1552       } else {
1553         throw new Error('Could not resolve reference.\r\n' + vPath);
1554       }
1555       if (!element) throw new Error('Could not resolve path [' + pathPart.property + '] in [' + currentPath + '] \r\n' + vPath);
1556     }
1557
1558     moduleName = ''; // create the vPath for the resolved element, do not add the element itself this will be done later in the res(...) function
1559     return [element, resultPathParts.slice(0, -1).map(p => `${moduleName !== p.ns ? `${moduleName = p.ns}:` : ''}${p.property}${p.ind || ''}`).join('/')];
1560   }
1561
1562   private resolveView(vPath: string) {
1563     const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g; // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
1564     let element: ViewElement | null = null;
1565     let partMatch: RegExpExecArray | null;
1566     let view: ViewSpecification | null = null;
1567     let moduleName = '';
1568     if (vPath) do {
1569       partMatch = vPathParser.exec(vPath);
1570       if (partMatch) {
1571         if (element === null) {
1572           moduleName = partMatch[1]!;
1573           const rootModule = this._modules[moduleName];
1574           if (!rootModule) return null;
1575           element = rootModule.elements[`${moduleName}:${partMatch[2]!}`];
1576         } else if (isViewElementObjectOrList(element)) {
1577           view = this._views[+element.viewId];
1578           if (moduleName !== partMatch[1]) {
1579             moduleName = partMatch[1];
1580             element = view.elements[`${moduleName}:${partMatch[2]}`];
1581           } else {
1582             element = view.elements[partMatch[2]];
1583           }
1584         } else {
1585           return null;
1586         }
1587         if (!element) return null;
1588       }
1589     } while (partMatch);
1590     return element && isViewElementObjectOrList(element) && this._views[+element.viewId] || null;
1591   }
1592
1593   private resolveType(type: string, module: Module) {
1594     const colonInd = type.indexOf(':');
1595     const preFix = colonInd > -1 ? type.slice(0, colonInd) : '';
1596     const typeName = colonInd > -1 ? type.slice(colonInd + 1) : type;
1597
1598     const res = preFix
1599       ? this._modules[module.imports[preFix]].typedefs[typeName]
1600       : module.typedefs[typeName];
1601     return res;
1602   }
1603
1604   private resolveGrouping(grouping: string, module: Module) {
1605     const collonInd = grouping.indexOf(':');
1606     const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : '';
1607     const groupingName = collonInd > -1 ? grouping.slice(collonInd + 1) : grouping;
1608
1609     return preFix
1610       ? this._modules[module.imports[preFix]].groupings[groupingName]
1611       : module.groupings[groupingName];
1612
1613   }
1614
1615   private resolveIdentity(identity: string, module: Module) {
1616     const collonInd = identity.indexOf(':');
1617     const preFix = collonInd > -1 ? identity.slice(0, collonInd) : '';
1618     const identityName = collonInd > -1 ? identity.slice(collonInd + 1) : identity;
1619
1620     return preFix
1621       ? this._modules[module.imports[preFix]].identities[identityName]
1622       : module.identities[identityName];
1623
1624   }
1625 }