@foxglove/omgidl-parser
Version:
Parse OMG IDL to flattened definitions for serialization
149 lines (139 loc) • 5.7 kB
text/typescript
import { ConstantValue } from "@foxglove/message-definition";
import {
ConstantIDLNode,
EnumIDLNode,
ModuleIDLNode,
StructIDLNode,
StructMemberIDLNode,
TypedefIDLNode,
} from "./IDLNodes";
import { UnionIDLNode } from "./IDLNodes/UnionIDLNode";
import { AnyIDLNode } from "./IDLNodes/interfaces";
import { AnyASTNode, AnyAnnotation, UnresolvedConstantValue } from "./astTypes";
import { IDLMessageDefinition } from "./types";
/** Initializes map of IDL nodes to their scoped namespaces */
export function buildMap(definitions: AnyASTNode[]): Map<string, AnyIDLNode> {
const idlMap = new Map<string, AnyIDLNode>();
for (const definition of definitions) {
// build flattened definition map
traverseIDL([definition], (path) => {
const node = path[path.length - 1]!;
const namePath = path.map((n) => n.name);
const scopePath = namePath.slice(0, -1);
const newNodes = makeIDLNode(scopePath, node, idlMap);
idlMap.set(newNodes.scopedIdentifier, newNodes);
if (node.declarator === "enum") {
// DDS X-Types spec section 7.3.1.2.1.5: Enumerated Literal Values
// How C++ does implicit enums is that they will increment after the last explicit enum
// even if it collides with an existing enum
// initialize to -1 so that first value is 0
let prevEnumValue = -1;
const enumConstants = node.enumerators.flatMap((m) => {
const enumValue = getValueAnnotation(m.annotations) ?? (++prevEnumValue as ConstantValue);
if (typeof enumValue !== "number") {
throw new Error(
// eslint-disable-next-line @typescript-eslint/no-base-to-string
`Enum value, ${enumValue?.toString() ?? "undefined"}, assigned to ${node.name}::${
m.name
} must be a number`,
);
}
prevEnumValue = enumValue;
return {
declarator: "const" as const,
isConstant: true as const,
name: m.name,
type: "unsigned long",
value: enumValue,
isComplex: false,
};
});
// Enum scope constants
for (const constant of enumConstants) {
const idlConstantNode = new ConstantIDLNode(namePath, constant, idlMap);
idlMap.set(idlConstantNode.scopedIdentifier, idlConstantNode);
}
// enums constants can be referred to by their Enum::VALUE or by their parent module followed by ::VALUE.
// Ex: Test::Colors::RED or Test::RED
const moduleLevelNamePath = namePath.slice(0, -1);
for (const constant of enumConstants) {
const idlConstantNode = new ConstantIDLNode(moduleLevelNamePath, constant, idlMap);
idlMap.set(idlConstantNode.scopedIdentifier, idlConstantNode);
}
}
});
}
return idlMap;
}
function getValueAnnotation(
annotations: Record<string, AnyAnnotation> | undefined,
): UnresolvedConstantValue | ConstantValue | undefined {
if (!annotations) {
return undefined;
}
const valueAnnotation = annotations["value"];
if (valueAnnotation && valueAnnotation.type === "const-param") {
return valueAnnotation.value;
}
return undefined;
}
/** Convert to IDL Message Definitions for serialization and compatibility with Foxglove's Raw Message panel. Returned in order of original definitions*/
export function toIDLMessageDefinitions(map: Map<string, AnyIDLNode>): IDLMessageDefinition[] {
const messageDefinitions: IDLMessageDefinition[] = [];
// flatten for output to message definition
// Because the map entries are in original insertion order, they should reflect the original order of the definitions
// This is important for ros2idl compatibility
for (const node of map.values()) {
if (node.declarator === "struct") {
messageDefinitions.push(node.toIDLMessageDefinition());
} else if (node.declarator === "module") {
const def = node.toIDLMessageDefinition();
if (def != undefined) {
messageDefinitions.push(def);
}
} else if (node.declarator === "const") {
// Don't need constants for deserialization, all constant usage should be resolved by now.
// This does not omit enum constants from output because they are added under the enum toIDLMessageDefinition.
} else if (node.declarator === "enum") {
messageDefinitions.push(node.toIDLMessageDefinition());
} else if (node.declarator === "union") {
messageDefinitions.push(node.toIDLMessageDefinition());
}
}
return messageDefinitions;
}
const makeIDLNode = (
scopePath: string[],
node: AnyASTNode,
idlMap: Map<string, AnyIDLNode>,
): AnyIDLNode => {
switch (node.declarator) {
case "module":
return new ModuleIDLNode(scopePath, node, idlMap);
case "enum":
return new EnumIDLNode(scopePath, node, idlMap);
case "const":
return new ConstantIDLNode(scopePath, node, idlMap);
case "struct":
return new StructIDLNode(scopePath, node, idlMap);
case "struct-member":
return new StructMemberIDLNode(scopePath, node, idlMap);
case "typedef":
return new TypedefIDLNode(scopePath, node, idlMap);
case "union":
return new UnionIDLNode(scopePath, node, idlMap);
}
};
/**
* Iterates through IDL tree and calls `processNode` function on each node.
* NOTE: Does not process enum members
*/
function traverseIDL(path: AnyASTNode[], processNode: (path: AnyASTNode[]) => void) {
const currNode = path[path.length - 1]!;
if ("definitions" in currNode) {
currNode.definitions.forEach((n) => {
traverseIDL([...path, n], processNode);
});
}
processNode(path);
}