use std::fmt;
use std::sync::Arc;
#[derive(Clone)]
pub struct InstanceId(Digits);
const INLINE_DIGITS: usize = 22;
#[derive(Clone)]
enum Digits {
Inline { len: u8, bytes: [u8; INLINE_DIGITS] },
Heap(Box<str>),
}
impl InstanceId {
#[must_use]
pub fn new(value: &str) -> Option<Self> {
Self::from_ascii_digits(value.as_bytes())
}
pub(crate) fn from_ascii_digits(value: &[u8]) -> Option<Self> {
(!value.is_empty() && value.iter().all(u8::is_ascii_digit))
.then(|| Self::from_digits(value))
}
pub(crate) fn from_digits(value: &[u8]) -> Self {
if value.len() <= INLINE_DIGITS {
let mut bytes = [0; INLINE_DIGITS];
bytes[..value.len()].copy_from_slice(value);
Self(Digits::Inline {
len: u8::try_from(value.len()).expect("INLINE_DIGITS fits in u8"),
bytes,
})
} else {
let text = std::str::from_utf8(value).expect("ASCII digits are UTF-8");
Self(Digits::Heap(text.into()))
}
}
#[must_use]
pub fn as_str(&self) -> &str {
match &self.0 {
Digits::Inline { len, bytes } => {
std::str::from_utf8(&bytes[..usize::from(*len)]).unwrap_or_default()
}
Digits::Heap(digits) => digits,
}
}
}
impl PartialEq for InstanceId {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for InstanceId {}
impl std::hash::Hash for InstanceId {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl PartialOrd for InstanceId {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for InstanceId {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl fmt::Debug for InstanceId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_tuple("InstanceId")
.field(&self.as_str())
.finish()
}
}
impl From<u64> for InstanceId {
fn from(value: u64) -> Self {
Self::from_digits(value.to_string().as_bytes())
}
}
impl fmt::Display for InstanceId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "#{}", self.as_str())
}
}
#[derive(Clone)]
pub struct Str(StrRepr);
const INLINE_TEXT: usize = 22;
#[derive(Clone)]
enum StrRepr {
Inline { len: u8, bytes: [u8; INLINE_TEXT] },
Shared(Arc<str>),
}
impl Str {
#[must_use]
pub fn as_str(&self) -> &str {
match &self.0 {
StrRepr::Inline { len, bytes } => {
std::str::from_utf8(&bytes[..usize::from(*len)]).unwrap_or_default()
}
StrRepr::Shared(text) => text,
}
}
#[must_use]
pub fn is_shared(&self) -> bool {
matches!(self.0, StrRepr::Shared(_))
}
#[must_use]
pub fn ptr_eq(&self, other: &Self) -> bool {
match (&self.0, &other.0) {
(StrRepr::Shared(a), StrRepr::Shared(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
fn inline(text: &str) -> Option<Self> {
let mut bytes = [0; INLINE_TEXT];
bytes
.get_mut(..text.len())?
.copy_from_slice(text.as_bytes());
Some(Self(StrRepr::Inline {
len: u8::try_from(text.len()).expect("INLINE_TEXT fits in u8"),
bytes,
}))
}
}
impl From<&str> for Str {
fn from(text: &str) -> Self {
Self::inline(text).unwrap_or_else(|| Self(StrRepr::Shared(Arc::from(text))))
}
}
impl From<String> for Str {
fn from(text: String) -> Self {
Self::inline(&text).unwrap_or_else(|| Self(StrRepr::Shared(Arc::from(text))))
}
}
impl From<Arc<str>> for Str {
fn from(text: Arc<str>) -> Self {
Self::inline(&text).unwrap_or(Self(StrRepr::Shared(text)))
}
}
impl From<&Str> for String {
fn from(text: &Str) -> Self {
text.as_str().to_owned()
}
}
impl std::ops::Deref for Str {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for Str {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl std::borrow::Borrow<str> for Str {
fn borrow(&self) -> &str {
self.as_str()
}
}
impl PartialEq for Str {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for Str {}
impl PartialEq<str> for Str {
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl PartialEq<&str> for Str {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl PartialOrd for Str {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Str {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl std::hash::Hash for Str {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl fmt::Debug for Str {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), formatter)
}
}
impl fmt::Display for Str {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl Default for Str {
fn default() -> Self {
Self::from("")
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Parameter<S = Str> {
Null,
Derived,
Bool(bool),
LogicalUnknown,
Integer(S),
Real(S),
Text(S),
Binary(S),
Enum(S),
Ref(InstanceId),
List(Box<[Self]>),
Typed {
type_name: S,
value: Box<Self>,
},
}
impl<S> Parameter<S> {
#[must_use]
pub fn as_reference(&self) -> Option<InstanceId> {
match self {
Self::Ref(id) => Some(id.clone()),
_ => None,
}
}
#[must_use]
pub fn as_list(&self) -> Option<&[Self]> {
match self {
Self::List(items) => Some(items),
_ => None,
}
}
#[must_use]
pub fn unwrap_typed(&self) -> &Self {
match self {
Self::Typed { value, .. } => value.unwrap_typed(),
value => value,
}
}
}
impl<S: AsRef<str>> Parameter<S> {
#[must_use]
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text(text) => Some(text.as_ref()),
_ => None,
}
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
match self {
Self::Integer(value) | Self::Real(value) => value.as_ref().parse().ok(),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HeaderRecord<S = Str> {
pub name: S,
pub parameters: Box<[Parameter<S>]>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Record<S = Str> {
pub name: S,
pub parameters: Box<[Parameter<S>]>,
}
impl<S> Record<S> {
#[must_use]
pub fn new(name: S, parameters: impl Into<Box<[Parameter<S>]>>) -> Self {
Self {
name,
parameters: parameters.into(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Instance<S = Str> {
Simple(Record<S>),
Complex(Box<[Record<S>]>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct DataRecord<S = Str> {
pub id: InstanceId,
pub instance: Instance<S>,
}
impl<S> DataRecord<S> {
#[must_use]
pub fn simple(id: InstanceId, name: S, parameters: impl Into<Box<[Parameter<S>]>>) -> Self {
Self {
id,
instance: Instance::Simple(Record::new(name, parameters)),
}
}
#[must_use]
pub fn complex(id: InstanceId, records: impl Into<Box<[Record<S>]>>) -> Self {
Self {
id,
instance: Instance::Complex(records.into()),
}
}
#[must_use]
pub fn records(&self) -> &[Record<S>] {
match &self.instance {
Instance::Simple(record) => std::slice::from_ref(record),
Instance::Complex(records) => records,
}
}
#[must_use]
pub fn as_simple(&self) -> Option<&Record<S>> {
match &self.instance {
Instance::Simple(record) => Some(record),
Instance::Complex(_) => None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct HeaderSection<S = Str> {
pub records: Vec<HeaderRecord<S>>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StandardHeader {
pub description: Option<Vec<String>>,
pub implementation_level: Option<String>,
pub name: Option<String>,
pub time_stamp: Option<String>,
pub author: Option<Vec<String>>,
pub organization: Option<Vec<String>>,
pub preprocessor_version: Option<String>,
pub originating_system: Option<String>,
pub authorization: Option<String>,
pub schema: Option<Vec<String>>,
}
impl<S: AsRef<str>> HeaderSection<S> {
#[must_use]
pub fn standard(&self) -> StandardHeader {
let mut header = StandardHeader::default();
for record in &self.records {
match record.name.as_ref().to_ascii_uppercase().as_str() {
"FILE_DESCRIPTION" => {
header.description = text_list(record.parameters.first());
header.implementation_level = text(record.parameters.get(1));
}
"FILE_NAME" => {
header.name = text(record.parameters.first());
header.time_stamp = text(record.parameters.get(1));
header.author = text_list(record.parameters.get(2));
header.organization = text_list(record.parameters.get(3));
header.preprocessor_version = text(record.parameters.get(4));
header.originating_system = text(record.parameters.get(5));
header.authorization = text(record.parameters.get(6));
}
"FILE_SCHEMA" => header.schema = text_list(record.parameters.first()),
_ => {}
}
}
header
}
}
fn text<S: AsRef<str>>(parameter: Option<&Parameter<S>>) -> Option<String> {
parameter?.as_text().map(ToOwned::to_owned)
}
fn text_list<S: AsRef<str>>(parameter: Option<&Parameter<S>>) -> Option<Vec<String>> {
Some(
parameter?
.as_list()?
.iter()
.filter_map(Parameter::as_text)
.map(ToOwned::to_owned)
.collect(),
)
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct DataSection<S = Str> {
pub records: Vec<DataRecord<S>>,
}
impl<S> DataSection<S> {
#[must_use]
pub fn get(&self, id: &InstanceId) -> Option<&DataRecord<S>> {
self.records.iter().find(|record| &record.id == id)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Exchange<S = Str> {
pub header: HeaderSection<S>,
pub data: DataSection<S>,
}