1use std::fmt;
9use std::sync::Arc;
10
11#[derive(Clone)]
22pub struct InstanceId(Digits);
23
24const INLINE_DIGITS: usize = 22;
26
27#[derive(Clone)]
28enum Digits {
29 Inline { len: u8, bytes: [u8; INLINE_DIGITS] },
30 Heap(Box<str>),
31}
32
33impl InstanceId {
34 #[must_use]
36 pub fn new(value: &str) -> Option<Self> {
37 Self::from_ascii_digits(value.as_bytes())
38 }
39
40 pub(crate) fn from_ascii_digits(value: &[u8]) -> Option<Self> {
43 (!value.is_empty() && value.iter().all(u8::is_ascii_digit))
44 .then(|| Self::from_digits(value))
45 }
46
47 pub(crate) fn from_digits(value: &[u8]) -> Self {
49 if value.len() <= INLINE_DIGITS {
50 let mut bytes = [0; INLINE_DIGITS];
51 bytes[..value.len()].copy_from_slice(value);
52 Self(Digits::Inline {
53 len: u8::try_from(value.len()).expect("INLINE_DIGITS fits in u8"),
54 bytes,
55 })
56 } else {
57 let text = std::str::from_utf8(value).expect("ASCII digits are UTF-8");
59 Self(Digits::Heap(text.into()))
60 }
61 }
62
63 #[must_use]
65 pub fn as_str(&self) -> &str {
66 match &self.0 {
67 Digits::Inline { len, bytes } => {
68 std::str::from_utf8(&bytes[..usize::from(*len)]).unwrap_or_default()
71 }
72 Digits::Heap(digits) => digits,
73 }
74 }
75}
76
77impl PartialEq for InstanceId {
78 fn eq(&self, other: &Self) -> bool {
79 self.as_str() == other.as_str()
80 }
81}
82
83impl Eq for InstanceId {}
84
85impl std::hash::Hash for InstanceId {
86 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
87 self.as_str().hash(state);
88 }
89}
90
91impl PartialOrd for InstanceId {
92 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
93 Some(self.cmp(other))
94 }
95}
96
97impl Ord for InstanceId {
98 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
99 self.as_str().cmp(other.as_str())
100 }
101}
102
103impl fmt::Debug for InstanceId {
104 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105 formatter
106 .debug_tuple("InstanceId")
107 .field(&self.as_str())
108 .finish()
109 }
110}
111
112impl From<u64> for InstanceId {
113 fn from(value: u64) -> Self {
114 Self::from_digits(value.to_string().as_bytes())
115 }
116}
117
118impl fmt::Display for InstanceId {
119 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120 write!(formatter, "#{}", self.as_str())
121 }
122}
123
124#[derive(Clone)]
134pub struct Str(StrRepr);
135
136const INLINE_TEXT: usize = 22;
138
139#[derive(Clone)]
140enum StrRepr {
141 Inline { len: u8, bytes: [u8; INLINE_TEXT] },
142 Shared(Arc<str>),
143}
144
145impl Str {
146 #[must_use]
148 pub fn as_str(&self) -> &str {
149 match &self.0 {
150 StrRepr::Inline { len, bytes } => {
153 std::str::from_utf8(&bytes[..usize::from(*len)]).unwrap_or_default()
154 }
155 StrRepr::Shared(text) => text,
156 }
157 }
158
159 #[must_use]
163 pub fn is_shared(&self) -> bool {
164 matches!(self.0, StrRepr::Shared(_))
165 }
166
167 #[must_use]
169 pub fn ptr_eq(&self, other: &Self) -> bool {
170 match (&self.0, &other.0) {
171 (StrRepr::Shared(a), StrRepr::Shared(b)) => Arc::ptr_eq(a, b),
172 _ => false,
173 }
174 }
175
176 fn inline(text: &str) -> Option<Self> {
178 let mut bytes = [0; INLINE_TEXT];
179 bytes
180 .get_mut(..text.len())?
181 .copy_from_slice(text.as_bytes());
182 Some(Self(StrRepr::Inline {
183 len: u8::try_from(text.len()).expect("INLINE_TEXT fits in u8"),
184 bytes,
185 }))
186 }
187}
188
189impl From<&str> for Str {
190 fn from(text: &str) -> Self {
191 Self::inline(text).unwrap_or_else(|| Self(StrRepr::Shared(Arc::from(text))))
192 }
193}
194
195impl From<String> for Str {
196 fn from(text: String) -> Self {
197 Self::inline(&text).unwrap_or_else(|| Self(StrRepr::Shared(Arc::from(text))))
198 }
199}
200
201impl From<Arc<str>> for Str {
202 fn from(text: Arc<str>) -> Self {
204 Self::inline(&text).unwrap_or(Self(StrRepr::Shared(text)))
205 }
206}
207
208impl From<&Str> for String {
209 fn from(text: &Str) -> Self {
210 text.as_str().to_owned()
211 }
212}
213
214impl std::ops::Deref for Str {
215 type Target = str;
216
217 fn deref(&self) -> &str {
218 self.as_str()
219 }
220}
221
222impl AsRef<str> for Str {
223 fn as_ref(&self) -> &str {
224 self.as_str()
225 }
226}
227
228impl std::borrow::Borrow<str> for Str {
229 fn borrow(&self) -> &str {
230 self.as_str()
231 }
232}
233
234impl PartialEq for Str {
235 fn eq(&self, other: &Self) -> bool {
236 self.as_str() == other.as_str()
237 }
238}
239
240impl Eq for Str {}
241
242impl PartialEq<str> for Str {
243 fn eq(&self, other: &str) -> bool {
244 self.as_str() == other
245 }
246}
247
248impl PartialEq<&str> for Str {
249 fn eq(&self, other: &&str) -> bool {
250 self.as_str() == *other
251 }
252}
253
254impl PartialOrd for Str {
255 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
256 Some(self.cmp(other))
257 }
258}
259
260impl Ord for Str {
261 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
262 self.as_str().cmp(other.as_str())
263 }
264}
265
266impl std::hash::Hash for Str {
267 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
268 self.as_str().hash(state);
269 }
270}
271
272impl fmt::Debug for Str {
273 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
274 fmt::Debug::fmt(self.as_str(), formatter)
275 }
276}
277
278impl fmt::Display for Str {
279 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
280 formatter.write_str(self.as_str())
281 }
282}
283
284impl Default for Str {
285 fn default() -> Self {
286 Self::from("")
287 }
288}
289
290#[derive(Debug, Clone, PartialEq)]
292pub enum Parameter<S = Str> {
293 Null,
295 Derived,
297 Bool(bool),
299 LogicalUnknown,
301 Integer(S),
303 Real(S),
305 Text(S),
307 Binary(S),
309 Enum(S),
311 Ref(InstanceId),
313 List(Box<[Self]>),
316 Typed {
318 type_name: S,
320 value: Box<Self>,
323 },
324}
325
326impl<S> Parameter<S> {
327 #[must_use]
329 pub fn as_reference(&self) -> Option<InstanceId> {
330 match self {
331 Self::Ref(id) => Some(id.clone()),
332 _ => None,
333 }
334 }
335
336 #[must_use]
338 pub fn as_list(&self) -> Option<&[Self]> {
339 match self {
340 Self::List(items) => Some(items),
341 _ => None,
342 }
343 }
344
345 #[must_use]
347 pub fn unwrap_typed(&self) -> &Self {
348 match self {
349 Self::Typed { value, .. } => value.unwrap_typed(),
350 value => value,
351 }
352 }
353}
354
355impl<S: AsRef<str>> Parameter<S> {
356 #[must_use]
358 pub fn as_text(&self) -> Option<&str> {
359 match self {
360 Self::Text(text) => Some(text.as_ref()),
361 _ => None,
362 }
363 }
364
365 #[must_use]
367 pub fn as_f64(&self) -> Option<f64> {
368 match self {
369 Self::Integer(value) | Self::Real(value) => value.as_ref().parse().ok(),
370 _ => None,
371 }
372 }
373}
374
375#[derive(Debug, Clone, PartialEq)]
377pub struct HeaderRecord<S = Str> {
378 pub name: S,
380 pub parameters: Box<[Parameter<S>]>,
382}
383
384#[derive(Debug, Clone, PartialEq)]
386pub struct Record<S = Str> {
387 pub name: S,
389 pub parameters: Box<[Parameter<S>]>,
391}
392
393impl<S> Record<S> {
394 #[must_use]
396 pub fn new(name: S, parameters: impl Into<Box<[Parameter<S>]>>) -> Self {
397 Self {
398 name,
399 parameters: parameters.into(),
400 }
401 }
402}
403
404#[derive(Debug, Clone, PartialEq)]
411pub enum Instance<S = Str> {
412 Simple(Record<S>),
414 Complex(Box<[Record<S>]>),
416}
417
418#[derive(Debug, Clone, PartialEq)]
420pub struct DataRecord<S = Str> {
421 pub id: InstanceId,
423 pub instance: Instance<S>,
425}
426
427impl<S> DataRecord<S> {
428 #[must_use]
430 pub fn simple(id: InstanceId, name: S, parameters: impl Into<Box<[Parameter<S>]>>) -> Self {
431 Self {
432 id,
433 instance: Instance::Simple(Record::new(name, parameters)),
434 }
435 }
436
437 #[must_use]
439 pub fn complex(id: InstanceId, records: impl Into<Box<[Record<S>]>>) -> Self {
440 Self {
441 id,
442 instance: Instance::Complex(records.into()),
443 }
444 }
445
446 #[must_use]
449 pub fn records(&self) -> &[Record<S>] {
450 match &self.instance {
451 Instance::Simple(record) => std::slice::from_ref(record),
452 Instance::Complex(records) => records,
453 }
454 }
455
456 #[must_use]
458 pub fn as_simple(&self) -> Option<&Record<S>> {
459 match &self.instance {
460 Instance::Simple(record) => Some(record),
461 Instance::Complex(_) => None,
462 }
463 }
464}
465
466#[derive(Debug, Clone, Default, PartialEq)]
468pub struct HeaderSection<S = Str> {
469 pub records: Vec<HeaderRecord<S>>,
471}
472
473#[derive(Debug, Clone, Default, PartialEq, Eq)]
478pub struct StandardHeader {
479 pub description: Option<Vec<String>>,
481 pub implementation_level: Option<String>,
483 pub name: Option<String>,
485 pub time_stamp: Option<String>,
487 pub author: Option<Vec<String>>,
489 pub organization: Option<Vec<String>>,
491 pub preprocessor_version: Option<String>,
493 pub originating_system: Option<String>,
495 pub authorization: Option<String>,
497 pub schema: Option<Vec<String>>,
499}
500
501impl<S: AsRef<str>> HeaderSection<S> {
502 #[must_use]
504 pub fn standard(&self) -> StandardHeader {
505 let mut header = StandardHeader::default();
506 for record in &self.records {
507 match record.name.as_ref().to_ascii_uppercase().as_str() {
508 "FILE_DESCRIPTION" => {
509 header.description = text_list(record.parameters.first());
510 header.implementation_level = text(record.parameters.get(1));
511 }
512 "FILE_NAME" => {
513 header.name = text(record.parameters.first());
514 header.time_stamp = text(record.parameters.get(1));
515 header.author = text_list(record.parameters.get(2));
516 header.organization = text_list(record.parameters.get(3));
517 header.preprocessor_version = text(record.parameters.get(4));
518 header.originating_system = text(record.parameters.get(5));
519 header.authorization = text(record.parameters.get(6));
520 }
521 "FILE_SCHEMA" => header.schema = text_list(record.parameters.first()),
522 _ => {}
523 }
524 }
525 header
526 }
527}
528
529fn text<S: AsRef<str>>(parameter: Option<&Parameter<S>>) -> Option<String> {
530 parameter?.as_text().map(ToOwned::to_owned)
531}
532
533fn text_list<S: AsRef<str>>(parameter: Option<&Parameter<S>>) -> Option<Vec<String>> {
534 Some(
535 parameter?
536 .as_list()?
537 .iter()
538 .filter_map(Parameter::as_text)
539 .map(ToOwned::to_owned)
540 .collect(),
541 )
542}
543
544#[derive(Debug, Clone, Default, PartialEq)]
546pub struct DataSection<S = Str> {
547 pub records: Vec<DataRecord<S>>,
549}
550
551impl<S> DataSection<S> {
552 #[must_use]
554 pub fn get(&self, id: &InstanceId) -> Option<&DataRecord<S>> {
555 self.records.iter().find(|record| &record.id == id)
556 }
557}
558
559#[derive(Debug, Clone, Default, PartialEq)]
561pub struct Exchange<S = Str> {
562 pub header: HeaderSection<S>,
564 pub data: DataSection<S>,
566}