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