2 import { Token, Statement, Module, Identity } from "../models/yang";
3 import { ViewSpecification, ViewElement, isViewElementObjectOrList, ViewElementBase, isViewElementReference } from "../models/uiModels";
4 import { yangService } from "../services/yangService";
6 export const splitVPath = (vPath: string, vPathParser: RegExp): RegExpMatchArray[] => {
7 const pathParts: RegExpMatchArray[] = [];
8 let partMatch: RegExpExecArray | null;
10 partMatch = vPathParser.exec(vPath);
12 pathParts.push(partMatch);
20 private pos: number = 0;
21 private buf: string = "";
23 constructor(input: string) {
28 private _optable: { [key: string]: string } = {
34 private _isNewline(char: string): boolean {
35 return char === '\r' || char === '\n';
38 private _isWhitespace(char: string): boolean {
39 return char === ' ' || char === '\t' || this._isNewline(char);
42 private _isDigit(char: string): boolean {
43 return char >= '0' && char <= '9';
46 private _isAlpha(char: string): boolean {
47 return (char >= 'a' && char <= 'z') ||
48 (char >= 'A' && char <= 'Z')
51 private _isAlphanum(char: string): boolean {
52 return this._isAlpha(char) || this._isDigit(char) ||
53 char === '_' || char === '-' || char === '.';
56 private _skipNontokens() {
57 while (this.pos < this.buf.length) {
58 const char = this.buf.charAt(this.pos);
59 if (this._isWhitespace(char)) {
67 private _processString(terminator: string | null): Token {
68 // this.pos points at the opening quote. Find the ending quote.
69 let end_index = this.pos + 1;
70 while (end_index < this.buf.length) {
71 const char = this.buf.charAt(end_index);
76 if (terminator === null && (this._isWhitespace(char) || this._optable[char] !== undefined) || char === terminator) {
82 if (end_index >= this.buf.length) {
83 throw Error('Unterminated quote at ' + this.pos);
85 const start = this.pos + (terminator ? 1 : 0);
86 const end = end_index;
89 value: this.buf.substring(start, end),
93 this.pos = terminator ? end + 1 : end;
98 private _processIdentifier(): Token {
99 let endpos = this.pos + 1;
100 while (endpos < this.buf.length &&
101 this._isAlphanum(this.buf.charAt(endpos))) {
107 value: this.buf.substring(this.pos, endpos),
115 private _processNumber(): Token {
116 let endpos = this.pos + 1;
117 while (endpos < this.buf.length &&
118 this._isDigit(this.buf.charAt(endpos))) {
124 value: this.buf.substring(this.pos, endpos),
132 private _processLineComment() {
133 var endpos = this.pos + 2;
134 // Skip until the end of the line
135 while (endpos < this.buf.length && !this._isNewline(this.buf.charAt(endpos))) {
138 this.pos = endpos + 1;
141 private _processBlockComment() {
142 var endpos = this.pos + 2;
143 // Skip until the end of the line
144 while (endpos < this.buf.length && !((this.buf.charAt(endpos) === "/" && this.buf.charAt(endpos - 1) === "*"))) {
147 this.pos = endpos + 1;
150 public tokenize(): Token[] {
151 const result: Token[] = [];
152 this._skipNontokens();
153 while (this.pos < this.buf.length) {
155 const char = this.buf.charAt(this.pos);
156 const op = this._optable[char];
158 if (op !== undefined) {
159 result.push({ name: op, value: char, start: this.pos, end: ++this.pos });
160 } else if (this._isAlpha(char)) {
161 result.push(this._processIdentifier());
162 this._skipNontokens();
163 const peekChar = this.buf.charAt(this.pos);
164 if (this._optable[peekChar] === undefined) {
165 result.push((peekChar !== "'" && peekChar !== '"')
166 ? this._processString(null)
167 : this._processString(peekChar));
169 } else if (char === '/' && this.buf.charAt(this.pos + 1) === "/") {
170 this._processLineComment();
171 } else if (char === '/' && this.buf.charAt(this.pos + 1) === "*") {
172 this._processBlockComment();
174 throw Error('Token error at ' + this.pos + " " + this.buf[this.pos]);
176 this._skipNontokens();
181 public tokenize2(): Statement {
182 let stack: Statement[] = [{ key: "ROOT", sub: [] }];
183 let current: Statement | null = null;
185 this._skipNontokens();
186 while (this.pos < this.buf.length) {
188 const char = this.buf.charAt(this.pos);
189 const op = this._optable[char];
191 if (op !== undefined) {
192 if (op === "L_BRACE") {
193 current && stack.unshift(current);
195 } else if (op === "R_BRACE") {
196 current = stack.shift() || null;
199 } else if (this._isAlpha(char)) {
200 const key = this._processIdentifier().value;
201 this._skipNontokens();
202 let peekChar = this.buf.charAt(this.pos);
204 if (this._optable[peekChar] === undefined) {
205 arg = (peekChar === '"' || peekChar === "'")
206 ? this._processString(peekChar).value
207 : this._processString(null).value;
210 this._skipNontokens();
211 peekChar = this.buf.charAt(this.pos);
212 if (peekChar !== "+") break;
214 this._skipNontokens();
215 peekChar = this.buf.charAt(this.pos);
216 arg += (peekChar === '"' || peekChar === "'")
217 ? this._processString(peekChar).value
218 : this._processString(null).value;
220 current = { key, arg, sub: [] };
221 stack[0].sub!.push(current);
222 } else if (char === '/' && this.buf.charAt(this.pos + 1) === "/") {
223 this._processLineComment();
224 } else if (char === '/' && this.buf.charAt(this.pos + 1) === "*") {
225 this._processBlockComment();
227 throw Error('Token error at ' + this.pos + " " + this.buf.slice(this.pos - 10, this.pos + 10));
229 this._skipNontokens();
231 if (stack[0].key !== "ROOT" || !stack[0].sub![0]) {
232 throw new Error("Internal Perser Error");
234 return stack[0].sub![0];
238 export class YangParser {
239 private _groupingsToResolve: (() => void)[] = [];
240 private _identityToResolve: (() => void)[] = [];
242 private _modules: { [name: string]: Module } = {};
243 private _views: ViewSpecification[] = [{
257 public get modules() {
258 return this._modules;
265 public async addCapability(capability: string, version?: string) {
267 if (this._modules[capability]) {
271 const data = await yangService.getCapability(capability, version);
273 throw new Error(`Could not load yang file for ${capability}.`);
276 const rootStatement = new YangLexer(data).tokenize2();
278 if (rootStatement.key !== "module") {
279 throw new Error(`Root element of ${capability} is not a module.`);
281 if (rootStatement.arg !== capability) {
282 throw new Error(`Root element capability ${rootStatement.arg} does not requested ${capability}.`);
285 const module = this._modules[capability] = {
286 name: rootStatement.arg,
298 await this.handleModule(module, rootStatement, capability);
301 private async handleModule(module: Module, rootStatement: Statement, capability: string) {
303 // extract namespace && prefix
304 module.namespace = this.extractValue(rootStatement, "namespace");
305 module.prefix = this.extractValue(rootStatement, "prefix");
307 module.imports[module.prefix] = capability;
311 const revisions = this.extractNodes(rootStatement, "revision");
314 ...revisions.reduce<{ [version: string]: { }}>((acc, version) => {
316 throw new Error(`Module [${module.name}] has a version w/o version number.`);
318 const description = this.extractValue(version, "description");
319 const reference = this.extractValue(version,"reference");
329 const features = this.extractNodes(rootStatement, "feature");
332 ...features.reduce<{ [version: string]: {} }>((acc, feature) => {
334 throw new Error(`Module [${module.name}] has a feature w/o name.`);
336 const description = this.extractValue(feature, "description");
345 const imports = this.extractNodes(rootStatement, "import");
348 ...imports.reduce < { [key: string]: string }>((acc, imp) => {
349 const prefix = imp.sub && imp.sub.filter(s => s.key === "prefix");
351 throw new Error(`Module [${module.name}] has an import with neither name nor prefix.`);
353 acc[prefix && prefix.length === 1 && prefix[0].arg || imp.arg] = imp.arg;
358 // import all required files
359 if (imports) for (let ind = 0; ind < imports.length; ++ind) {
360 await this.addCapability(imports[ind].arg!);
363 this.extractTypeDefinitions(rootStatement, module, "");
365 this.extractIdentites(rootStatement, 0, module, "");
367 const groupings = this.extractGroupings(rootStatement, 0, module, "");
368 this._views.push(...groupings);
370 const augments = this.extractAugments(rootStatement, 0, module, "");
371 this._views.push(...augments);
373 // the default for config on module level is config = true;
374 const [currentView, subViews] = this.extractSubViews(rootStatement, 0, module, "");
375 this._views.push(currentView, ...subViews);
377 // create the root elements for this module
378 module.elements = currentView.elements;
379 Object.keys(module.elements).forEach(key => {
380 const viewElement = module.elements[key];
381 if (!isViewElementObjectOrList(viewElement)) {
382 throw new Error(`Module: [${module}]. Only List or Object allowed on root level.`);
384 const viewIdIndex = Number(viewElement.viewId);
385 module.views[key] = this._views[viewIdIndex];
386 this._views[0].elements[key] = module.elements[key];
391 public postProcess() {
392 // process all groupings
393 // execute all post processes like resolving in propper order
394 this._groupingsToResolve.forEach(cb => {
395 try { cb(); } catch (error) {
396 console.warn(`Error resolving: [${error.message}]`);
400 // process all augmentations
401 Object.keys(this.modules).forEach(modKey => {
402 const module = this.modules[modKey];
403 Object.keys(module.augments).forEach(augKey => {
404 const augments = module.augments[augKey];
405 const viewSpec = this.resolveView(augKey);
406 if (!viewSpec) console.warn(`Could not find view to augment [${augKey}] in [${module.name}].`);
407 if (augments && viewSpec) {
408 augments.forEach(augment => Object.keys(augment.elements).forEach(key => {
409 const elm = augment.elements[key];
410 viewSpec.elements[key] = {
411 ...augment.elements[key],
412 when: elm.when ? `(${augment.when}) and (${elm.when})` : augment.when,
413 ifFeature: elm.ifFeature ? `(${augment.ifFeature}) and (${elm.ifFeature})` : augment.ifFeature,
420 // process Identities
421 const traverseIdentity = (identities : Identity[]) => {
422 const result: Identity[] = [];
423 for (let identity of identities) {
424 if (identity.children && identity.children.length > 0) {
425 result.push(...traverseIdentity(identity.children));
427 result.push(identity);
434 const baseIdentites: Identity[] = [];
435 Object.keys(this.modules).forEach(modKey => {
436 const module = this.modules[modKey];
437 Object.keys(module.identities).forEach(idKey => {
438 const identity = module.identities[idKey];
439 if (identity.base != null) {
440 const base = this.resolveIdentity(identity.base, module);
441 base.children?.push(identity);
443 baseIdentites.push(identity);
447 baseIdentites.forEach(identity => {
448 identity.values = identity.children && traverseIdentity(identity.children) || [];
451 this._identityToResolve.forEach(cb => {
452 try { cb(); } catch (error) {
453 console.warn(error.message);
460 private get nextId() {
461 return this._nextId++;
464 private extractNodes(statement: Statement, key: string): Statement[] {
465 return statement.sub && statement.sub.filter(s => s.key === key) || [];
468 private extractValue(statement: Statement, key: string): string | undefined;
469 private extractValue(statement: Statement, key: string, parser: RegExp): RegExpExecArray | undefined;
470 private extractValue(statement: Statement, key: string, parser?: RegExp): string | RegExpExecArray | undefined {
471 const typeNodes = this.extractNodes(statement, key);
472 const rawValue = typeNodes.length > 0 && typeNodes[0].arg || undefined;
474 ? rawValue && parser.exec(rawValue) || undefined
478 private extractTypeDefinitions(statement: Statement, module: Module, currentPath: string): void {
479 const typedefs = this.extractNodes(statement, "typedef");
480 typedefs && typedefs.forEach(def => {
482 throw new Error(`Module: [${module.name}]. Found typefed without name.`);
484 module.typedefs[def.arg] = this.getViewElement(def, module, 0, currentPath, false);
488 /** Handles Goupings like named Container */
489 private extractGroupings(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
490 const subViews: ViewSpecification[] = [];
491 const groupings = this.extractNodes(statement, "grouping");
492 if (groupings && groupings.length > 0) {
493 subViews.push(...groupings.reduce<ViewSpecification[]>((acc, cur) => {
495 throw new Error(`Module: [${module.name}][${currentPath}]. Found grouping without name.`);
497 const grouping = cur.arg;
499 // the default for config on module level is config = true;
500 const [currentView, subViews] = this.extractSubViews(cur, parentId, module, currentPath);
501 grouping && (module.groupings[grouping] = currentView);
502 acc.push(currentView, ...subViews);
510 /** Handles Augmants also like named Container */
511 private extractAugments(statement: Statement, parentId: number, module: Module, currentPath: string): ViewSpecification[] {
512 const subViews: ViewSpecification[] = [];
513 const augments = this.extractNodes(statement, "augment");
514 if (augments && augments.length > 0) {
515 subViews.push(...augments.reduce<ViewSpecification[]>((acc, cur) => {
517 throw new Error(`Module: [${module.name}][${currentPath}]. Found augment without path.`);
519 const augment = this.resolveReferencePath(cur.arg, module);
521 // the default for config on module level is config = true;
522 const [currentView, subViews] = this.extractSubViews(cur, parentId, module, currentPath);
524 module.augments[augment] = module.augments[augment] || [];
525 module.augments[augment].push(currentView);
527 acc.push(currentView, ...subViews);
535 /** Handles Identities */
536 private extractIdentites(statement: Statement, parentId: number, module: Module, currentPath: string) {
537 const identities = this.extractNodes(statement, "identity");
538 module.identities = identities.reduce<{ [name: string]: Identity }>((acc, cur) => {
540 throw new Error(`Module: [${module.name}][${currentPath}]. Found identiy without name.`);
543 id: `${module.name}:${cur.arg}`,
545 base: this.extractValue(cur, "base"),
546 description: this.extractValue(cur, "description"),
547 reference: this.extractValue(cur, "reference"),
554 private extractSubViews(statement: Statement, parentId: number, module: Module, currentPath: string): [ViewSpecification, ViewSpecification[]] {
555 const subViews: ViewSpecification[] = [];
556 const currentId = this.nextId;
557 let elements: ViewElement[] = [];
559 const configValue = this.extractValue(statement, "config");
560 const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
562 // extract conditions
563 const ifFeature = this.extractValue(statement, "if-feature");
564 const whenCondition = this.extractValue(statement, "when");
566 // extract all container
567 const container = this.extractNodes(statement, "container");
568 if (container && container.length > 0) {
569 subViews.push(...container.reduce<ViewSpecification[]>((acc, cur) => {
571 throw new Error(`Module: [${module.name}]. Found container without name.`);
573 const [currentView, subViews] = this.extractSubViews(cur, currentId, module, `${currentPath}/${module.name}:${cur.arg}`);
575 id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg,
578 viewId: currentView.id,
581 acc.push(currentView, ...subViews);
587 // a list is a list of containers with the leafs contained in the list
588 const lists = this.extractNodes(statement, "list");
589 if (lists && lists.length > 0) {
590 subViews.push(...lists.reduce<ViewSpecification[]>((acc, cur) => {
592 throw new Error(`Module: [${module.name}]. Found list without name.`);
594 const key = this.extractValue(cur, "key") || undefined;
595 if (config && !key) {
596 throw new Error(`Module: [${module.name}]. Found configurable list without key.`);
598 const [currentView, subViews] = this.extractSubViews(cur, currentId, module, `${currentPath}/${module.name}:${cur.arg}`);
600 id: parentId === 0 ? `${module.name}:${cur.arg}` : cur.arg,
604 viewId: currentView.id,
608 acc.push(currentView, ...subViews);
613 // process all leaf-lists
614 // a leaf-list is a list of some type
615 const leafLists = this.extractNodes(statement, "leaf-list");
616 if (leafLists && leafLists.length > 0) {
617 elements.push(...leafLists.reduce<ViewElement[]>((acc, cur) => {
618 const element = this.getViewElement(cur, module, parentId, currentPath, true);
619 element && acc.push(element);
625 // a leaf is mainly a property of an object
626 const leafs = this.extractNodes(statement, "leaf");
627 if (leafs && leafs.length > 0) {
628 elements.push(...leafs.reduce<ViewElement[]>((acc, cur) => {
629 const element = this.getViewElement(cur, module, parentId, currentPath, false);
630 element && acc.push(element);
636 const choiceStms = this.extractNodes(statement, "choice");
637 if (choiceStms && choiceStms.length > 0) {
638 for (let i = 0; i < choiceStms.length; ++i) {
639 const cases = this.extractNodes(choiceStms[i], "case");
640 console.warn(`Choice found ${choiceStms[i].arg}::${cases.map(c => c.arg).join(";")}`, choiceStms[i]);
644 const rpcs = this.extractNodes(statement, "rpc");
645 if (rpcs && rpcs.length > 0) {
649 if (!statement.arg) {
650 throw new Error(`Module: [${module.name}]. Found statement without name.`);
653 const viewSpec: ViewSpecification = {
654 id: String(currentId),
655 parentView: String(parentId),
657 title: statement.arg,
660 ifFeature: ifFeature,
662 elements: elements.reduce<{ [name: string]: ViewElement }>((acc, cur) => {
668 // evaluate canEdit depending on all conditions
669 Object.defineProperty(viewSpec, "canEdit", {
671 return Object.keys(viewSpec.elements).some(key => {
672 const elm = viewSpec.elements[key];
673 return (!isViewElementObjectOrList(elm) && elm.config);
678 // merge in all uses references and resolve groupings
679 const usesRefs = this.extractNodes(statement, "uses");
680 if (usesRefs && usesRefs.length > 0) {
682 viewSpec.uses = (viewSpec.uses || []);
683 for (let i = 0; i < usesRefs.length; ++i) {
684 const groupingName = usesRefs[i].arg;
686 throw new Error(`Module: [${module.name}]. Found an uses statement without a grouping name.`);
689 viewSpec.uses.push(this.resolveReferencePath(groupingName, module));
691 this._groupingsToResolve.push(() => {
692 const groupingViewSpec = this.resolveGrouping(groupingName, module);
693 if (groupingViewSpec) {
694 Object.keys(groupingViewSpec.elements).forEach(key => {
695 const elm = groupingViewSpec.elements[key];
696 viewSpec.elements[key] = {
697 ...groupingViewSpec.elements[key],
698 when: elm.when ? `(${groupingViewSpec.when}) and (${elm.when})` : groupingViewSpec.when,
699 ifFeature: elm.ifFeature ? `(${groupingViewSpec.ifFeature}) and (${elm.ifFeature})` : groupingViewSpec.ifFeature,
707 return [viewSpec, subViews];
710 /** Extracts the UI View from the type in the cur statement. */
711 private getViewElement(cur: Statement, module: Module, parentId: number, currentPath: string, isList: boolean): ViewElement {
713 const type = this.extractValue(cur, "type");
714 const defaultVal = this.extractValue(cur, "default") || undefined;
715 const description = this.extractValue(cur, "description") || undefined;
716 const rangeMatch = this.extractValue(cur, "range", /^(\d+)\.\.(\d+)/) || undefined;
718 const configValue = this.extractValue(cur, "config");
719 const config = configValue == null ? true : configValue.toLocaleLowerCase() !== "false";
721 const mandatory = this.extractValue(cur, "mandatory") === "true" || false;
724 throw new Error(`Module: [${module.name}]. Found element without name.`);
728 throw new Error(`Module: [${module.name}].[${cur.arg}]. Found element without type.`);
731 const element: ViewElementBase = {
732 id: parentId === 0 ? `${module.name}:${cur.arg}`: cur.arg,
735 mandatory: mandatory,
738 description: description
741 if (type === "string") {
745 pattern: this.extractNodes(this.extractNodes(cur, "type")[0]!, "pattern").map(p => p.arg!).filter(p => !!p),
747 } else if (type === "boolean") {
752 } else if (type === "uint8") {
756 min: rangeMatch ? Number(rangeMatch[0]) : 0,
757 max: rangeMatch ? Number(rangeMatch[1]) : +255,
758 units: this.extractValue(cur, "units") || undefined,
759 format: this.extractValue(cur, "format") || undefined,
761 } else if (type === "uint16") {
765 min: rangeMatch ? Number(rangeMatch[0]) : 0,
766 max: rangeMatch ? Number(rangeMatch[1]) : +65535,
767 units: this.extractValue(cur, "units") || undefined,
768 format: this.extractValue(cur, "format") || undefined,
770 } else if (type === "uint32") {
774 min: rangeMatch ? Number(rangeMatch[0]) : 0,
775 max: rangeMatch ? Number(rangeMatch[1]) : +4294967295,
776 units: this.extractValue(cur, "units") || undefined,
777 format: this.extractValue(cur, "format") || undefined,
779 } else if (type === "uint64") {
783 min: rangeMatch ? Number(rangeMatch[0]) : 0,
784 max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615,
785 units: this.extractValue(cur, "units") || undefined,
786 format: this.extractValue(cur, "format") || undefined,
788 } else if (type === "int8") {
792 min: rangeMatch ? Number(rangeMatch[0]) : -128,
793 max: rangeMatch ? Number(rangeMatch[1]) : +127,
794 units: this.extractValue(cur, "units") || undefined,
795 format: this.extractValue(cur, "format") || undefined,
797 } else if (type === "int16") {
801 min: rangeMatch ? Number(rangeMatch[0]) : -32768,
802 max: rangeMatch ? Number(rangeMatch[1]) : +32767,
803 units: this.extractValue(cur, "units") || undefined,
804 format: this.extractValue(cur, "format") || undefined,
806 } else if (type === "int32") {
810 min: rangeMatch ? Number(rangeMatch[0]) : -2147483648,
811 max: rangeMatch ? Number(rangeMatch[1]) : +2147483647,
812 units: this.extractValue(cur, "units") || undefined,
813 format: this.extractValue(cur, "format") || undefined,
815 } else if (type === "int64") {
819 min: rangeMatch ? Number(rangeMatch[0]) : 0,
820 max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615,
821 units: this.extractValue(cur, "units") || undefined,
822 format: this.extractValue(cur, "format") || undefined,
824 } else if (type === "decimal16") {
828 min: rangeMatch ? Number(rangeMatch[0]) : 0,
829 max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615,
830 units: this.extractValue(cur, "units") || undefined,
831 format: this.extractValue(cur, "format") || undefined,
832 fDigits: Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1
834 } else if (type === "decimal32") {
838 min: rangeMatch ? Number(rangeMatch[0]) : 0,
839 max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615,
840 units: this.extractValue(cur, "units") || undefined,
841 format: this.extractValue(cur, "format") || undefined,
842 fDigits: Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1
844 } else if (type === "decimal64") {
848 min: rangeMatch ? Number(rangeMatch[0]) : 0,
849 max: rangeMatch ? Number(rangeMatch[1]) : +18446744073709551615,
850 units: this.extractValue(cur, "units") || undefined,
851 format: this.extractValue(cur, "format") || undefined,
852 fDigits: Number(this.extractValue(this.extractNodes(cur, "type")[0]!, "fraction-digits")) || -1
854 } else if (type === "enumeration") {
855 const typeNode = this.extractNodes(cur, "type")[0]!;
856 const enumNodes = this.extractNodes(typeNode, "enum");
860 options: enumNodes.reduce<{ key: string; value: string; description?: string }[]>((acc, enumNode) => {
862 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found option without name.`);
864 const ifClause = this.extractValue(enumNode, "if-feature");
865 const value = this.extractValue(enumNode, "value");
868 value: value != null ? value : enumNode.arg,
869 description: this.extractValue(enumNode, "description") || undefined
871 // todo: ❗ handle the if clause ⚡
872 acc.push(enumOption);
876 } else if (type === "leafref") {
877 const typeNode = this.extractNodes(cur, "type")[0]!;
878 const vPath = this.extractValue(typeNode, "path");
880 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found leafref without path.`);
882 const refPath = this.resolveReferencePath(vPath, module);
883 const resolve = this.resolveReference.bind(this);
884 const res : ViewElement = {
887 referencePath: refPath,
888 ref(this: ViewElement, currentPath: string) {
889 const resolved = resolve(refPath, currentPath);
895 mandatory: this.mandatory,
897 default: this.default,
898 description: this.description,
903 } else if (type === "identityref") {
904 const typeNode = this.extractNodes(cur, "type")[0]!;
905 const base = this.extractValue(typeNode, "base");
907 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found identityref without base.`);
909 const res: ViewElement = {
914 this._identityToResolve.push(() => {
915 const identity : Identity = this.resolveIdentity(base, module);
917 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Could not resolve identity [${base}].`);
919 if (!identity.values || identity.values.length === 0) {
920 throw new Error(`Identity: [${base}] has no values.`);
922 res.options = identity.values.map(val => ({
925 description: val.description
929 } else if (type === "empty") {
930 // todo: ❗ handle empty ⚡
931 /* 9.11. The empty Built-In Type
932 The empty built-in type represents a leaf that does not have any
933 value, it conveys information by its presence or absence. */
934 console.warn(`found type: empty in [${module.name}][${currentPath}][${element.label}]`);
939 } else if (type === "union") {
940 // todo: ❗ handle union ⚡
941 /* 9.12. The union Built-In Type */
942 console.warn(`found type: union in [${module.name}][${currentPath}][${element.label}]`);
947 } else if (type === "bits") {
948 const typeNode = this.extractNodes(cur, "type")[0]!;
949 const bitNodes = this.extractNodes(typeNode, "bit");
953 flags: bitNodes.reduce<{[name: string]: number | undefined; }>((acc, bitNode) => {
955 throw new Error(`Module: [${module.name}][${currentPath}][${cur.arg}]. Found bit without name.`);
957 const ifClause = this.extractValue(bitNode, "if-feature");
958 const pos = Number(this.extractValue(bitNode, "position"));
959 acc[bitNode.arg] = pos === pos ? pos : undefined;
963 } else if (type === "binary") {
964 const typeNode = this.extractNodes(cur, "type")[0]!;
965 const length = Number(this.extractValue(typeNode, "length"));
969 length: length === length ? length : undefined
972 // not a build in type, have to resolve type
973 const typeRef = this.resolveType(type, module);
974 if (typeRef == null) console.error(new Error(`Could not resolve type ${type} in [${module.name}][${currentPath}].`));
978 description: description
983 private resolveReferencePath(vPath: string, module: Module) {
984 const vPathParser = /(?:(?:([^\/\:]+):)?([^\/]+))/g // 1 = opt: namespace / 2 = property
985 return vPath.replace(vPathParser, (_, ns, property) => {
986 const nameSpace = ns && module.imports[ns] || module.name;
987 return `${nameSpace}:${property}`;
992 private resolveReference(vPath: string, currentPath: string) {
993 const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
994 let element : ViewElement | null = null;
997 const vPathParts = splitVPath(vPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] }));
998 const resultPathParts = !vPath.startsWith("/")
999 ? splitVPath(currentPath, vPathParser).map(p => ({ ns: p[1], property: p[2], ind: p[3] }))
1002 for (let i = 0; i < vPathParts.length; ++i){
1003 const vPathPart = vPathParts[i];
1004 if (vPathPart.property === "..") {
1005 resultPathParts.pop();
1006 } else if (vPathPart.property !== ".") {
1007 resultPathParts.push(vPathPart);
1011 // resolve element by path
1012 for (let j = 0; j < resultPathParts.length;++j){
1013 const pathPart = resultPathParts[j];
1015 moduleName = pathPart.ns;
1016 const rootModule = this._modules[moduleName];
1017 if (!rootModule) throw new Error("Could not resolve module [" + moduleName +"].\r\n" + vPath);
1018 element = rootModule.elements[`${pathPart.ns}:${pathPart.property}`];
1019 } else if (element && isViewElementObjectOrList(element)) {
1020 const view: ViewSpecification = this._views[+element.viewId];
1021 if (moduleName !== pathPart.ns) {
1022 moduleName = pathPart.ns;
1023 element = view.elements[`${moduleName}:${pathPart.property}`];
1025 element = view.elements[pathPart.property] || view.elements[`${moduleName}:${pathPart.property}`];
1028 throw new Error("Could not resolve reference.\r\n" + vPath);
1030 if (!element) throw new Error("Could not resolve path [" + pathPart.property + "] in ["+ currentPath +"] \r\n" + vPath);
1036 private resolveView(vPath: string) {
1037 const vPathParser = /(?:(?:([^\/\[\]\:]+):)?([^\/\[\]]+)(\[[^\]]+\])?)/g // 1 = opt: namespace / 2 = property / 3 = opt: indexPath
1038 let element: ViewElement | null = null;
1039 let partMatch: RegExpExecArray | null;
1040 let view: ViewSpecification | null = null;
1041 let moduleName = "";
1043 partMatch = vPathParser.exec(vPath);
1045 if (element === null) {
1046 moduleName = partMatch[1]!;
1047 const rootModule = this._modules[moduleName];
1048 if (!rootModule) return null;
1049 element = rootModule.elements[`${moduleName}:${partMatch[2]!}`];
1050 } else if (isViewElementObjectOrList(element)) {
1051 view = this._views[+element.viewId];
1052 if (moduleName !== partMatch[1]) {
1053 moduleName = partMatch[1];
1054 element = view.elements[`${moduleName}:${partMatch[2]}`];
1056 element = view.elements[partMatch[2]];
1061 if (!element) return null;
1064 return element && isViewElementObjectOrList(element) && this._views[+element.viewId] || null;
1067 private resolveType(type: string, module: Module) {
1068 const collonInd = type.indexOf(":");
1069 const preFix = collonInd > -1 ? type.slice(0, collonInd) : "";
1070 const typeName = collonInd > -1 ? type.slice(collonInd + 1) : type;
1073 ? this._modules[module.imports[preFix]].typedefs[typeName]
1074 : module.typedefs[typeName];
1078 private resolveGrouping(grouping: string, module: Module) {
1079 const collonInd = grouping.indexOf(":");
1080 const preFix = collonInd > -1 ? grouping.slice(0, collonInd) : "";
1081 const groupingName = collonInd > -1 ? grouping.slice(collonInd + 1) : grouping;
1084 ? this._modules[module.imports[preFix]].groupings[groupingName]
1085 : module.groupings[groupingName];
1089 private resolveIdentity(identity: string, module: Module) {
1090 const collonInd = identity.indexOf(":");
1091 const preFix = collonInd > -1 ? identity.slice(0, collonInd) : "";
1092 const identityName = collonInd > -1 ? identity.slice(collonInd + 1) : identity;
1095 ? this._modules[module.imports[preFix]].identities[identityName]
1096 : module.identities[identityName];