UNPKG

@foxglove/omgidl-parser

Version:

Parse OMG IDL to flattened definitions for serialization

219 lines (200 loc) 7.75 kB
import { IDLNode } from "./IDLNode"; import { AnyIDLNode, IEnumIDLNode, IReferenceTypeIDLNode, IStructIDLNode, IUnionIDLNode, } from "./interfaces"; import { BaseASTNode, StructMemberASTNode, TypedefASTNode, UnresolvedConstantValue, } from "../astTypes"; import { SIMPLE_TYPES } from "../primitiveTypes"; type PossibleTypeRefNode = | IStructIDLNode | IReferenceTypeIDLNode<TypedefASTNode> | IEnumIDLNode | IUnionIDLNode; /** Class used for struct members and typedefs because they can reference each other and other types (enum and struct) * This class resolves the fields of these types to their final values. */ export abstract class ReferenceTypeIDLNode<T extends TypedefASTNode | StructMemberASTNode> extends IDLNode<T> implements IReferenceTypeIDLNode<T> { /** Indicates that it references another typedef, enum or struct. (ie: uses a non-builtin / simple type) */ private typeNeedsResolution = false; /** Used to hold an optional referenced node if needsResolution==true. Resolved/Set with `typeRef()` function. * Not meant to be used outside of `typeRef()` function. */ private typeRefNode?: PossibleTypeRefNode; constructor(scopePath: string[], astNode: T, idlMap: Map<string, AnyIDLNode>) { super(scopePath, astNode, idlMap); if (!SIMPLE_TYPES.has(astNode.type)) { this.typeNeedsResolution = true; } } get type(): string { if (this.typeNeedsResolution) { const parent = this.typeRef(); if (parent.declarator === "typedef" || parent.declarator === "enum") { return parent.type; } return parent.scopedIdentifier; } return this.astNode.type; } get isComplex(): boolean { if (!this.typeNeedsResolution) { return false; } const parent = this.typeRef(); if (parent.declarator === "typedef") { return parent.isComplex; } return parent.declarator === "struct" || parent.declarator === "union"; } get enumType(): string | undefined { if (this.typeNeedsResolution) { const parent = this.typeRef(); if (parent.declarator === "enum") { return parent.scopedIdentifier; } } return undefined; } get isArray(): boolean | undefined { let isArray = this.astNode.isArray; if (this.typeNeedsResolution) { const parent = this.typeRef(); if (parent.declarator === "typedef") { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing isArray ||= parent.isArray; } } return isArray; } get arrayLengths(): number[] | undefined { // Arraylengths are composed such that the prior arrayLengths describe the outermost arrays. // This means that the arrayLengths on the typedef should be pushed to the end as innermost arrayLengths. const arrayLengths = this.astNode.arrayLengths ? [...this.astNode.arrayLengths] : []; if (this.typeNeedsResolution) { const parent = this.typeRef(); if (parent.declarator === "typedef" && parent.arrayLengths) { arrayLengths.push(...parent.arrayLengths); } } const finalArrayLengths: number[] = []; // Resolve constant usage in arraylengths for (const arrayLength of arrayLengths) { const resolvedArrayLength = this.resolvePossibleNumericConstantUsage(arrayLength); // Shouldn't return undefined since the arrayLengths array should never include undefined if (resolvedArrayLength != undefined) { finalArrayLengths.push(resolvedArrayLength); } } return finalArrayLengths.length > 0 ? finalArrayLengths : undefined; } get arrayUpperBound(): number | undefined { let arrayUpperBound = undefined; if (this.typeNeedsResolution) { const parent = this.typeRef(); if (parent.declarator === "typedef") { arrayUpperBound = parent.arrayUpperBound; } } if (this.astNode.arrayUpperBound != undefined) { // Note: we forbid variable length array composition, so this won't be overriding the parent value arrayUpperBound = this.astNode.arrayUpperBound; } return this.resolvePossibleNumericConstantUsage(arrayUpperBound); } get upperBound(): number | undefined { let upperBound = undefined; if (this.typeNeedsResolution) { const parent = this.typeRef(); if (parent.declarator === "typedef") { upperBound = parent.upperBound; } } if (this.astNode.upperBound != undefined) { // Note: we forbid variable length array composition, so this won't be overriding the parent value upperBound = this.astNode.upperBound; } // Check for constant usage return this.resolvePossibleNumericConstantUsage(upperBound); } get annotations(): BaseASTNode["annotations"] { let annotations = undefined; if (this.typeNeedsResolution) { const parent = this.typeRef(); // We do not want to inherit annotations from a struct or enum if (parent.declarator === "typedef" && parent.annotations != undefined) { annotations = { ...parent.annotations }; } } if (this.astNode.annotations != undefined) { // prioritize the astNode annotations annotations = { ...annotations, ...this.astNode.annotations }; } return annotations; } private resolvePossibleNumericConstantUsage( astValue: UnresolvedConstantValue | number | undefined, ): number | undefined { if (typeof astValue === "number" || astValue == undefined) { return astValue; } const constantNodeIdentifier = astValue.name; const constantNodeValue = this.getConstantNode(constantNodeIdentifier).value!; // should never be undefined if (typeof constantNodeValue !== "number") { throw Error( `Expected constant value ${constantNodeIdentifier} in ${ this.scopedIdentifier } to be a number, but got ${constantNodeValue.toString()}`, ); } return constantNodeValue; } /** Gets Node with the given name in the current instance's scope and checks that it is a valid type reference node */ private getValidTypeReference(typeName: string): PossibleTypeRefNode { const maybeValidParent = this.getNode(this.scopePath, typeName); if ( !(maybeValidParent.declarator === "struct") && !(maybeValidParent.declarator === "typedef") && !(maybeValidParent.declarator === "union") && !(maybeValidParent.declarator === "enum") ) { throw new Error( `Expected ${typeName} to be non-module, non-constant type in ${this.scopedIdentifier}`, ); } return maybeValidParent; } /** Resolves to a type reference node value or fails if one is not found. * Only to be used when needsResolution==true and the `type` on the astNode is not "simple". * Also checks the parent against current serialization limitations. (ie: we do not support composing variable length arrays with typedefs) */ private typeRef(): PossibleTypeRefNode { if (this.typeRefNode == undefined) { this.typeRefNode = this.getValidTypeReference(this.astNode.type); } if (!(this.typeRefNode instanceof ReferenceTypeIDLNode)) { return this.typeRefNode; } // check potential errors if (this.astNode.isArray === true && this.typeRefNode.isArray === true) { const thisNodeIsFixedSize = this.astNode.arrayLengths != undefined; const parentNodeIsFixedSize = this.typeRefNode.arrayLengths != undefined; if (!thisNodeIsFixedSize || !parentNodeIsFixedSize) { throw new Error( `We do not support composing variable length arrays with typedefs: ${this.scopedIdentifier} referencing ${this.typeRefNode.scopedIdentifier}`, ); } } return this.typeRefNode; } }