use std::cell::Cell;
use std::collections::BTreeMap;
use std::collections::HashMap;
use cadmpeg_core::CodecError;
use cadmpeg_ir::transform::Transform;
pub(crate) mod target;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct Ref(pub u64);
impl std::fmt::Display for Ref {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "#{}", self.0)
}
}
#[derive(Clone, Copy)]
enum Refusal {
NonFiniteReal(f64),
NonSimilarOperator(Transform),
}
pub(crate) struct Emitter {
lines: Vec<String>,
counts: BTreeMap<&'static str, usize>,
interned: HashMap<String, Ref>,
refusal: Cell<Option<Refusal>>,
}
impl Emitter {
pub(crate) fn new() -> Self {
Emitter {
lines: Vec::new(),
counts: BTreeMap::new(),
interned: HashMap::new(),
refusal: Cell::new(None),
}
}
fn refuse(&self, refusal: Refusal) {
if self.refusal.get().is_none() {
self.refusal.set(Some(refusal));
}
}
pub(crate) fn refuse_operator(&self, transform: Transform) {
self.refuse(Refusal::NonSimilarOperator(transform));
}
pub(crate) fn real(&self, v: f64) -> String {
part21_real(v).unwrap_or_else(|| {
self.refuse(Refusal::NonFiniteReal(v));
String::new()
})
}
pub(crate) fn emit(&mut self, type_: &'static str, params: &str) -> Ref {
let id = cadmpeg_core::decode::u64_from_index(self.lines.len()) + 1;
self.lines.push(format!("#{id} = {type_}({params});"));
*self.counts.entry(type_).or_insert(0) += 1;
Ref(id)
}
pub(crate) fn emit_raw(&mut self, tally: &'static str, body: &str) -> Ref {
let id = cadmpeg_core::decode::u64_from_index(self.lines.len()) + 1;
self.lines.push(format!("#{id} = {body};"));
*self.counts.entry(tally).or_insert(0) += 1;
Ref(id)
}
pub(crate) fn emit_interned(&mut self, type_: &'static str, params: &str) -> Ref {
let key = format!("{type_}|{params}");
if let Some(r) = self.interned.get(&key) {
return *r;
}
let r = self.emit(type_, params);
self.interned.insert(key, r);
r
}
pub(crate) fn counts(&self) -> BTreeMap<String, usize> {
self.counts
.iter()
.map(|(type_, count)| ((*type_).to_string(), *count))
.collect()
}
pub(crate) fn into_lines(self) -> Result<Vec<String>, CodecError> {
match self.refusal.get() {
None => Ok(self.lines),
Some(Refusal::NonFiniteReal(value)) => Err(CodecError::NotImplemented(format!(
"STEP writer computed the non-finite real {value}, which Part 21 cannot state"
))),
Some(Refusal::NonSimilarOperator(transform)) => {
Err(CodecError::NotImplemented(format!(
"STEP writer cannot state the transform with rows {:?} as a \
CARTESIAN_TRANSFORMATION_OPERATOR_3D, which states a similarity",
transform.rows()
)))
}
}
}
}
fn part21_real(v: f64) -> Option<String> {
if !v.is_finite() {
return None;
}
let mut s = format!("{v}");
if let Some(e_pos) = s.find(['e', 'E']) {
let (mantissa, exp) = s.split_at(e_pos);
let exp = &exp[1..];
let mantissa = if mantissa.contains('.') {
mantissa.to_string()
} else {
format!("{mantissa}.")
};
let exp = if let Some(rest) = exp.strip_prefix('-') {
format!("-{rest}")
} else {
exp.strip_prefix('+').unwrap_or(exp).to_string()
};
return Some(format!("{mantissa}E{exp}"));
}
if !s.contains('.') {
s.push('.');
}
Some(s)
}
pub(crate) fn string(s: &str) -> String {
format!("'{}'", crate::strings::encode(s))
}
pub(crate) fn refs(items: &[Ref]) -> String {
let mut out = String::from("(");
for (i, r) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push('#');
out.push_str(&r.0.to_string());
}
out.push(')');
out
}
#[cfg(test)]
pub(crate) mod tests;