@foxglove/omgidl-parser
Version:
Parse OMG IDL to flattened definitions for serialization
219 lines (200 loc) • 7.75 kB
text/typescript
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;
}
}