extern crate alloc;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use zerodds_ast::Extensibility;
use crate::key::{
AggregateDelimiter, CollectionDelimiterPolicy, DefaultedDiscriminatorMember, Dialect,
ElementPadding, Encoding, Endianness, EnumHolderWidth, EnumeratorValue, FixedDigitCount,
KeyHolderAlignment, KeyHolderExtensibility, KeyHolderOrder, LayoutKey, LengthCodePolicy,
MapDelimiter, MapEntryOrder, MutableUnionFraming, PidFlagPolicy, PlCdr1HeaderPolicy, Purpose,
SequenceElementDelimiter, ShortPidRange, UnionLabelMatch, WcharFraming, WcharWidth,
WstringCodeUnits, WstringLengthPolicy, WstringUnitOrder,
};
const MAP_LENGTH_VALUES: [(&str, bool); 2] =
[("sequence-equivalent", true), ("rule-15-omits", false)];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AxisFlag {
LengthCodes,
CollectionDelimiters,
WstringLength,
WcharFraming,
WcharWidth,
MapDelimiter,
WstringUnits,
PlCdr1Headers,
PidFlags,
EnumHolder,
UnionLabels,
MapEntries,
EnumeratorValue,
UnionFraming,
ShortPidRange,
FixedDigits,
DefaultExtensibility,
KeyHolder,
KeyHolderOrder,
KeyHolderAlignment,
DefaultedDiscriminatorMember,
MapLength,
ElementPadding,
AggregateDelimiter,
SequenceElementDelimiter,
WstringCodeUnits,
}
#[derive(Debug, Clone, Copy)]
pub struct DialectSettings {
pub key: LayoutKey,
pub map_length_with_delimiter: bool,
pub lowering: DialectLowering,
}
impl DialectSettings {
#[must_use]
pub fn in_dialect(
dialect: Dialect,
encoding: Encoding,
endianness: Endianness,
purpose: Purpose,
) -> Self {
Self {
key: LayoutKey::in_dialect(dialect, encoding, endianness, purpose),
map_length_with_delimiter: !dialect.map_count_omitted_when_delimited(),
lowering: DialectLowering::for_dialect(dialect),
}
}
}
fn joined_names<T: Copy, const N: usize>(values: [T; N], name: fn(T) -> &'static str) -> String {
values.into_iter().map(name).collect::<Vec<_>>().join(" | ")
}
impl AxisFlag {
#[must_use]
pub const fn flag_name(self) -> &'static str {
match self {
Self::LengthCodes => "--length-codes",
Self::CollectionDelimiters => "--collection-delimiters",
Self::WstringLength => "--wstring-length",
Self::WcharFraming => "--wchar-framing",
Self::WcharWidth => "--wchar-width",
Self::MapDelimiter => "--map-delimiter",
Self::WstringUnits => "--wstring-units",
Self::PlCdr1Headers => "--pl-cdr1-headers",
Self::PidFlags => "--pid-flags",
Self::EnumHolder => "--enum-holder",
Self::UnionLabels => "--union-labels",
Self::MapEntries => "--map-entries",
Self::EnumeratorValue => "--enumerator-value",
Self::UnionFraming => "--union-framing",
Self::ShortPidRange => "--short-pid-range",
Self::FixedDigits => "--fixed-digits",
Self::DefaultExtensibility => "--default-extensibility",
Self::KeyHolder => "--key-holder",
Self::KeyHolderOrder => "--key-holder-order",
Self::KeyHolderAlignment => "--key-holder-alignment",
Self::DefaultedDiscriminatorMember => "--defaulted-discriminator-member",
Self::MapLength => "--map-length",
Self::ElementPadding => "--element-padding",
Self::AggregateDelimiter => "--aggregate-delimiter",
Self::SequenceElementDelimiter => "--sequence-element-delimiter",
Self::WstringCodeUnits => "--wstring-code-units",
}
}
#[must_use]
pub fn help_values(self) -> String {
match self {
Self::LengthCodes => joined_names(LengthCodePolicy::ALL, LengthCodePolicy::as_str),
Self::CollectionDelimiters => joined_names(
CollectionDelimiterPolicy::ALL,
CollectionDelimiterPolicy::as_str,
),
Self::WstringLength => format!(
"{} | {}",
joined_names(WstringLengthPolicy::MEASURED, WstringLengthPolicy::as_str),
WstringLengthPolicy::COORDINATE_FORM_HELP
),
Self::WcharFraming => joined_names(WcharFraming::ALL, WcharFraming::as_str),
Self::WcharWidth => joined_names(WcharWidth::ALL, WcharWidth::as_str),
Self::MapDelimiter => joined_names(MapDelimiter::ALL, MapDelimiter::as_str),
Self::WstringUnits => joined_names(WstringUnitOrder::ALL, WstringUnitOrder::as_str),
Self::PlCdr1Headers => {
joined_names(PlCdr1HeaderPolicy::ALL, PlCdr1HeaderPolicy::as_str)
}
Self::PidFlags => joined_names(PidFlagPolicy::ALL, PidFlagPolicy::as_str),
Self::EnumHolder => joined_names(EnumHolderWidth::ALL, EnumHolderWidth::as_str),
Self::UnionLabels => joined_names(UnionLabelMatch::ALL, UnionLabelMatch::as_str),
Self::MapEntries => joined_names(MapEntryOrder::ALL, MapEntryOrder::as_str),
Self::EnumeratorValue => joined_names(EnumeratorValue::ALL, EnumeratorValue::as_str),
Self::UnionFraming => {
joined_names(MutableUnionFraming::ALL, MutableUnionFraming::as_str)
}
Self::ShortPidRange => joined_names(ShortPidRange::ALL, ShortPidRange::as_str),
Self::FixedDigits => joined_names(FixedDigitCount::ALL, FixedDigitCount::as_str),
Self::DefaultExtensibility => joined_names(Extensibility::ALL, Extensibility::as_str),
Self::KeyHolder => {
joined_names(KeyHolderExtensibility::ALL, KeyHolderExtensibility::as_str)
}
Self::KeyHolderOrder => joined_names(KeyHolderOrder::ALL, KeyHolderOrder::as_str),
Self::KeyHolderAlignment => {
joined_names(KeyHolderAlignment::ALL, KeyHolderAlignment::as_str)
}
Self::DefaultedDiscriminatorMember => joined_names(
DefaultedDiscriminatorMember::ALL,
DefaultedDiscriminatorMember::as_str,
),
Self::MapLength => {
joined_names([MAP_LENGTH_VALUES[0].0, MAP_LENGTH_VALUES[1].0], |name| {
name
})
}
Self::ElementPadding => joined_names(ElementPadding::ALL, ElementPadding::as_str),
Self::AggregateDelimiter => {
joined_names(AggregateDelimiter::ALL, AggregateDelimiter::as_str)
}
Self::SequenceElementDelimiter => joined_names(
SequenceElementDelimiter::ALL,
SequenceElementDelimiter::as_str,
),
Self::WstringCodeUnits => joined_names(WstringCodeUnits::ALL, WstringCodeUnits::as_str),
}
}
pub fn parse_and_apply(
self,
settings: DialectSettings,
value: &str,
) -> Result<DialectSettings, String> {
let error = |kind: &str| format!("`{value}` is not a {kind}");
let mut settings = settings;
match self {
Self::LengthCodes => {
settings.key = settings.key.with_length_codes(
LengthCodePolicy::parse(value).ok_or_else(|| error("length-code policy"))?,
);
}
Self::CollectionDelimiters => {
settings.key = settings.key.with_collection_delimiters(
CollectionDelimiterPolicy::parse(value)
.ok_or_else(|| error("delimiter policy"))?,
);
}
Self::WstringLength => {
settings.key = settings.key.with_wstring_length(
WstringLengthPolicy::parse(value)
.ok_or_else(|| error("wstring-length policy"))?,
);
}
Self::WcharFraming => {
settings.key = settings.key.with_wchar_framing(
WcharFraming::parse(value).ok_or_else(|| error("wchar framing"))?,
);
}
Self::WcharWidth => {
settings.key = settings.key.with_wchar_width(
WcharWidth::parse(value).ok_or_else(|| error("wchar width"))?,
);
}
Self::MapDelimiter => {
settings.key = settings.key.with_map_delimiter(
MapDelimiter::parse(value).ok_or_else(|| error("map delimiter policy"))?,
);
}
Self::WstringUnits => {
settings.key = settings.key.with_wstring_unit_order(
WstringUnitOrder::parse(value).ok_or_else(|| error("wstring unit order"))?,
);
}
Self::PlCdr1Headers => {
settings.key = settings.key.with_pl_cdr1_headers(
PlCdr1HeaderPolicy::parse(value)
.ok_or_else(|| error("PL_CDR1 header policy"))?,
);
}
Self::PidFlags => {
settings.key = settings.key.with_pid_flags(
PidFlagPolicy::parse(value).ok_or_else(|| error("PID-flag policy"))?,
);
}
Self::EnumHolder => {
settings.key = settings.key.with_enum_holder(
EnumHolderWidth::parse(value).ok_or_else(|| error("enum-holder width"))?,
);
}
Self::UnionLabels => {
settings.key = settings.key.with_union_label(
UnionLabelMatch::parse(value).ok_or_else(|| error("union-label rule"))?,
);
}
Self::MapEntries => {
settings.key = settings.key.with_map_entries(
MapEntryOrder::parse(value).ok_or_else(|| error("map-entry order"))?,
);
}
Self::EnumeratorValue => {
settings.key = settings.key.with_enumerator_value(
EnumeratorValue::parse(value)
.ok_or_else(|| error("enumerator-value policy"))?,
);
}
Self::UnionFraming => {
settings.key = settings.key.with_union_framing(
MutableUnionFraming::parse(value).ok_or_else(|| error("union framing"))?,
);
}
Self::ShortPidRange => {
settings.key = settings.key.with_short_pid_range(
ShortPidRange::parse(value).ok_or_else(|| error("short-PID range"))?,
);
}
Self::FixedDigits => {
settings.key = settings.key.with_fixed_digits(
FixedDigitCount::parse(value).ok_or_else(|| error("fixed digit count"))?,
);
}
Self::DefaultExtensibility => {
settings.key = settings.key.with_default_extensibility(
Extensibility::parse(value).ok_or_else(|| error("extensibility"))?,
);
}
Self::KeyHolder => {
settings.key = settings.key.with_key_holder(
KeyHolderExtensibility::parse(value)
.ok_or_else(|| error("key-holder policy"))?,
);
}
Self::KeyHolderOrder => {
settings.key = settings.key.with_key_holder_order(
KeyHolderOrder::parse(value).ok_or_else(|| error("key-holder order"))?,
);
}
Self::KeyHolderAlignment => {
settings.key = settings.key.with_key_holder_alignment(
KeyHolderAlignment::parse(value)
.ok_or_else(|| error("key-holder alignment"))?,
);
}
Self::DefaultedDiscriminatorMember => {
settings.key = settings.key.with_defaulted_discriminator_member(
DefaultedDiscriminatorMember::parse(value)
.ok_or_else(|| error("defaulted-discriminator-member policy"))?,
);
}
Self::MapLength => {
let (_, with_delimiter) = MAP_LENGTH_VALUES
.into_iter()
.find(|(name, _)| *name == value)
.ok_or_else(|| error("map-length policy"))?;
settings.map_length_with_delimiter = with_delimiter;
}
Self::ElementPadding => {
settings.key = settings.key.with_element_padding(
ElementPadding::parse(value).ok_or_else(|| error("element-padding policy"))?,
);
}
Self::AggregateDelimiter => {
settings.key = settings.key.with_aggregate_delimiter(
AggregateDelimiter::parse(value)
.ok_or_else(|| error("aggregate-delimiter policy"))?,
);
}
Self::SequenceElementDelimiter => {
settings.key = settings.key.with_sequence_element_delimiter(
SequenceElementDelimiter::parse(value)
.ok_or_else(|| error("sequence-element-delimiter policy"))?,
);
}
Self::WstringCodeUnits => {
settings.key = settings.key.with_wstring_code_units(
WstringCodeUnits::parse(value).ok_or_else(|| error("wstring code units"))?,
);
}
}
Ok(settings)
}
}
pub const AXES: [AxisFlag; 26] = [
AxisFlag::LengthCodes,
AxisFlag::CollectionDelimiters,
AxisFlag::WstringLength,
AxisFlag::WcharFraming,
AxisFlag::WcharWidth,
AxisFlag::MapDelimiter,
AxisFlag::WstringUnits,
AxisFlag::PlCdr1Headers,
AxisFlag::PidFlags,
AxisFlag::EnumHolder,
AxisFlag::UnionLabels,
AxisFlag::MapEntries,
AxisFlag::EnumeratorValue,
AxisFlag::UnionFraming,
AxisFlag::ShortPidRange,
AxisFlag::FixedDigits,
AxisFlag::DefaultExtensibility,
AxisFlag::KeyHolder,
AxisFlag::KeyHolderOrder,
AxisFlag::KeyHolderAlignment,
AxisFlag::DefaultedDiscriminatorMember,
AxisFlag::MapLength,
AxisFlag::ElementPadding,
AxisFlag::AggregateDelimiter,
AxisFlag::SequenceElementDelimiter,
AxisFlag::WstringCodeUnits,
];
pub fn dialect_settings_from_overrides(
dialect: Dialect,
encoding: Encoding,
endianness: Endianness,
purpose: Purpose,
overrides: &[(AxisFlag, &str)],
) -> Result<DialectSettings, String> {
let mut settings = DialectSettings::in_dialect(dialect, encoding, endianness, purpose);
for &(axis, value) in overrides {
settings = axis.parse_and_apply(settings, value)?;
}
Ok(settings)
}
#[must_use]
pub fn dialect_names() -> String {
Dialect::ALL
.iter()
.map(|d| d.as_str())
.collect::<Vec<_>>()
.join(", ")
}
#[must_use]
pub fn dialect_help() -> String {
let mut out = String::from("specification (the default)");
for d in Dialect::ALL {
if !d.is_default() {
out.push_str(" | ");
out.push_str(d.as_str());
}
}
out
}
#[derive(Debug, Clone, Copy)]
pub struct DialectLowering {
pub hashed_id_advances_the_counter: bool,
pub hashid_reads_the_escaped_spelling: bool,
pub admits_name_of_enclosing_scope: bool,
}
impl DialectLowering {
#[must_use]
pub const fn for_dialect(dialect: Dialect) -> Self {
Self {
hashed_id_advances_the_counter: dialect.hashed_id_advances_the_counter(),
hashid_reads_the_escaped_spelling: dialect.hashid_reads_the_escaped_spelling(),
admits_name_of_enclosing_scope: dialect.admits_the_name_of_the_enclosing_scope(),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
fn default_settings() -> DialectSettings {
DialectSettings::in_dialect(
Dialect::default(),
Encoding::PlainCdr2,
Endianness::Little,
Purpose::Data,
)
}
#[test]
fn every_axis_parses_back_its_own_as_str() {
let settings = default_settings();
for axis in AXES {
let text = axis.help_values();
let first_value = text.split(" | ").next().unwrap();
let result = axis.parse_and_apply(settings, first_value);
assert!(
result.is_ok(),
"axis {:?} failed to parse its own value `{first_value}`: {:?}",
axis,
result.err()
);
}
}
#[test]
fn an_unknown_value_is_reported() {
let settings = default_settings();
let err = AxisFlag::LengthCodes
.parse_and_apply(settings, "nonsense")
.expect_err("should fail");
assert!(err.contains("nonsense"), "{err}");
assert!(err.contains("length-code"), "{err}");
}
#[test]
fn dialect_first_then_override() {
let overrides = [(AxisFlag::LengthCodes, "compact")];
let settings = dialect_settings_from_overrides(
Dialect::FastDds,
Encoding::PlainCdr2,
Endianness::Little,
Purpose::Data,
&overrides,
)
.expect("should parse");
assert_eq!(settings.key.length_codes, LengthCodePolicy::Compact);
}
#[test]
fn every_dialect_name_is_parseable() {
for d in Dialect::ALL {
let parsed = Dialect::parse(d.as_str());
assert_eq!(parsed, Some(d), "{}", d.as_str());
}
}
#[test]
fn wstring_length_help_names_the_coordinate_form_once() {
let text = AxisFlag::WstringLength.help_values();
assert!(text.contains("specification"));
assert!(text.contains("ros-type-support"));
assert!(text.contains(WstringLengthPolicy::COORDINATE_FORM_HELP));
}
#[test]
fn wchar_framing_help_names_every_measured_form() {
let text = AxisFlag::WcharFraming.help_values();
for framing in WcharFraming::ALL {
assert!(text.contains(framing.as_str()), "{text}");
}
}
#[test]
fn map_length_axis_overrides_the_dialects_answer() {
let settings = default_settings();
assert!(settings.map_length_with_delimiter);
let overridden = AxisFlag::MapLength
.parse_and_apply(settings, "rule-15-omits")
.expect("known value");
assert!(!overridden.map_length_with_delimiter);
let opendds = DialectSettings::in_dialect(
Dialect::OpenDds,
Encoding::PlainCdr2,
Endianness::Little,
Purpose::Data,
);
assert!(!opendds.map_length_with_delimiter);
}
#[test]
fn map_length_rejects_an_unknown_value() {
let settings = default_settings();
let err = AxisFlag::MapLength
.parse_and_apply(settings, "nonsense")
.expect_err("should fail");
assert!(err.contains("nonsense"), "{err}");
}
}