Skip to main content

ergo_sbe/
ir.rs

1//! Token intermediate representation for an SBE schema.
2//!
3//! After [`crate::parse`], you usually only need [`Ir`] via [`crate::Schema`].
4//! The flat [`Token`] stream (sbe-tool style) uses [`Signal`] brackets for
5//! messages, fields, composites, enums, sets, groups, and var-data; [`Encoding`]
6//! holds wire layout. [`crate::resolve_schema`] fills offsets and defaults.
7//!
8//! Most application code never inspects IR directly — use generated codecs.
9
10/// Byte order declared by the schema; applies to every primitive encoding.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum ByteOrder {
13    /// Little-endian — the SBE default.
14    LittleEndian,
15    /// Big-endian.
16    BigEndian,
17}
18
19/// Structural role of a token in the IR stream.
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum Signal {
22    /// Opens a message definition.
23    BeginMessage,
24    /// Closes a message definition.
25    EndMessage,
26    /// Opens a field — a message field or a composite member.
27    BeginField,
28    /// Closes a field.
29    EndField,
30    /// Opens a composite type definition.
31    BeginComposite,
32    /// Closes a composite type definition.
33    EndComposite,
34    /// Opens an enum type definition.
35    BeginEnum,
36    /// Closes an enum type definition.
37    EndEnum,
38    /// Opens a bitset (choice/set) type definition.
39    BeginSet,
40    /// Closes a bitset (choice/set) type definition.
41    EndSet,
42    /// Opens a repeating group.
43    BeginGroup,
44    /// Closes a repeating group.
45    EndGroup,
46    /// Opens a variable-length data field.
47    BeginVarData,
48    /// Closes a variable-length data field.
49    EndVarData,
50    /// A leaf encoding token (primitive within a composite, enum, or set).
51    Encoding,
52}
53
54/// Field presence semantics.
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
56pub enum Presence {
57    /// Always present on the wire.
58    #[default]
59    Required,
60    /// May be absent; encoded as the type's null value.
61    Optional,
62    /// Not encoded — the value is fixed by the schema.
63    Constant,
64}
65
66/// SBE primitive wire types.
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum PrimitiveType {
69    /// Single ASCII byte.
70    Char,
71    /// Signed 8-bit integer.
72    Int8,
73    /// Unsigned 8-bit integer.
74    UInt8,
75    /// Signed 16-bit integer.
76    Int16,
77    /// Unsigned 16-bit integer.
78    UInt16,
79    /// Signed 32-bit integer.
80    Int32,
81    /// Unsigned 32-bit integer.
82    UInt32,
83    /// Signed 64-bit integer.
84    Int64,
85    /// Unsigned 64-bit integer.
86    UInt64,
87    /// IEEE-754 single-precision float.
88    Float,
89    /// IEEE-754 double-precision float.
90    Double,
91}
92
93impl PrimitiveType {
94    /// On-wire size in bytes.
95    #[must_use]
96    pub const fn size(self) -> usize {
97        match self {
98            Self::Char | Self::Int8 | Self::UInt8 => 1,
99            Self::Int16 | Self::UInt16 => 2,
100            Self::Int32 | Self::UInt32 | Self::Float => 4,
101            Self::Int64 | Self::UInt64 | Self::Double => 8,
102        }
103    }
104
105    /// Unsigned integer primitives plus `char` (one octet).
106    pub(crate) const fn is_unsigned_int(self) -> bool {
107        matches!(
108            self,
109            Self::Char | Self::UInt8 | Self::UInt16 | Self::UInt32 | Self::UInt64
110        )
111    }
112
113    /// Signed integer primitives.
114    pub(crate) const fn is_signed_int(self) -> bool {
115        matches!(self, Self::Int8 | Self::Int16 | Self::Int32 | Self::Int64)
116    }
117
118    /// Inclusive range for a signed integer primitive.
119    pub(crate) const fn signed_range(self) -> Option<(i64, i64)> {
120        match self {
121            Self::Int8 => Some((i8::MIN as i64, i8::MAX as i64)),
122            Self::Int16 => Some((i16::MIN as i64, i16::MAX as i64)),
123            Self::Int32 => Some((i32::MIN as i64, i32::MAX as i64)),
124            Self::Int64 => Some((i64::MIN, i64::MAX)),
125            _ => None,
126        }
127    }
128
129    /// Maximum inclusive value for an unsigned integer primitive (or `char`).
130    pub(crate) const fn unsigned_max(self) -> Option<u64> {
131        match self {
132            Self::Char | Self::UInt8 => Some(u8::MAX as u64),
133            Self::UInt16 => Some(u16::MAX as u64),
134            Self::UInt32 => Some(u32::MAX as u64),
135            Self::UInt64 => Some(u64::MAX),
136            _ => None,
137        }
138    }
139}
140
141/// How a token is encoded on the wire.
142#[derive(Clone, Debug, Default, PartialEq, Eq)]
143pub struct Encoding {
144    /// Primitive type for leaf tokens; `None` for structural tokens.
145    pub primitive_type: Option<PrimitiveType>,
146    /// Byte offset within the enclosing block; `None` when not declared.
147    pub offset: Option<usize>,
148    /// Presence of the value.
149    pub presence: Presence,
150    /// Schema version in which this token was introduced.
151    pub since_version: u16,
152    /// Null sentinel for optional fields; `None` when not applicable.
153    pub null_value: Option<u64>,
154    /// Character encoding for string fields (e.g. `"UTF-8"`, `"ASCII"`); `None` for non-string fields.
155    pub character_encoding: Option<String>,
156    /// SBE semantic type annotation (e.g. `"Price"`, `"Qty"`); `None` when not declared.
157    pub semantic_type: Option<String>,
158    /// Minimum valid value for the type; `None` when not declared.
159    pub min_value: Option<u64>,
160    /// Maximum valid value for the type; `None` when not declared.
161    pub max_value: Option<u64>,
162    /// Human-readable description from XML; `None` when absent.
163    pub description: Option<String>,
164    /// Constant value for `presence="constant"` fields; `None` otherwise.
165    pub constant_value: Option<String>,
166    /// Array length for fixed-size primitive arrays.
167    pub length: Option<usize>,
168    /// Epoch for timestamp encoding (e.g. "unix"); `None` when not declared.
169    pub epoch: Option<String>,
170    /// Time unit for timestamp encoding (e.g. "nanoseconds"); `None` when not declared.
171    pub time_unit: Option<String>,
172    /// Whether this token's wire size is variable; used for var-data composites.
173    pub is_variable_length: bool,
174    /// Whether this type or field is marked as deprecated in the schema.
175    pub deprecated: bool,
176}
177
178/// One token in the flat IR stream.
179#[derive(Clone, Debug, PartialEq, Eq)]
180pub struct Token {
181    /// SBE field/message/type id; `None` for structural tokens that don't carry an id.
182    pub id: Option<u16>,
183    /// Name of the declared entity (message, field, composite, …).
184    pub name: String,
185    /// Structural role.
186    pub signal: Signal,
187    /// Wire-encoding metadata. Populated on `BeginField`; default elsewhere.
188    pub encoding: Encoding,
189    /// Source span into the input XML text, for miette diagnostics.
190    /// `None` for synthetic tokens not originating from the schema source.
191    pub span: Option<std::ops::Range<usize>>,
192}
193
194/// The parsed schema IR: schema-level metadata plus the token stream.
195#[derive(Clone, Debug, PartialEq, Eq)]
196pub struct Ir {
197    /// SBE package name.
198    pub package: String,
199    /// SBE schema id.
200    pub id: u16,
201    /// SBE schema version.
202    pub version: u16,
203    /// Schema-declared byte order.
204    pub byte_order: ByteOrder,
205    /// Schema-level description from XML; `None` when absent.
206    pub description: Option<String>,
207    /// Semantic version string from XML; `None` when absent.
208    pub semantic_version: Option<String>,
209    /// Name of the composite type used as the message header (default `"messageHeader"`).
210    pub header_type: String,
211    /// Flat token stream.
212    pub tokens: Vec<Token>,
213}