@foxglove/omgidl-parser
Version:
Parse OMG IDL to flattened definitions for serialization
114 lines (103 loc) • 3.73 kB
text/typescript
import { AnyIDLNode, IConstantIDLNode, IIDLNode } from "./interfaces";
import { BaseASTNode } from "../astTypes";
/** Class used to resolve ASTNodes to IDLMessageDefinitions
* There is a subclass of this class for each `declarator` type on the ASTNode.
* This class is meant to provide functions and variables that exist across all of the nodes to help them resolve to complete message definitions.
*/
export abstract class IDLNode<T extends BaseASTNode = BaseASTNode> implements IIDLNode<T> {
/** Map of all IDLNodes in a schema definition */
protected map: Map<string, AnyIDLNode>;
/** Unresolved node parsed directly from schema */
protected readonly astNode: T;
/** Array of strings that represent namespace scope that astNode is contained within. */
readonly scopePath: string[];
constructor(scopePath: string[], astNode: T, idlMap: Map<string, AnyIDLNode>) {
this.scopePath = scopePath;
this.astNode = astNode;
this.map = idlMap;
}
get declarator(): T["declarator"] {
return this.astNode.declarator;
}
get name(): BaseASTNode["name"] {
return this.astNode.name;
}
get annotations(): BaseASTNode["annotations"] {
return this.astNode.annotations;
}
/** Returns scoped identifier of the astNode: (...scopePath::name) */
get scopedIdentifier(): string {
return toScopedIdentifier([...this.scopePath, this.name]);
}
/** Gets any node in map. Fails if not found.*/
protected getNode(scopePath: string[], name: string): AnyIDLNode {
const maybeNode = resolveScopedOrLocalNodeReference({
usedIdentifier: name,
scopeOfUsage: scopePath,
definitionMap: this.map,
});
if (maybeNode == undefined) {
throw new Error(
`Could not find node ${name} in ${scopePath.join("::")} referenced by ${
this.scopedIdentifier
}`,
);
}
return maybeNode;
}
/** Gets a constant node under a local-to-this-node or scoped identifier. Fails if not a ConstantNode */
protected getConstantNode(
identifier: string,
scopePath: string[] = this.scopePath,
): IConstantIDLNode {
const maybeConstantNode = this.getNode(scopePath, identifier);
if (maybeConstantNode.declarator !== "const") {
throw new Error(`Expected ${this.name} to be a constant in ${this.scopedIdentifier}`);
}
return maybeConstantNode;
}
}
/** Takes a potentially scope-less name used in a given scope and attempts to find the Node in the map
* that matches it's name in the local scope or any larger encapsulating scope.
* For example:
* ```Cpp
* module foo {
* typedef customType uint32;
* module bar {
* customType FooBar;
* foo::customType BarFoo;
* };
* };
* ```
* Both of these usages of customType should resolve given this function.
*/
function resolveScopedOrLocalNodeReference({
usedIdentifier,
scopeOfUsage,
definitionMap,
}: {
usedIdentifier: string;
scopeOfUsage: string[];
definitionMap: Map<string, AnyIDLNode>;
}): AnyIDLNode | undefined {
// If using local un-scoped identifier, it will not be found in the definitions map
// In this case we try by building up the namespace prefix until we find a match
let referencedNode = undefined;
// start with most specific scope and work upwards
const currPrefix: string[] = [...scopeOfUsage];
for (;;) {
const identifierToTry = toScopedIdentifier([...currPrefix, usedIdentifier]);
referencedNode = definitionMap.get(identifierToTry);
if (referencedNode != undefined) {
break;
}
if (currPrefix.length === 0) {
break;
}
currPrefix.pop();
}
return referencedNode;
}
export function toScopedIdentifier(path: string[]): string {
return path.join("::");
}