-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
CanOpenGen uses explicit stages so YAML parsing, semantic behavior, allocation, and output formats do not leak into one another:
YAML -> raw models -> module resolver -> type resolver
-> allocator -> validator -> resolved IR -> generators
Generators consume only resolved and validated internal representations. The EDS backend feeds Eds2Od; it does not produce CANoopEn C++ itself.
The YAML parser emits frozen dataclasses rather than exposing YAML dictionaries:
DeviceDefinition / ModuleDefinition
βββ ModuleImport[]
β βββ ModuleParameter[]
βββ CustomTypeDefinition[]
β βββ EnumMember[]
βββ ObjectDefinition[]
β βββ SubObjectDefinition[]
βββ PdoDefinition[]
ObjectDefinition holds the YAML type name, category, optional explicit index,
qualified name, and exactly one valid variable/record/array shape. Record fields retain
explicit subindices. PdoDefinition preserves symbolic mapping order.
Primitive aliases and their CANopen names, widths, integer ranges, and PDO-mappability live in one registry. Numeric EDS datatype identifiers are intentionally deferred until the bundled Eds2Od source is inspected.
The raw IR is structurally valid but unresolved. It intentionally contains module names, custom base names, symbolic references, and absent automatic addresses. Later stages produce new resolved models instead of mutating YAML dictionaries.
The module resolver recursively loads Modules/<namespace>.yml and produces:
ResolvedModuleGraph
βββ root: DeviceDefinition | ModuleDefinition
βββ root_dependencies[]
βββ ResolvedModule[]
βββ ModuleDefinition
βββ parameters[]
βββ dependencies[]
The module tuple is dependency-first and contains each equally configured transitive module once. DFS state reports a complete dependency cycle; a separate configuration registry rejects one namespace reached with different scalar parameters. Direct duplicate imports fail instead of being silently normalized.
ResolvedModuleGraph exposes the combined object/PDO inputs and each owner's transitive
visibility. ResolvedPdoDefinition replaces every local, imported, or explicit
qualified mapping name with a ResolvedObjectReference. Unknown and ambiguous names
fail before generator phases.
The type resolver recursively lowers each local type reference without changing raw definitions:
ResolvedDefinitionTypes
βββ ResolvedCustomType[]
β βββ primitive / alias_chain / enum_members
βββ ResolvedObjectType[]
βββ ResolvedDataType
βββ item_datatype
βββ ResolvedSubObjectType[]
Every ResolvedDataType records its declared name, resolved primitive, applicable
custom type name, complete alias chain, and inherited/declared enum semantics. A
per-run recursion stack reports exact cycles without global mutable resolver state.
The graph-aware resolver builds one declaration table keyed by (namespace, local name). Local declarations shadow imports; imported unqualified names must be unique;
explicit qualified names remain selectable. Cross-module aliases use the same enum,
range, and cycle rules as device-local aliases.
The allocator pairs raw definitions with immutable address metadata:
AllocatedObjectDictionary
βββ AllocatedObject[]
βββ ObjectDefinition
βββ index / AddressSource / probe_distance
βββ AllocatedSubObject[]
βββ subindex / role / AddressSource / probe_distance
Explicit indexes/subindices are validated and reserved before automatic allocation.
CRC32 helpers expose canonicalization separately from probing so the address command
and compatibility tests use the same implementation. Arrays produce reserved count and
sequential element entries; records produce reserved count and allocated field entries.
Phase 2 operates on a supplied complete object sequence and has no module-loading knowledge. The CLI passes the Phase 4 graph's complete transitive object set into the same allocator.