Skip to main content

viva_genapi/
nodemap.rs

1//! NodeMap implementation for runtime feature access.
2
3use std::cell::Cell;
4use std::collections::{HashMap, HashSet, hash_map::Entry as HashMapEntry};
5
6use tracing::{debug, trace, warn};
7use viva_genapi_xml::{
8    AccessMode, AddressTerm, Addressing, EnumEntryDecl, EnumValueSrc, FloatEncoding,
9    FormulaBindings, IndexOffset, NodeDecl, PredicateRefs, Sign, SkippedNode, Visibility, XmlModel,
10};
11
12use crate::bitops::{extract, insert};
13use crate::conversions::{
14    apply_scale, bytes_to_i64, decode_ieee754, encode_bitfield_value, encode_float, encode_ieee754,
15    get_raw_or_read, i64_to_bytes, interpret_bitfield_value, map_bitops_error, round_to_i64,
16};
17use crate::nodes::{
18    BooleanNode, CategoryNode, CommandNode, ConverterNode, EnumMapping, EnumNode, FloatNode,
19    IntConverterNode, IntegerNode, Node, RegisterNode, SkNode, StringNode,
20};
21use crate::swissknife::{
22    AstNode as SkAst, EvalError as SkEvalError, EvalMode, Value as SkValue, collect_identifiers,
23    evaluate as eval_ast, is_builtin_constant, parse_expression, substitute,
24};
25use crate::{GenApiError, RegisterIo, SkOutput};
26
27/// Runtime nodemap built from an [`XmlModel`] capable of reading and writing
28/// feature values via a [`RegisterIo`] transport.
29#[derive(Debug)]
30pub struct NodeMap {
31    version: String,
32    nodes: HashMap<String, Node>,
33    dependents: HashMap<String, Vec<String>>,
34    skipped: Vec<SkippedNode>,
35    generation: Cell<u64>,
36}
37
38fn register_addressing_dependency(
39    dependents: &mut HashMap<String, Vec<String>>,
40    node_name: &str,
41    addressing: &Addressing,
42) {
43    for provider in addressing.referenced_nodes() {
44        dependents
45            .entry(provider.to_string())
46            .or_default()
47            .push(node_name.to_string());
48    }
49}
50
51fn register_predicate_dependencies(
52    dependents: &mut HashMap<String, Vec<String>>,
53    node_name: &str,
54    predicates: &PredicateRefs,
55) {
56    for provider in predicates.references() {
57        dependents
58            .entry(provider.to_string())
59            .or_default()
60            .push(node_name.to_string());
61    }
62}
63
64fn ensure_readable(access: &AccessMode, name: &str) -> Result<(), GenApiError> {
65    if matches!(access, AccessMode::WO) {
66        return Err(GenApiError::Access(name.to_string()));
67    }
68    Ok(())
69}
70
71/// Refuse a register bound to a port we do not route.
72///
73/// `<pPort>` selects which port a register's address is relative to. Absent, or
74/// `"Device"`, means the device's own register space; anything else — a chunk
75/// port, an event port, a serial port — is a different address space entirely.
76///
77/// Reading such a node through the device port would not fail. It would return
78/// whatever happens to live at that address: the Micro-Epsilon scanCONTROL's
79/// three `Chunk*Results` registers all sit at address `0x0`, so a device-port
80/// read hands back GVCP bootstrap registers dressed as measurement data. That
81/// is the silent-wrong-answer failure ADR-0018 exists to refuse, so the node is
82/// parsed and listed but not readable until GA-12 routes ports properly.
83///
84/// This is deliberately stricter than the ~200 `IntReg`/`FloatReg`/`StringReg`
85/// nodes on non-device ports that this crate already exposes without a guard.
86/// The asymmetry is GA-12's debt, not a new inconsistency: new code conforms,
87/// and the old code is on the list.
88fn ensure_device_port(name: &str, port: Option<&str>) -> Result<(), GenApiError> {
89    match port {
90        None => Ok(()),
91        Some(p) if p.eq_ignore_ascii_case("Device") => Ok(()),
92        Some(p) => Err(GenApiError::Unavailable(format!(
93            "register '{name}' is bound to port '{p}'; \
94             non-device ports are not routed yet (GA-12)"
95        ))),
96    }
97}
98
99fn ensure_writable(access: &AccessMode, name: &str) -> Result<(), GenApiError> {
100    if matches!(access, AccessMode::RO) {
101        return Err(GenApiError::Access(name.to_string()));
102    }
103    Ok(())
104}
105
106impl NodeMap {
107    /// Return the schema version string associated with the XML description.
108    pub fn version(&self) -> &str {
109        &self.version
110    }
111
112    /// Fetch a node by name for inspection.
113    pub fn node(&self, name: &str) -> Option<&Node> {
114        self.nodes.get(name)
115    }
116
117    /// Return an iterator over all node names in the map.
118    pub fn node_names(&self) -> impl Iterator<Item = &str> {
119        self.nodes.keys().map(|s| s.as_str())
120    }
121
122    /// Return the list of nodes that should be invalidated when `name` changes.
123    ///
124    /// Returns an empty slice if the node has no dependents.
125    pub fn dependents(&self, name: &str) -> &[String] {
126        self.dependents
127            .get(name)
128            .map(|v| v.as_slice())
129            .unwrap_or(&[])
130    }
131
132    /// Return all category nodes as `(name, children)` pairs.
133    pub fn categories(&self) -> Vec<(&str, &[String])> {
134        self.nodes
135            .values()
136            .filter_map(|node| match node {
137                Node::Category(cat) => Some((cat.name.as_str(), cat.children.as_slice())),
138                _ => None,
139            })
140            .collect()
141    }
142
143    /// Return names of nodes visible at the given level or below.
144    ///
145    /// A node with `Visibility::Expert` is visible at level `Expert` and `Guru`,
146    /// but not at `Beginner`.
147    pub fn nodes_at_visibility(&self, level: Visibility) -> Vec<&str> {
148        self.nodes
149            .iter()
150            .filter(|(_, node)| node.visibility() <= level)
151            .map(|(name, _)| name.as_str())
152            .collect()
153    }
154
155    /// Construct a [`NodeMap`] from an [`XmlModel`], validating formulas.
156    ///
157    /// A declaration that cannot be turned into a runtime node is dropped and
158    /// recorded in [`NodeMap::skipped`] rather than failing the whole model.
159    /// Cameras carry thousands of nodes and only a handful of them matter to
160    /// any one application; refusing to open a camera because one obscure
161    /// feature is unrepresentable serves nobody.
162    pub fn try_from_xml(model: XmlModel) -> Result<Self, GenApiError> {
163        let mut nodes = HashMap::new();
164        let mut dependents: HashMap<String, Vec<String>> = HashMap::new();
165        // Losses from the XML layer travel with the ones from this layer. A
166        // consumer holding a NodeMap has no access to the XmlModel it was
167        // built from, so leaving them behind would make a feature the parser
168        // could not read indistinguishable from one the camera does not have.
169        let mut skipped = model.skipped;
170        for decl in model.nodes {
171            let tag = decl.kind().to_string();
172            let name = Some(decl.name().to_string());
173            // A node we cannot build costs that one feature, not the camera.
174            // The same isolation the XML layer already applies (issue #48) --
175            // a single unusual declaration must not make a camera unopenable.
176            let mut local: HashMap<String, Vec<String>> = HashMap::new();
177            match build_node(decl, &mut local) {
178                Ok((node_name, node)) => {
179                    for (provider, mut names) in local {
180                        dependents.entry(provider).or_default().append(&mut names);
181                    }
182                    nodes.insert(node_name, node);
183                }
184                Err(err) => {
185                    warn!(
186                        tag = %tag,
187                        node = name.as_deref().unwrap_or("<unnamed>"),
188                        error = %err,
189                        "dropping GenApi node"
190                    );
191                    skipped.push(SkippedNode {
192                        tag,
193                        name,
194                        error: err.to_string(),
195                    });
196                }
197            }
198        }
199
200        Ok(NodeMap {
201            version: model.version,
202            nodes,
203            dependents,
204            skipped,
205            generation: Cell::new(0),
206        })
207    }
208
209    /// Declarations this camera has that we do not expose.
210    ///
211    /// Covers both losses: a node the XML parser could not read, and one it
212    /// read but that could not be turned into a runtime node. Empty for a
213    /// document we fully understand; anything listed here is worth reporting
214    /// as a bug. `viva-camctl report` prints it.
215    pub fn skipped(&self) -> &[SkippedNode] {
216        &self.skipped
217    }
218
219    /// Read an integer feature value using the provided transport.
220    pub fn get_integer(&self, name: &str, io: &dyn RegisterIo) -> Result<i64, GenApiError> {
221        if let Some(Node::IntConverter(_)) = self.nodes.get(name) {
222            return self.get_int_converter(name, io);
223        }
224        if let Some(output) = self.nodes.get(name).and_then(|node| match node {
225            Node::SwissKnife(sk) => Some(sk.output),
226            _ => None,
227        }) {
228            return match output {
229                SkOutput::Integer => {
230                    let node = match self.nodes.get(name) {
231                        Some(Node::SwissKnife(node)) => node,
232                        _ => unreachable!("node vanished during lookup"),
233                    };
234                    let mut stack = HashSet::new();
235                    let value = self.evaluate_swissknife(node, io, &mut stack)?;
236                    sk_to_i64(name, value)
237                }
238                SkOutput::Float => Err(GenApiError::Type(name.to_string())),
239            };
240        }
241        let node = self.get_integer_node(name)?;
242        ensure_readable(&node.access, name)?;
243        self.ensure_selectors(name, &node.selected_if, io)?;
244        // Return static value if present.
245        if let Some(v) = node.value {
246            return Ok(v);
247        }
248        // Delegate to pValue node if present.
249        if let Some(ref pv) = node.pvalue {
250            let pv = pv.clone();
251            return self.get_integer(&pv, io);
252        }
253        let addressing = node
254            .addressing
255            .as_ref()
256            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
257        let (address, len) = self.resolve_address(name, addressing, io)?;
258        if let Some(value) = *node.cache.borrow() {
259            return Ok(value);
260        }
261        let raw = io.read(address, len as usize).map_err(|err| match err {
262            GenApiError::Io(_) => err,
263            other => other,
264        })?;
265        let value = if let Some(bitfield) = node.bitfield {
266            let extracted = extract(&raw, bitfield).map_err(|err| map_bitops_error(name, err))?;
267            interpret_bitfield_value(
268                name,
269                extracted,
270                bitfield.bit_length,
271                integer_sign(node).is_signed(),
272            )?
273        } else {
274            bytes_to_i64(name, &raw, integer_sign(node))?
275        };
276        debug!(node = %name, raw = value, "read integer feature");
277        node.cache.replace(Some(value));
278        node.raw_cache.replace(Some(raw));
279        Ok(value)
280    }
281
282    /// Write an integer feature and update dependent caches.
283    pub fn set_integer(
284        &mut self,
285        name: &str,
286        value: i64,
287        io: &dyn RegisterIo,
288    ) -> Result<(), GenApiError> {
289        if let Some(Node::IntConverter(_)) = self.nodes.get(name) {
290            return self.set_int_converter(name, value, io);
291        }
292        let node = self.get_integer_node(name)?;
293        self.ensure_writable_now(name, &node.access, io)?;
294        self.ensure_selectors(name, &node.selected_if, io)?;
295        if let Some(ref pv) = node.pvalue {
296            let pv = pv.clone();
297            return self.set_integer(&pv, value, io);
298        }
299        let addressing = node
300            .addressing
301            .as_ref()
302            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
303        let (address, len) = self.resolve_address(name, addressing, io)?;
304        if value < node.min || value > node.max {
305            return Err(GenApiError::Range(name.to_string()));
306        }
307        if let Some(inc) = node.inc
308            && inc != 0
309            && (value - node.min) % inc != 0
310        {
311            return Err(GenApiError::Range(name.to_string()));
312        }
313        if let Some(bitfield) = node.bitfield {
314            let encoded = encode_bitfield_value(name, value, bitfield.bit_length, node.min < 0)?;
315            let mut raw = get_raw_or_read(&node.raw_cache, io, address, len)?;
316            insert(&mut raw, bitfield, encoded).map_err(|err| map_bitops_error(name, err))?;
317            debug!(node = %name, raw = value, "write integer feature");
318            io.write(address, &raw).map_err(|err| match err {
319                GenApiError::Io(_) => err,
320                other => other,
321            })?;
322            node.cache.replace(Some(value));
323            node.raw_cache.replace(Some(raw));
324        } else {
325            let bytes = i64_to_bytes(name, value, len, integer_sign(node))?;
326            debug!(node = %name, raw = value, "write integer feature");
327            io.write(address, &bytes).map_err(|err| match err {
328                GenApiError::Io(_) => err,
329                other => other,
330            })?;
331            node.cache.replace(Some(value));
332            node.raw_cache.replace(Some(bytes));
333        }
334        self.invalidate_dependents(name);
335        Ok(())
336    }
337
338    /// Read a floating point feature.
339    pub fn get_float(&self, name: &str, io: &dyn RegisterIo) -> Result<f64, GenApiError> {
340        match self.nodes.get(name) {
341            Some(Node::Converter(_)) => return self.get_converter(name, io),
342            Some(Node::IntConverter(_)) => {
343                return self.get_int_converter(name, io).map(|v| v as f64);
344            }
345            _ => {}
346        }
347        if let Some(output) = self.nodes.get(name).and_then(|node| match node {
348            Node::SwissKnife(sk) => Some(sk.output),
349            _ => None,
350        }) {
351            return match output {
352                SkOutput::Float => {
353                    let node = match self.nodes.get(name) {
354                        Some(Node::SwissKnife(node)) => node,
355                        _ => unreachable!("node vanished during lookup"),
356                    };
357                    let mut stack = HashSet::new();
358                    let value = self.evaluate_swissknife(node, io, &mut stack)?;
359                    Ok(value.as_f64())
360                }
361                SkOutput::Integer => self.get_integer(name, io).map(|v| v as f64),
362            };
363        }
364        let node = self.get_float_node(name)?;
365        ensure_readable(&node.access, name)?;
366        self.ensure_selectors(name, &node.selected_if, io)?;
367        if let Some(ref pv) = node.pvalue {
368            let pv = pv.clone();
369            return self.get_float(&pv, io);
370        }
371        let addressing = node
372            .addressing
373            .as_ref()
374            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
375        let (address, len) = self.resolve_address(name, addressing, io)?;
376        if let Some(value) = *node.cache.borrow() {
377            return Ok(value);
378        }
379        let raw = io.read(address, len as usize).map_err(|err| match err {
380            GenApiError::Io(_) => err,
381            other => other,
382        })?;
383        let value = match node.encoding {
384            FloatEncoding::Ieee754 => {
385                let v = decode_ieee754(name, &raw, node.byte_order)?;
386                debug!(node = %name, value = v, "read float feature (ieee754)");
387                v
388            }
389            FloatEncoding::ScaledInteger => {
390                // `<Float>`/`<FloatReg>` declare no `<Sign>`; a scaled raw
391                // value is conventionally signed so an offset can go either way.
392                let raw_value = bytes_to_i64(name, &raw, Sign::Signed)?;
393                let v = apply_scale(node, raw_value as f64);
394                debug!(node = %name, raw = raw_value, value = v, "read float feature (scaled)");
395                v
396            }
397        };
398        node.cache.replace(Some(value));
399        Ok(value)
400    }
401
402    /// Write a floating point feature using the scale/offset conversion.
403    pub fn set_float(
404        &mut self,
405        name: &str,
406        value: f64,
407        io: &dyn RegisterIo,
408    ) -> Result<(), GenApiError> {
409        match self.nodes.get(name) {
410            Some(Node::Converter(_)) => return self.set_converter(name, value, io),
411            Some(Node::IntConverter(_)) => {
412                return self.set_int_converter(name, round_to_i64(name, value)?, io);
413            }
414            _ => {}
415        }
416        let node = self.get_float_node(name)?;
417        self.ensure_writable_now(name, &node.access, io)?;
418        self.ensure_selectors(name, &node.selected_if, io)?;
419        if let Some(ref pv) = node.pvalue {
420            let pv = pv.clone();
421            return self.set_float(&pv, value, io);
422        }
423        let addressing = node
424            .addressing
425            .as_ref()
426            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
427        let (address, len) = self.resolve_address(name, addressing, io)?;
428        if value < node.min || value > node.max {
429            return Err(GenApiError::Range(name.to_string()));
430        }
431        let bytes = match node.encoding {
432            FloatEncoding::Ieee754 => {
433                let bytes = encode_ieee754(name, value, len, node.byte_order)?;
434                debug!(node = %name, value, "write float feature (ieee754)");
435                bytes
436            }
437            FloatEncoding::ScaledInteger => {
438                let raw = encode_float(node, value)?;
439                let bytes = i64_to_bytes(name, raw, len, Sign::Signed)?;
440                debug!(node = %name, raw, value, "write float feature (scaled)");
441                bytes
442            }
443        };
444        io.write(address, &bytes).map_err(|err| match err {
445            GenApiError::Io(_) => err,
446            other => other,
447        })?;
448        node.cache.replace(Some(value));
449        self.invalidate_dependents(name);
450        Ok(())
451    }
452
453    /// Read an enumeration feature returning the symbolic entry name.
454    pub fn get_enum(&self, name: &str, io: &dyn RegisterIo) -> Result<String, GenApiError> {
455        let node = self.get_enum_node(name)?;
456        ensure_readable(&node.access, name)?;
457        self.ensure_selectors(name, &node.selected_if, io)?;
458        // When pValue is set, read the integer from the delegate node.
459        if let Some(ref pv) = node.pvalue {
460            let pv = pv.clone();
461            if let Some(value) = node.value_cache.borrow().clone() {
462                return Ok(value);
463            }
464            let raw_value = self.get_integer(&pv, io)?;
465            let entry = self.lookup_enum_entry(node, raw_value, io)?;
466            node.value_cache.replace(Some(entry.clone()));
467            return Ok(entry);
468        }
469        let addressing = node
470            .addressing
471            .as_ref()
472            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing")))?;
473        let (address, len) = self.resolve_address(name, addressing, io)?;
474        if let Some(value) = node.value_cache.borrow().clone() {
475            return Ok(value);
476        }
477        let raw = io.read(address, len as usize).map_err(|err| match err {
478            GenApiError::Io(_) => err,
479            other => other,
480        })?;
481        // `<Enumeration>` declares no `<Sign>`; entry values may be negative.
482        let raw_value = bytes_to_i64(name, &raw, Sign::Signed)?;
483        let entry = self.lookup_enum_entry(node, raw_value, io)?;
484        debug!(node = %name, raw = raw_value, entry = %entry, "read enum feature");
485        node.value_cache.replace(Some(entry.clone()));
486        Ok(entry)
487    }
488
489    /// Write an enumeration entry.
490    pub fn set_enum(
491        &mut self,
492        name: &str,
493        entry: &str,
494        io: &dyn RegisterIo,
495    ) -> Result<(), GenApiError> {
496        let node = self.get_enum_node(name)?;
497        self.ensure_writable_now(name, &node.access, io)?;
498        self.ensure_selectors(name, &node.selected_if, io)?;
499        if let Some(ref pv) = node.pvalue {
500            let pv = pv.clone();
501            let entry_decl = node
502                .entries
503                .iter()
504                .find(|candidate| candidate.name == entry)
505                .ok_or_else(|| GenApiError::EnumNoSuchEntry {
506                    node: name.to_string(),
507                    entry: entry.to_string(),
508                })?;
509            let raw_value = self.resolve_enum_entry_value(node, entry_decl, io)?;
510            let entry_str = entry.to_string();
511            // Re-borrow node after mutable self call.
512            self.set_integer(&pv, raw_value, io)?;
513            let node = self.get_enum_node(name)?;
514            node.value_cache.replace(Some(entry_str));
515            node.invalidate();
516            self.invalidate_dependents(name);
517            return Ok(());
518        }
519        let addressing = node
520            .addressing
521            .as_ref()
522            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing")))?;
523        let (address, len) = self.resolve_address(name, addressing, io)?;
524        let entry_decl = node
525            .entries
526            .iter()
527            .find(|candidate| candidate.name == entry)
528            .ok_or_else(|| GenApiError::EnumNoSuchEntry {
529                node: name.to_string(),
530                entry: entry.to_string(),
531            })?;
532        let raw = self.resolve_enum_entry_value(node, entry_decl, io)?;
533        let bytes = i64_to_bytes(name, raw, len, Sign::Signed)?;
534        debug!(node = %name, raw, entry, "write enum feature");
535        io.write(address, &bytes).map_err(|err| match err {
536            GenApiError::Io(_) => err,
537            other => other,
538        })?;
539        node.value_cache.replace(None);
540        self.invalidate_dependents(name);
541        Ok(())
542    }
543
544    /// List the available entry names for an enumeration feature.
545    pub fn enum_entries(&self, name: &str) -> Result<Vec<String>, GenApiError> {
546        let node = self.get_enum_node(name)?;
547        if let Some(mapping) = node.mapping_cache.borrow().as_ref() {
548            let mut names: Vec<_> = mapping.by_name.keys().cloned().collect();
549            names.sort();
550            names.dedup();
551            return Ok(names);
552        }
553        let mut names: Vec<_> = node
554            .entries
555            .iter()
556            .map(|entry| entry.name.clone())
557            .collect();
558        names.sort();
559        names.dedup();
560        Ok(names)
561    }
562
563    /// Evaluate `pIsImplemented` for `name`, returning `true` when the feature
564    /// is implemented by the device.
565    ///
566    /// Absent `pIsImplemented` defaults to `true` (matching the GenICam spec:
567    /// an undeclared predicate means "always implemented"). Evaluation errors
568    /// propagate to the caller so bad XML is visible rather than silently
569    /// reported as implemented.
570    pub fn is_implemented(&self, name: &str, io: &dyn RegisterIo) -> Result<bool, GenApiError> {
571        let prefs = self.predicate_refs(name)?;
572        match &prefs.p_is_implemented {
573            None => Ok(true),
574            Some(provider) => self.eval_predicate_ref(name, provider, io),
575        }
576    }
577
578    /// Evaluate `pIsAvailable` plus selector gating for `name`.
579    ///
580    /// Returns `false` when the feature is not implemented, when
581    /// `pIsAvailable` evaluates to zero, or when any `selected_if` rule is
582    /// violated by the current selector value. Callers that want pure XML
583    /// gating without selector checks should use [`NodeMap::is_implemented`]
584    /// instead.
585    pub fn is_available(&self, name: &str, io: &dyn RegisterIo) -> Result<bool, GenApiError> {
586        if !self.is_implemented(name, io)? {
587            return Ok(false);
588        }
589        let prefs = self.predicate_refs(name)?;
590        if let Some(provider) = &prefs.p_is_available
591            && !self.eval_predicate_ref(name, provider, io)?
592        {
593            return Ok(false);
594        }
595        let selected_if = self
596            .nodes
597            .get(name)
598            .and_then(Self::selected_if_slice)
599            .unwrap_or(&[]);
600        self.selectors_allow(selected_if, io)
601    }
602
603    /// Refuse a write the device's current state does not permit.
604    ///
605    /// The static `<AccessMode>` is only half the picture, and for a great
606    /// many real nodes it is the less informative half: FLIR's `ExposureTime`
607    /// declares no `<AccessMode>` at all — so it defaults to `RW` — and puts
608    /// the entire restriction in `<pIsLocked>ExposureTime_Lck</pIsLocked>`, a
609    /// device register. Checking only the static mode meant we sent writes the
610    /// camera's own description said were not allowed, and the device answered
611    /// `ACCESS_DENIED` (issue #45).
612    ///
613    /// Deliberately *not* routed through [`NodeMap::effective_access_mode`]:
614    /// that function collapses "unavailable" into `RO` because it serves a UI
615    /// that has a separate availability flag. Here the distinction is the
616    /// whole value of the error, so the two conditions are checked separately.
617    ///
618    /// Reads keep the static [`AccessMode::WO`] check only. Evaluating
619    /// predicates on every `get` would add device round-trips to the hottest
620    /// path in the library for a check the subsequent read reports anyway; a
621    /// refused write, by contrast, is worth one predicate evaluation to turn a
622    /// wire error into a named local one. See backlog GA-06.
623    fn ensure_writable_now(
624        &self,
625        name: &str,
626        access: &AccessMode,
627        io: &dyn RegisterIo,
628    ) -> Result<(), GenApiError> {
629        ensure_writable(access, name)?;
630        if !self.is_available(name, io)? {
631            return Err(GenApiError::Unavailable(name.to_string()));
632        }
633        let prefs = self.predicate_refs(name)?;
634        if let Some(provider) = &prefs.p_is_locked
635            && self.eval_predicate_ref(name, provider, io)?
636        {
637            return Err(GenApiError::Locked {
638                name: name.to_string(),
639                locked_by: provider.to_string(),
640            });
641        }
642        Ok(())
643    }
644
645    /// Return the effective [`AccessMode`] for `name` given the current
646    /// device state.
647    ///
648    /// - If the feature is unavailable (see [`NodeMap::is_available`]), the
649    ///   function returns `AccessMode::RO` — we cannot report "NA" without
650    ///   introducing a new variant, and Studio's wire protocol carries the
651    ///   availability flag separately.
652    /// - If `pIsLocked` evaluates truthy, `RW` downgrades to `RO`; `RO` and
653    ///   `WO` are unaffected.
654    /// - Otherwise the statically declared access mode applies.
655    pub fn effective_access_mode(
656        &self,
657        name: &str,
658        io: &dyn RegisterIo,
659    ) -> Result<AccessMode, GenApiError> {
660        let node = self
661            .nodes
662            .get(name)
663            .ok_or_else(|| GenApiError::NodeNotFound(name.to_string()))?;
664        let base = node.access_mode().unwrap_or(AccessMode::RO);
665        if !self.is_available(name, io)? {
666            return Ok(AccessMode::RO);
667        }
668        let prefs = self.predicate_refs(name)?;
669        if let Some(provider) = &prefs.p_is_locked
670            && self.eval_predicate_ref(name, provider, io)?
671        {
672            return Ok(match base {
673                AccessMode::RW => AccessMode::RO,
674                other => other,
675            });
676        }
677        Ok(base)
678    }
679
680    /// Return the subset of enum entries currently reported as available by
681    /// the device, or the full static list when no entry declares an
682    /// `pIsImplemented`/`pIsAvailable`.
683    ///
684    /// Falling back to the full list preserves current behaviour for XMLs
685    /// that don't gate individual entries, so callers stop seeing the stale
686    /// static list when the new predicates are added and otherwise behave as
687    /// before.
688    pub fn available_enum_entries(
689        &self,
690        name: &str,
691        io: &dyn RegisterIo,
692    ) -> Result<Vec<String>, GenApiError> {
693        let node = self.get_enum_node(name)?;
694        let any_entry_predicate = node.entries.iter().any(|e| !e.predicates.is_empty());
695        if !any_entry_predicate {
696            return self.enum_entries(name);
697        }
698        let mut out = Vec::new();
699        for entry in &node.entries {
700            if let Some(provider) = &entry.predicates.p_is_implemented
701                && !self.eval_predicate_ref(name, provider, io)?
702            {
703                continue;
704            }
705            if let Some(provider) = &entry.predicates.p_is_available
706                && !self.eval_predicate_ref(name, provider, io)?
707            {
708                continue;
709            }
710            out.push(entry.name.clone());
711        }
712        out.sort();
713        out.dedup();
714        Ok(out)
715    }
716
717    fn predicate_refs(&self, name: &str) -> Result<&PredicateRefs, GenApiError> {
718        self.nodes
719            .get(name)
720            .map(Node::predicates)
721            .ok_or_else(|| GenApiError::NodeNotFound(name.to_string()))
722    }
723
724    /// Evaluate a `pIs*` reference by reading the target node as an integer
725    /// truthy value.
726    ///
727    /// `ctx` is the node that owns the predicate; it is used for diagnostics
728    /// and cycle detection so a predicate that accidentally resolves back to
729    /// its own owner fails fast rather than recursing. Providers can be any
730    /// numeric-resolvable node: Integer, Boolean, Enum (integer form),
731    /// SwissKnife, or a Converter.
732    fn eval_predicate_ref(
733        &self,
734        ctx: &str,
735        provider: &str,
736        io: &dyn RegisterIo,
737    ) -> Result<bool, GenApiError> {
738        if provider == ctx {
739            return Err(GenApiError::ExprEval {
740                name: ctx.to_string(),
741                msg: "predicate references the node it gates".into(),
742            });
743        }
744        let mut stack = HashSet::new();
745        stack.insert(ctx.to_string());
746        let value = self.resolve_numeric(provider, io, &mut stack)?;
747        trace!(node = %ctx, provider, value, "predicate eval");
748        Ok(value != 0.0)
749    }
750
751    /// Non-erroring cousin of [`NodeMap::ensure_selectors`] — returns `Ok(false)`
752    /// when a selector gating rule rejects the current state, rather than
753    /// converting that into a [`GenApiError::Unavailable`].
754    fn selectors_allow(
755        &self,
756        rules: &[(String, Vec<String>)],
757        io: &dyn RegisterIo,
758    ) -> Result<bool, GenApiError> {
759        for (selector, allowed) in rules {
760            if allowed.is_empty() {
761                continue;
762            }
763            let current = self.get_selector_value(selector, io)?;
764            if !allowed.iter().any(|v| v == &current) {
765                return Ok(false);
766            }
767        }
768        Ok(true)
769    }
770
771    fn selected_if_slice(node: &Node) -> Option<&[(String, Vec<String>)]> {
772        match node {
773            Node::Integer(n) => Some(&n.selected_if),
774            Node::Float(n) => Some(&n.selected_if),
775            Node::Enum(n) => Some(&n.selected_if),
776            Node::Boolean(n) => Some(&n.selected_if),
777            _ => None,
778        }
779    }
780
781    /// Read a boolean feature.
782    pub fn get_bool(&self, name: &str, io: &dyn RegisterIo) -> Result<bool, GenApiError> {
783        let node = self.get_bool_node(name)?;
784        ensure_readable(&node.access, name)?;
785        self.ensure_selectors(name, &node.selected_if, io)?;
786        if let Some(ref pv) = node.pvalue {
787            let pv = pv.clone();
788            let raw = self.get_integer(&pv, io)?;
789            let on = node.on_value.unwrap_or(1);
790            return Ok(raw == on);
791        }
792        let addressing = node
793            .addressing
794            .as_ref()
795            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
796        let bitfield = node
797            .bitfield
798            .ok_or_else(|| GenApiError::Parse(format!("{name}: boolean without bitfield")))?;
799        let (address, len) = self.resolve_address(name, addressing, io)?;
800        if let Some(value) = *node.cache.borrow() {
801            return Ok(value);
802        }
803        let raw = io.read(address, len as usize).map_err(|err| match err {
804            GenApiError::Io(_) => err,
805            other => other,
806        })?;
807        let raw_value = extract(&raw, bitfield).map_err(|err| map_bitops_error(name, err))?;
808        let value = raw_value != 0;
809        debug!(node = %name, raw = raw_value, value, "read boolean feature");
810        node.cache.replace(Some(value));
811        node.raw_cache.replace(Some(raw));
812        Ok(value)
813    }
814
815    /// Write a boolean feature.
816    pub fn set_bool(
817        &mut self,
818        name: &str,
819        value: bool,
820        io: &dyn RegisterIo,
821    ) -> Result<(), GenApiError> {
822        let node = self.get_bool_node(name)?;
823        self.ensure_writable_now(name, &node.access, io)?;
824        self.ensure_selectors(name, &node.selected_if, io)?;
825        if let Some(ref pv) = node.pvalue {
826            let pv = pv.clone();
827            let on = node.on_value.unwrap_or(1);
828            let off = node.off_value.unwrap_or(0);
829            let raw = if value { on } else { off };
830            return self.set_integer(&pv, raw, io);
831        }
832        let addressing = node
833            .addressing
834            .as_ref()
835            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
836        let bitfield = node
837            .bitfield
838            .ok_or_else(|| GenApiError::Parse(format!("{name}: boolean without bitfield")))?;
839        let (address, len) = self.resolve_address(name, addressing, io)?;
840        let encoded = if value { 1 } else { 0 };
841        let mut raw = get_raw_or_read(&node.raw_cache, io, address, len)?;
842        insert(&mut raw, bitfield, encoded).map_err(|err| map_bitops_error(name, err))?;
843        debug!(node = %name, raw = encoded, value, "write boolean feature");
844        io.write(address, &raw).map_err(|err| match err {
845            GenApiError::Io(_) => err,
846            other => other,
847        })?;
848        node.cache.replace(Some(value));
849        node.raw_cache.replace(Some(raw));
850        self.invalidate_dependents(name);
851        Ok(())
852    }
853
854    /// Execute a command feature by writing a value to the command register.
855    pub fn exec_command(&mut self, name: &str, io: &dyn RegisterIo) -> Result<(), GenApiError> {
856        let node = self.get_command_node(name)?;
857        // Determine the value to write and the target.
858        let cmd_value = node.command_value.unwrap_or(1);
859
860        if let Some(ref pv) = node.pvalue {
861            // Delegate to the pValue node.
862            let pv = pv.clone();
863            debug!(node = %name, "execute command via pValue");
864            return self.set_integer(&pv, cmd_value, io);
865        }
866
867        let address = node
868            .address
869            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no address or pValue")))?;
870        if node.len == 0 {
871            return Err(GenApiError::Parse(format!(
872                "command node {name} has zero length"
873            )));
874        }
875        let data = i64_to_bytes(name, cmd_value, node.len, Sign::Signed)?;
876        debug!(node = %name, "execute command");
877        io.write(address, &data).map_err(|err| match err {
878            GenApiError::Io(_) => err,
879            other => other,
880        })?;
881        self.invalidate_dependents(name);
882        Ok(())
883    }
884
885    fn get_integer_node(&self, name: &str) -> Result<&IntegerNode, GenApiError> {
886        match self.nodes.get(name) {
887            Some(Node::Integer(node)) => Ok(node),
888            Some(_) => Err(GenApiError::Type(name.to_string())),
889            None => Err(GenApiError::NodeNotFound(name.to_string())),
890        }
891    }
892
893    fn get_float_node(&self, name: &str) -> Result<&FloatNode, GenApiError> {
894        match self.nodes.get(name) {
895            Some(Node::Float(node)) => Ok(node),
896            Some(_) => Err(GenApiError::Type(name.to_string())),
897            None => Err(GenApiError::NodeNotFound(name.to_string())),
898        }
899    }
900
901    fn get_enum_node(&self, name: &str) -> Result<&EnumNode, GenApiError> {
902        match self.nodes.get(name) {
903            Some(Node::Enum(node)) => Ok(node),
904            Some(_) => Err(GenApiError::Type(name.to_string())),
905            None => Err(GenApiError::NodeNotFound(name.to_string())),
906        }
907    }
908
909    fn get_bool_node(&self, name: &str) -> Result<&BooleanNode, GenApiError> {
910        match self.nodes.get(name) {
911            Some(Node::Boolean(node)) => Ok(node),
912            Some(_) => Err(GenApiError::Type(name.to_string())),
913            None => Err(GenApiError::NodeNotFound(name.to_string())),
914        }
915    }
916
917    fn get_command_node(&self, name: &str) -> Result<&CommandNode, GenApiError> {
918        match self.nodes.get(name) {
919            Some(Node::Command(node)) => Ok(node),
920            Some(_) => Err(GenApiError::Type(name.to_string())),
921            None => Err(GenApiError::NodeNotFound(name.to_string())),
922        }
923    }
924
925    fn ensure_selectors(
926        &self,
927        node_name: &str,
928        rules: &[(String, Vec<String>)],
929        io: &dyn RegisterIo,
930    ) -> Result<(), GenApiError> {
931        for (selector, allowed) in rules {
932            if allowed.is_empty() {
933                continue;
934            }
935            let current = self.get_selector_value(selector, io)?;
936            if !allowed.iter().any(|value| value == &current) {
937                return Err(GenApiError::Unavailable(format!(
938                    "node '{node_name}' unavailable for selector '{selector}={current}'"
939                )));
940            }
941        }
942        Ok(())
943    }
944
945    fn lookup_enum_entry(
946        &self,
947        node: &EnumNode,
948        raw_value: i64,
949        io: &dyn RegisterIo,
950    ) -> Result<String, GenApiError> {
951        {
952            let mut cache = node.mapping_cache.borrow_mut();
953            if cache.is_none() {
954                *cache = Some(self.build_enum_mapping(node, io)?);
955            }
956            if let Some(mapping) = cache.as_ref()
957                && let Some(entry) = mapping.by_value.get(&raw_value)
958            {
959                return Ok(entry.clone());
960            }
961            *cache = Some(self.build_enum_mapping(node, io)?);
962            if let Some(mapping) = cache.as_ref()
963                && let Some(entry) = mapping.by_value.get(&raw_value)
964            {
965                return Ok(entry.clone());
966            }
967        }
968        Err(GenApiError::EnumValueUnknown {
969            node: node.name.clone(),
970            value: raw_value,
971        })
972    }
973
974    fn build_enum_mapping(
975        &self,
976        node: &EnumNode,
977        io: &dyn RegisterIo,
978    ) -> Result<EnumMapping, GenApiError> {
979        let mut by_value = HashMap::new();
980        let mut by_name = HashMap::new();
981
982        for entry in &node.entries {
983            let value = self.resolve_enum_entry_value(node, entry, io)?;
984            match by_value.entry(value) {
985                HashMapEntry::Vacant(slot) => {
986                    slot.insert(entry.name.clone());
987                }
988                HashMapEntry::Occupied(existing) => {
989                    warn!(
990                        enum_node = %node.name,
991                        value,
992                        kept = %existing.get(),
993                        dropped = %entry.name,
994                        "duplicate enum value"
995                    );
996                }
997            }
998            by_name.insert(entry.name.clone(), value);
999        }
1000
1001        let mut summary: Vec<_> = by_value
1002            .iter()
1003            .map(|(value, name)| (*value, name.clone()))
1004            .collect();
1005        summary.sort_by_key(|(value, _)| *value);
1006        debug!(node = %node.name, entries = ?summary, "build enum mapping");
1007
1008        Ok(EnumMapping { by_value, by_name })
1009    }
1010
1011    fn resolve_enum_entry_value(
1012        &self,
1013        node: &EnumNode,
1014        entry: &EnumEntryDecl,
1015        io: &dyn RegisterIo,
1016    ) -> Result<i64, GenApiError> {
1017        match &entry.value {
1018            EnumValueSrc::Literal(value) => Ok(*value),
1019            EnumValueSrc::FromNode(provider) => {
1020                let value = self.get_integer(provider, io)?;
1021                trace!(
1022                    enum_node = %node.name,
1023                    entry = %entry.name,
1024                    provider = %provider,
1025                    value,
1026                    "resolved enum entry from provider"
1027                );
1028                Ok(value)
1029            }
1030        }
1031    }
1032
1033    /// Resolve the device register address and length backing a feature.
1034    ///
1035    /// This is the addressing half of typed feature access, exposed for
1036    /// callers that need raw register I/O through [`RegisterIo`] — a
1037    /// file-transfer buffer, for instance, whose address the XML supplies
1038    /// through `<pAddress>` and which no typed accessor covers. Address
1039    /// terms, `<pIndex>` scaling and selector blocks resolve exactly as they
1040    /// do for `get_integer` and friends, so a caller does not have to
1041    /// reimplement GenICam addressing.
1042    ///
1043    /// Returns [`GenApiError::Unavailable`] for a node that has no addressing
1044    /// of its own because it delegates through `<pValue>`, and for a
1045    /// selector-mapped node whose current selector value has no block.
1046    pub fn register_address(
1047        &self,
1048        name: &str,
1049        io: &dyn RegisterIo,
1050    ) -> Result<(u64, u32), GenApiError> {
1051        let node = self
1052            .node(name)
1053            .ok_or_else(|| GenApiError::NodeNotFound(name.to_string()))?;
1054        let addressing = match node {
1055            Node::Integer(node) => node.addressing.as_ref(),
1056            Node::Float(node) => node.addressing.as_ref(),
1057            Node::Enum(node) => node.addressing.as_ref(),
1058            Node::Boolean(node) => node.addressing.as_ref(),
1059            Node::String(node) => Some(&node.addressing),
1060            Node::Register(node) => Some(&node.addressing),
1061            _ => None,
1062        }
1063        .ok_or_else(|| {
1064            GenApiError::Unavailable(format!("node '{name}' has no register addressing"))
1065        })?;
1066        self.resolve_address(name, addressing, io)
1067    }
1068
1069    fn resolve_address(
1070        &self,
1071        node_name: &str,
1072        addressing: &Addressing,
1073        io: &dyn RegisterIo,
1074    ) -> Result<(u64, u32), GenApiError> {
1075        match addressing {
1076            Addressing::Sum { terms, len } => {
1077                let mut address: u64 = 0;
1078                for term in terms {
1079                    address =
1080                        address.wrapping_add(self.resolve_address_term(node_name, term, *len, io)?);
1081                }
1082                if terms.len() > 1 {
1083                    debug!(
1084                        node = %node_name,
1085                        terms = terms.len(),
1086                        address = format_args!("0x{address:X}"),
1087                        len = *len,
1088                        "resolve summed address"
1089                    );
1090                }
1091                Ok((address, *len))
1092            }
1093            Addressing::BySelector { selector, map } => {
1094                let value = self.get_selector_value(selector, io)?;
1095                if let Some((_, (address, len))) = map.iter().find(|(name, _)| name == &value) {
1096                    let addr = *address;
1097                    let len = *len;
1098                    debug!(
1099                        node = %node_name,
1100                        selector = %selector,
1101                        value = %value,
1102                        address = format_args!("0x{addr:X}"),
1103                        len,
1104                        "resolve address via selector"
1105                    );
1106                    Ok((addr, len))
1107                } else {
1108                    Err(GenApiError::Unavailable(format!(
1109                        "node '{node_name}' unavailable for selector '{selector}={value}'"
1110                    )))
1111                }
1112            }
1113        }
1114    }
1115
1116    /// Resolve one address term to the offset it contributes.
1117    fn resolve_address_term(
1118        &self,
1119        node_name: &str,
1120        term: &AddressTerm,
1121        len: u32,
1122        io: &dyn RegisterIo,
1123    ) -> Result<u64, GenApiError> {
1124        let bad = |addr: i64| GenApiError::BadIndirectAddress {
1125            name: node_name.to_string(),
1126            addr,
1127        };
1128        match term {
1129            AddressTerm::Fixed(offset) => Ok(*offset),
1130            AddressTerm::Node(provider) => {
1131                let value = self.get_integer(provider, io)?;
1132                u64::try_from(value).map_err(|_| bad(value))
1133            }
1134            AddressTerm::Index { node, offset } => {
1135                let index = self.get_integer(node, io)?;
1136                let index = u64::try_from(index).map_err(|_| bad(index))?;
1137                let stride = match offset {
1138                    IndexOffset::Fixed(stride) => *stride,
1139                    IndexOffset::Node(provider) => {
1140                        let value = self.get_integer(provider, io)?;
1141                        u64::try_from(value).map_err(|_| bad(value))?
1142                    }
1143                    // A bare `<pIndex>` strides by the register length.
1144                    IndexOffset::Length => u64::from(len),
1145                };
1146                Ok(index.wrapping_mul(stride))
1147            }
1148        }
1149    }
1150
1151    fn get_selector_value(
1152        &self,
1153        selector: &str,
1154        io: &dyn RegisterIo,
1155    ) -> Result<String, GenApiError> {
1156        match self.nodes.get(selector) {
1157            Some(Node::Enum(_)) => self.get_enum(selector, io),
1158            Some(Node::Boolean(_)) => Ok(self.get_bool(selector, io)?.to_string()),
1159            Some(Node::Integer(_)) => Ok(self.get_integer(selector, io)?.to_string()),
1160            Some(_) => Err(GenApiError::Parse(format!(
1161                "selector {selector} has unsupported type"
1162            ))),
1163            None => Err(GenApiError::NodeNotFound(selector.to_string())),
1164        }
1165    }
1166
1167    /// Bind a formula's declared variables and evaluate it.
1168    ///
1169    /// Shared by SwissKnife, Converter and IntConverter: they differ only in
1170    /// which AST and variable list they hand over, in the arithmetic mode, and
1171    /// in any variables the caller binds directly (`FROM` and `OLD` on a
1172    /// write, which have no provider node to read).
1173    #[allow(clippy::too_many_arguments)]
1174    fn eval_formula(
1175        &self,
1176        name: &str,
1177        ast: &SkAst,
1178        vars: &[(String, String)],
1179        overrides: &[(&str, SkValue)],
1180        mode: EvalMode,
1181        io: &dyn RegisterIo,
1182        stack: &mut HashSet<String>,
1183    ) -> Result<SkValue, GenApiError> {
1184        let mut values: HashMap<String, SkValue> = HashMap::new();
1185        for (var, provider) in vars {
1186            if overrides.iter().any(|(ident, _)| ident == var) {
1187                continue;
1188            }
1189            values.insert(var.clone(), self.resolve_value(provider, io, stack)?);
1190        }
1191        for (ident, value) in overrides {
1192            values.insert((*ident).to_string(), *value);
1193        }
1194        let mut resolver = |ident: &str| -> Result<SkValue, SkEvalError> {
1195            values
1196                .get(ident)
1197                .copied()
1198                .ok_or_else(|| SkEvalError::UnknownVariable(ident.to_string()))
1199        };
1200        eval_ast(ast, &mut resolver, mode).map_err(|err| expr_error(name, err))
1201    }
1202
1203    fn evaluate_swissknife(
1204        &self,
1205        node: &SkNode,
1206        io: &dyn RegisterIo,
1207        stack: &mut HashSet<String>,
1208    ) -> Result<SkValue, GenApiError> {
1209        if let Some((value, generation)) = *node.cache.borrow()
1210            && generation == self.generation.get()
1211        {
1212            return Ok(value);
1213        }
1214        if !stack.insert(node.name.clone()) {
1215            stack.remove(&node.name);
1216            return Err(GenApiError::ExprEval {
1217                name: node.name.clone(),
1218                msg: "cyclic dependency".into(),
1219            });
1220        }
1221        let current_gen = self.generation.get();
1222        let result = self.eval_formula(
1223            &node.name,
1224            &node.ast,
1225            &node.vars,
1226            &[],
1227            eval_mode(node.output),
1228            io,
1229            stack,
1230        );
1231        stack.remove(&node.name);
1232        let value = result?;
1233        debug!(node = %node.name, value = %value, "evaluate SwissKnife");
1234        node.cache.replace(Some((value, current_gen)));
1235        Ok(value)
1236    }
1237
1238    /// Resolve a node reference to the value a formula should see.
1239    ///
1240    /// Integer-typed providers stay integral: routing a 64-bit register value
1241    /// through `f64` on the way into a formula would round away its low bits.
1242    fn resolve_value(
1243        &self,
1244        provider: &str,
1245        io: &dyn RegisterIo,
1246        stack: &mut HashSet<String>,
1247    ) -> Result<SkValue, GenApiError> {
1248        match self.nodes.get(provider) {
1249            Some(Node::Integer(_)) => self.get_integer(provider, io).map(SkValue::Int),
1250            Some(Node::Float(_)) => self.get_float(provider, io).map(SkValue::Float),
1251            Some(Node::Boolean(_)) => self
1252                .get_bool(provider, io)
1253                .map(|flag| SkValue::Int(i64::from(flag))),
1254            Some(Node::Enum(_)) => self.get_enum_numeric(provider, io).map(SkValue::Int),
1255            Some(Node::SwissKnife(node)) => self.evaluate_swissknife(node, io, stack),
1256            Some(Node::Converter(node)) => self.evaluate_converter(node, io, stack),
1257            Some(Node::IntConverter(node)) => self
1258                .evaluate_int_converter(node, io, stack)
1259                .map(SkValue::Int),
1260            Some(_) => Err(GenApiError::Type(provider.to_string())),
1261            None => Err(GenApiError::NodeNotFound(provider.to_string())),
1262        }
1263    }
1264
1265    fn resolve_numeric(
1266        &self,
1267        provider: &str,
1268        io: &dyn RegisterIo,
1269        stack: &mut HashSet<String>,
1270    ) -> Result<f64, GenApiError> {
1271        self.resolve_value(provider, io, stack)
1272            .map(|value| value.as_f64())
1273    }
1274
1275    fn get_enum_numeric(&self, name: &str, io: &dyn RegisterIo) -> Result<i64, GenApiError> {
1276        let entry = self.get_enum(name, io)?;
1277        let node = self.get_enum_node(name)?;
1278        {
1279            let mut mapping = node.mapping_cache.borrow_mut();
1280            if mapping.is_none() {
1281                *mapping = Some(self.build_enum_mapping(node, io)?);
1282            }
1283            if let Some(map) = mapping.as_ref()
1284                && let Some(value) = map.by_name.get(&entry)
1285            {
1286                return Ok(*value);
1287            }
1288        }
1289        Err(GenApiError::EnumNoSuchEntry {
1290            node: name.to_string(),
1291            entry,
1292        })
1293    }
1294
1295    fn invalidate_dependents(&self, name: &str) {
1296        self.bump_generation();
1297        if let Some(children) = self.dependents.get(name) {
1298            let mut visited = HashSet::new();
1299            for child in children {
1300                self.invalidate_recursive(child, &mut visited);
1301            }
1302        }
1303    }
1304
1305    fn invalidate_recursive(&self, name: &str, visited: &mut HashSet<String>) {
1306        if !visited.insert(name.to_string()) {
1307            return;
1308        }
1309        if let Some(node) = self.nodes.get(name) {
1310            node.invalidate_cache();
1311        }
1312        if let Some(children) = self.dependents.get(name) {
1313            for child in children {
1314                self.invalidate_recursive(child, visited);
1315            }
1316        }
1317    }
1318
1319    fn bump_generation(&self) {
1320        let current = self.generation.get();
1321        self.generation.set(current.wrapping_add(1));
1322    }
1323
1324    // ========================================================================
1325    // Converter/IntConverter/String support
1326    // ========================================================================
1327
1328    fn get_converter_node(&self, name: &str) -> Result<&ConverterNode, GenApiError> {
1329        match self.nodes.get(name) {
1330            Some(Node::Converter(node)) => Ok(node),
1331            Some(_) => Err(GenApiError::Type(name.to_string())),
1332            None => Err(GenApiError::NodeNotFound(name.to_string())),
1333        }
1334    }
1335
1336    fn get_int_converter_node(&self, name: &str) -> Result<&IntConverterNode, GenApiError> {
1337        match self.nodes.get(name) {
1338            Some(Node::IntConverter(node)) => Ok(node),
1339            Some(_) => Err(GenApiError::Type(name.to_string())),
1340            None => Err(GenApiError::NodeNotFound(name.to_string())),
1341        }
1342    }
1343
1344    fn get_string_node(&self, name: &str) -> Result<&StringNode, GenApiError> {
1345        match self.nodes.get(name) {
1346            Some(Node::String(node)) => Ok(node),
1347            Some(_) => Err(GenApiError::Type(name.to_string())),
1348            None => Err(GenApiError::NodeNotFound(name.to_string())),
1349        }
1350    }
1351
1352    fn get_register_node(&self, name: &str) -> Result<&RegisterNode, GenApiError> {
1353        match self.nodes.get(name) {
1354            Some(Node::Register(node)) => Ok(node),
1355            Some(_) => Err(GenApiError::Type(name.to_string())),
1356            None => Err(GenApiError::NodeNotFound(name.to_string())),
1357        }
1358    }
1359
1360    /// Read a Converter feature value (float) using the provided transport.
1361    pub fn get_converter(&self, name: &str, io: &dyn RegisterIo) -> Result<f64, GenApiError> {
1362        let node = self.get_converter_node(name)?;
1363        if let Some((value, generation)) = *node.cache.borrow()
1364            && generation == self.generation.get()
1365        {
1366            return Ok(value.as_f64());
1367        }
1368        let mut stack = HashSet::new();
1369        let value = self.evaluate_converter(node, io, &mut stack)?;
1370        node.cache.replace(Some((value, self.generation.get())));
1371        Ok(value.as_f64())
1372    }
1373
1374    /// Read an IntConverter feature value (integer) using the provided transport.
1375    pub fn get_int_converter(&self, name: &str, io: &dyn RegisterIo) -> Result<i64, GenApiError> {
1376        let node = self.get_int_converter_node(name)?;
1377        if let Some((value, generation)) = *node.cache.borrow()
1378            && generation == self.generation.get()
1379        {
1380            return Ok(value);
1381        }
1382        let mut stack = HashSet::new();
1383        let value = self.evaluate_int_converter(node, io, &mut stack)?;
1384        node.cache.replace(Some((value, self.generation.get())));
1385        Ok(value)
1386    }
1387
1388    /// Write a Converter feature value (float) through its `<FormulaTo>`.
1389    pub fn set_converter(
1390        &mut self,
1391        name: &str,
1392        value: f64,
1393        io: &dyn RegisterIo,
1394    ) -> Result<(), GenApiError> {
1395        let node = self.get_converter_node(name)?;
1396        let (p_value, raw) = self.converter_raw_write(
1397            &node.name,
1398            &node.ast_to,
1399            &node.vars_to,
1400            &node.p_value,
1401            SkValue::Float(value),
1402            eval_mode(node.output),
1403            io,
1404        )?;
1405        self.write_converter_raw(&p_value, raw, io)?;
1406        self.invalidate_dependents(name);
1407        Ok(())
1408    }
1409
1410    /// Write an IntConverter feature value through its `<FormulaTo>`.
1411    pub fn set_int_converter(
1412        &mut self,
1413        name: &str,
1414        value: i64,
1415        io: &dyn RegisterIo,
1416    ) -> Result<(), GenApiError> {
1417        let node = self.get_int_converter_node(name)?;
1418        let (p_value, raw) = self.converter_raw_write(
1419            &node.name,
1420            &node.ast_to,
1421            &node.vars_to,
1422            &node.p_value,
1423            SkValue::Int(value),
1424            EvalMode::Integer,
1425            io,
1426        )?;
1427        self.write_converter_raw(&p_value, raw, io)?;
1428        self.invalidate_dependents(name);
1429        Ok(())
1430    }
1431
1432    /// Evaluate a converter's `<FormulaTo>` to the raw value to write.
1433    ///
1434    /// `FROM` is the value the caller is setting, and `OLD` — where the
1435    /// formula declares it — is the register's current contents, which
1436    /// read-modify-write formulas such as `(FROM & 0x7FFFFFFF) | (OLD &
1437    /// 0x80000000)` depend on.
1438    #[allow(clippy::too_many_arguments)]
1439    fn converter_raw_write(
1440        &self,
1441        name: &str,
1442        ast_to: &SkAst,
1443        vars_to: &[(String, String)],
1444        p_value: &str,
1445        value: SkValue,
1446        mode: EvalMode,
1447        io: &dyn RegisterIo,
1448    ) -> Result<(String, SkValue), GenApiError> {
1449        let mut stack = HashSet::new();
1450        let mut overrides = vec![("FROM", value)];
1451        if vars_to.iter().any(|(var, _)| var == "OLD") {
1452            let old = self.resolve_value(p_value, io, &mut stack)?;
1453            overrides.push(("OLD", old));
1454        }
1455        let raw = self.eval_formula(name, ast_to, vars_to, &overrides, mode, io, &mut stack)?;
1456        Ok((p_value.to_string(), raw))
1457    }
1458
1459    fn write_converter_raw(
1460        &mut self,
1461        p_value: &str,
1462        raw: SkValue,
1463        io: &dyn RegisterIo,
1464    ) -> Result<(), GenApiError> {
1465        match self.nodes.get(p_value) {
1466            Some(Node::Float(_)) => self.set_float(p_value, raw.as_f64(), io),
1467            Some(Node::Boolean(_)) => self.set_bool(p_value, raw.is_truthy(), io),
1468            Some(_) => self.set_integer(p_value, sk_to_i64(p_value, raw)?, io),
1469            None => Err(GenApiError::NodeNotFound(p_value.to_string())),
1470        }
1471    }
1472
1473    /// Read a String feature value using the provided transport.
1474    pub fn get_string(&self, name: &str, io: &dyn RegisterIo) -> Result<String, GenApiError> {
1475        let node = self.get_string_node(name)?;
1476        ensure_readable(&node.access, name)?;
1477        if let Some((ref value, generation)) = *node.cache.borrow()
1478            && generation == self.generation.get()
1479        {
1480            return Ok(value.clone());
1481        }
1482        let (address, len) = self.resolve_address(name, &node.addressing, io)?;
1483        let raw = io.read(address, len as usize)?;
1484        // Convert bytes to string, stopping at first null byte
1485        let end = raw.iter().position(|&b| b == 0).unwrap_or(raw.len());
1486        let value = String::from_utf8_lossy(&raw[..end]).to_string();
1487        node.cache
1488            .replace(Some((value.clone(), self.generation.get())));
1489        debug!(node = %name, value = %value, "get_string");
1490        Ok(value)
1491    }
1492
1493    /// Write a String feature value using the provided transport.
1494    pub fn set_string(
1495        &self,
1496        name: &str,
1497        value: &str,
1498        io: &dyn RegisterIo,
1499    ) -> Result<(), GenApiError> {
1500        let node = self.get_string_node(name)?;
1501        self.ensure_writable_now(name, &node.access, io)?;
1502        let (address, len) = self.resolve_address(name, &node.addressing, io)?;
1503        // Build byte buffer with null termination
1504        let mut buf = vec![0u8; len as usize];
1505        let bytes = value.as_bytes();
1506        let copy_len = bytes.len().min(len as usize);
1507        buf[..copy_len].copy_from_slice(&bytes[..copy_len]);
1508        io.write(address, &buf)?;
1509        node.cache
1510            .replace(Some((value.to_string(), self.generation.get())));
1511        self.invalidate_dependents(name);
1512        debug!(node = %name, value = %value, "set_string");
1513        Ok(())
1514    }
1515
1516    /// Read a `<Register>` node's bytes using the provided transport.
1517    ///
1518    /// Returns the full declared length. For a large block — the Micro-Epsilon
1519    /// scanCONTROL declares `FileAccessBuffer` as 100 000 bytes — that is
1520    /// hundreds of chunked reads; use [`NodeMap::register_address`] and the
1521    /// transport directly when a partial read is what you want.
1522    pub fn get_register(&self, name: &str, io: &dyn RegisterIo) -> Result<Vec<u8>, GenApiError> {
1523        let node = self.get_register_node(name)?;
1524        ensure_readable(&node.access, name)?;
1525        ensure_device_port(name, node.port.as_deref())?;
1526        if let Some((ref value, generation)) = *node.cache.borrow()
1527            && generation == self.generation.get()
1528        {
1529            return Ok(value.clone());
1530        }
1531        let (address, len) = self.resolve_address(name, &node.addressing, io)?;
1532        let raw = io.read(address, len as usize)?;
1533        node.cache
1534            .replace(Some((raw.clone(), self.generation.get())));
1535        debug!(node = %name, len = raw.len(), "get_register");
1536        Ok(raw)
1537    }
1538
1539    /// Write a `<Register>` node's bytes using the provided transport.
1540    ///
1541    /// `data` must be exactly the declared length. Unlike [`NodeMap::set_string`],
1542    /// which pads with NULs, a short slice is refused: zero-padding a
1543    /// file-transfer buffer to 100 000 bytes because the caller supplied 12 is
1544    /// data loss, not a convenience.
1545    pub fn set_register(
1546        &self,
1547        name: &str,
1548        data: &[u8],
1549        io: &dyn RegisterIo,
1550    ) -> Result<(), GenApiError> {
1551        let node = self.get_register_node(name)?;
1552        self.ensure_writable_now(name, &node.access, io)?;
1553        ensure_device_port(name, node.port.as_deref())?;
1554        let (address, len) = self.resolve_address(name, &node.addressing, io)?;
1555        if data.len() != len as usize {
1556            return Err(GenApiError::Range(format!(
1557                "register '{name}' is {len} bytes; got {}",
1558                data.len()
1559            )));
1560        }
1561        io.write(address, data)?;
1562        node.cache
1563            .replace(Some((data.to_vec(), self.generation.get())));
1564        self.invalidate_dependents(name);
1565        debug!(node = %name, len = data.len(), "set_register");
1566        Ok(())
1567    }
1568
1569    /// Evaluate a Converter in the read direction (`<FormulaFrom>`).
1570    fn evaluate_converter(
1571        &self,
1572        node: &ConverterNode,
1573        io: &dyn RegisterIo,
1574        stack: &mut HashSet<String>,
1575    ) -> Result<SkValue, GenApiError> {
1576        if !stack.insert(node.name.clone()) {
1577            stack.remove(&node.name);
1578            return Err(GenApiError::ExprEval {
1579                name: node.name.clone(),
1580                msg: "cyclic dependency".into(),
1581            });
1582        }
1583        let result = self.eval_formula(
1584            &node.name,
1585            &node.ast_from,
1586            &node.vars_from,
1587            &[],
1588            eval_mode(node.output),
1589            io,
1590            stack,
1591        );
1592        stack.remove(&node.name);
1593        let value = result?;
1594        debug!(node = %node.name, value = %value, "evaluate Converter");
1595        Ok(value)
1596    }
1597
1598    /// Evaluate an IntConverter in the read direction (`<FormulaFrom>`).
1599    fn evaluate_int_converter(
1600        &self,
1601        node: &IntConverterNode,
1602        io: &dyn RegisterIo,
1603        stack: &mut HashSet<String>,
1604    ) -> Result<i64, GenApiError> {
1605        if !stack.insert(node.name.clone()) {
1606            stack.remove(&node.name);
1607            return Err(GenApiError::ExprEval {
1608                name: node.name.clone(),
1609                msg: "cyclic dependency".into(),
1610            });
1611        }
1612        let result = self.eval_formula(
1613            &node.name,
1614            &node.ast_from,
1615            &node.vars_from,
1616            &[],
1617            EvalMode::Integer,
1618            io,
1619            stack,
1620        );
1621        stack.remove(&node.name);
1622        let int_value = result?.as_i64();
1623        debug!(node = %node.name, int_value, "evaluate IntConverter");
1624        Ok(int_value)
1625    }
1626}
1627
1628/// Build one runtime node from its declaration, recording the nodes it depends
1629/// on in `dependents`.
1630fn build_node(
1631    decl: NodeDecl,
1632    dependents: &mut HashMap<String, Vec<String>>,
1633) -> Result<(String, Node), GenApiError> {
1634    match decl {
1635        NodeDecl::Integer {
1636            name,
1637            meta,
1638            addressing,
1639            len,
1640            access,
1641            min,
1642            max,
1643            inc,
1644            unit,
1645            bitfield,
1646            sign,
1647            selectors,
1648            selected_if,
1649            pvalue,
1650            p_max,
1651            p_min,
1652            value,
1653            predicates,
1654        } => {
1655            if let Some(ref addr) = addressing {
1656                register_addressing_dependency(dependents, &name, addr);
1657            }
1658            if let Some(ref pv) = pvalue {
1659                dependents.entry(pv.clone()).or_default().push(name.clone());
1660            }
1661            if let Some(ref pm) = p_max {
1662                dependents.entry(pm.clone()).or_default().push(name.clone());
1663            }
1664            if let Some(ref pm) = p_min {
1665                dependents.entry(pm.clone()).or_default().push(name.clone());
1666            }
1667            for (selector, _) in &selected_if {
1668                dependents
1669                    .entry(selector.clone())
1670                    .or_default()
1671                    .push(name.clone());
1672            }
1673            register_predicate_dependencies(dependents, &name, &predicates);
1674            let node = IntegerNode {
1675                name: name.clone(),
1676                meta,
1677                addressing,
1678                len,
1679                access,
1680                min,
1681                max,
1682                inc,
1683                unit,
1684                bitfield,
1685                sign,
1686                selectors,
1687                selected_if,
1688                pvalue,
1689                p_max,
1690                p_min,
1691                value,
1692                predicates,
1693                cache: std::cell::RefCell::new(None),
1694                raw_cache: std::cell::RefCell::new(None),
1695            };
1696            Ok((name, Node::Integer(node)))
1697        }
1698        NodeDecl::Float {
1699            name,
1700            meta,
1701            addressing,
1702            access,
1703            min,
1704            max,
1705            unit,
1706            scale,
1707            offset,
1708            selectors,
1709            selected_if,
1710            pvalue,
1711            encoding,
1712            byte_order,
1713            predicates,
1714        } => {
1715            if let Some(ref addr) = addressing {
1716                register_addressing_dependency(dependents, &name, addr);
1717            }
1718            if let Some(ref pv) = pvalue {
1719                dependents.entry(pv.clone()).or_default().push(name.clone());
1720            }
1721            for (selector, _) in &selected_if {
1722                dependents
1723                    .entry(selector.clone())
1724                    .or_default()
1725                    .push(name.clone());
1726            }
1727            register_predicate_dependencies(dependents, &name, &predicates);
1728            let node = FloatNode {
1729                name: name.clone(),
1730                meta,
1731                addressing,
1732                access,
1733                min,
1734                max,
1735                unit,
1736                scale,
1737                offset,
1738                selectors,
1739                selected_if,
1740                pvalue,
1741                encoding,
1742                byte_order,
1743                predicates,
1744                cache: std::cell::RefCell::new(None),
1745            };
1746            Ok((name, Node::Float(node)))
1747        }
1748        NodeDecl::Enum {
1749            name,
1750            meta,
1751            addressing,
1752            access,
1753            entries,
1754            default,
1755            selectors,
1756            selected_if,
1757            pvalue,
1758            predicates,
1759        } => {
1760            if let Some(ref addr) = addressing {
1761                register_addressing_dependency(dependents, &name, addr);
1762            }
1763            if let Some(ref pv) = pvalue {
1764                dependents.entry(pv.clone()).or_default().push(name.clone());
1765            }
1766            for (selector, _) in &selected_if {
1767                dependents
1768                    .entry(selector.clone())
1769                    .or_default()
1770                    .push(name.clone());
1771            }
1772            register_predicate_dependencies(dependents, &name, &predicates);
1773            let mut providers = Vec::new();
1774            let mut provider_set = HashSet::new();
1775            for entry in &entries {
1776                if let EnumValueSrc::FromNode(node_name) = &entry.value {
1777                    dependents
1778                        .entry(node_name.clone())
1779                        .or_default()
1780                        .push(name.clone());
1781                    if provider_set.insert(node_name.clone()) {
1782                        providers.push(node_name.clone());
1783                    }
1784                }
1785                register_predicate_dependencies(dependents, &name, &entry.predicates);
1786            }
1787            providers.sort();
1788            let node = EnumNode {
1789                name: name.clone(),
1790                meta,
1791                addressing,
1792                access,
1793                pvalue,
1794                entries,
1795                default,
1796                selectors,
1797                selected_if,
1798                providers,
1799                predicates,
1800                value_cache: std::cell::RefCell::new(None),
1801                mapping_cache: std::cell::RefCell::new(None),
1802            };
1803            Ok((name, Node::Enum(node)))
1804        }
1805        NodeDecl::Boolean {
1806            name,
1807            meta,
1808            addressing,
1809            len,
1810            access,
1811            bitfield,
1812            selectors,
1813            selected_if,
1814            pvalue,
1815            on_value,
1816            off_value,
1817            predicates,
1818        } => {
1819            if let Some(ref addr) = addressing {
1820                register_addressing_dependency(dependents, &name, addr);
1821            }
1822            if let Some(ref pv) = pvalue {
1823                dependents.entry(pv.clone()).or_default().push(name.clone());
1824            }
1825            for (selector, _) in &selected_if {
1826                dependents
1827                    .entry(selector.clone())
1828                    .or_default()
1829                    .push(name.clone());
1830            }
1831            register_predicate_dependencies(dependents, &name, &predicates);
1832            let node = BooleanNode {
1833                name: name.clone(),
1834                meta,
1835                addressing,
1836                len,
1837                access,
1838                bitfield,
1839                selectors,
1840                selected_if,
1841                pvalue,
1842                on_value,
1843                off_value,
1844                predicates,
1845                cache: std::cell::RefCell::new(None),
1846                raw_cache: std::cell::RefCell::new(None),
1847            };
1848            Ok((name, Node::Boolean(node)))
1849        }
1850        NodeDecl::Command {
1851            name,
1852            meta,
1853            address,
1854            len,
1855            pvalue,
1856            command_value,
1857            predicates,
1858        } => {
1859            if let Some(ref pv) = pvalue {
1860                dependents.entry(pv.clone()).or_default().push(name.clone());
1861            }
1862            register_predicate_dependencies(dependents, &name, &predicates);
1863            let node = CommandNode {
1864                name: name.clone(),
1865                meta,
1866                address,
1867                len,
1868                pvalue,
1869                command_value,
1870                predicates,
1871            };
1872            Ok((name, Node::Command(node)))
1873        }
1874        NodeDecl::Category {
1875            name,
1876            meta,
1877            children,
1878            predicates,
1879        } => {
1880            register_predicate_dependencies(dependents, &name, &predicates);
1881            let node = CategoryNode {
1882                name: name.clone(),
1883                meta,
1884                children,
1885                predicates,
1886            };
1887            Ok((name, Node::Category(node)))
1888        }
1889        NodeDecl::SwissKnife(decl) => {
1890            let name = decl.name;
1891            let meta = decl.meta;
1892            let expr = decl.expr;
1893            let variables = decl.variables;
1894            let output = decl.output;
1895            let predicates = decl.predicates;
1896            let mut ast = parse_expression(&expr).map_err(|err| GenApiError::ExprParse {
1897                name: name.clone(),
1898                msg: err.to_string(),
1899            })?;
1900            substitute(&mut ast, &formula_bindings(&name, &decl.bindings)?);
1901            let mut used = HashSet::new();
1902            collect_identifiers(&ast, &mut used);
1903            for ident in &used {
1904                // `E` and `PI` are language constants, not variables, so
1905                // they legitimately appear without a `<pVariable>`.
1906                if !variables.iter().any(|(var, _)| var == ident) && !is_builtin_constant(ident) {
1907                    return Err(GenApiError::UnknownVariable {
1908                        name: name.clone(),
1909                        var: ident.clone(),
1910                    });
1911                }
1912            }
1913            for (_, provider) in &variables {
1914                dependents
1915                    .entry(provider.clone())
1916                    .or_default()
1917                    .push(name.clone());
1918            }
1919            register_predicate_dependencies(dependents, &name, &predicates);
1920            let node = SkNode {
1921                name: name.clone(),
1922                meta,
1923                output,
1924                ast,
1925                vars: variables,
1926                predicates,
1927                cache: std::cell::RefCell::new(None),
1928            };
1929            Ok((name, Node::SwissKnife(node)))
1930        }
1931        NodeDecl::Converter(decl) => {
1932            let name = decl.name;
1933            let bindings = formula_bindings(&name, &decl.bindings)?;
1934            let mut ast_to =
1935                parse_expression(&decl.formula_to).map_err(|err| GenApiError::ExprParse {
1936                    name: name.clone(),
1937                    msg: format!("FormulaTo: {err}"),
1938                })?;
1939            substitute(&mut ast_to, &bindings);
1940            let mut ast_from =
1941                parse_expression(&decl.formula_from).map_err(|err| GenApiError::ExprParse {
1942                    name: name.clone(),
1943                    msg: format!("FormulaFrom: {err}"),
1944                })?;
1945            substitute(&mut ast_from, &bindings);
1946            // Register dependencies for all variable providers
1947            for (_, provider) in &decl.variables_to {
1948                dependents
1949                    .entry(provider.clone())
1950                    .or_default()
1951                    .push(name.clone());
1952            }
1953            for (_, provider) in &decl.variables_from {
1954                if !decl.variables_to.iter().any(|(_, p)| p == provider) {
1955                    dependents
1956                        .entry(provider.clone())
1957                        .or_default()
1958                        .push(name.clone());
1959                }
1960            }
1961            // Also depend on p_value
1962            dependents
1963                .entry(decl.p_value.clone())
1964                .or_default()
1965                .push(name.clone());
1966            register_predicate_dependencies(dependents, &name, &decl.predicates);
1967            let node = ConverterNode {
1968                name: name.clone(),
1969                meta: decl.meta,
1970                p_value: decl.p_value,
1971                ast_to,
1972                ast_from,
1973                vars_to: decl.variables_to,
1974                vars_from: decl.variables_from,
1975                unit: decl.unit,
1976                output: decl.output,
1977                predicates: decl.predicates,
1978                cache: std::cell::RefCell::new(None),
1979            };
1980            Ok((name, Node::Converter(node)))
1981        }
1982        NodeDecl::IntConverter(decl) => {
1983            let name = decl.name;
1984            let bindings = formula_bindings(&name, &decl.bindings)?;
1985            let mut ast_to =
1986                parse_expression(&decl.formula_to).map_err(|err| GenApiError::ExprParse {
1987                    name: name.clone(),
1988                    msg: format!("FormulaTo: {err}"),
1989                })?;
1990            substitute(&mut ast_to, &bindings);
1991            let mut ast_from =
1992                parse_expression(&decl.formula_from).map_err(|err| GenApiError::ExprParse {
1993                    name: name.clone(),
1994                    msg: format!("FormulaFrom: {err}"),
1995                })?;
1996            substitute(&mut ast_from, &bindings);
1997            for (_, provider) in &decl.variables_to {
1998                dependents
1999                    .entry(provider.clone())
2000                    .or_default()
2001                    .push(name.clone());
2002            }
2003            for (_, provider) in &decl.variables_from {
2004                if !decl.variables_to.iter().any(|(_, p)| p == provider) {
2005                    dependents
2006                        .entry(provider.clone())
2007                        .or_default()
2008                        .push(name.clone());
2009                }
2010            }
2011            dependents
2012                .entry(decl.p_value.clone())
2013                .or_default()
2014                .push(name.clone());
2015            register_predicate_dependencies(dependents, &name, &decl.predicates);
2016            let node = IntConverterNode {
2017                name: name.clone(),
2018                meta: decl.meta,
2019                p_value: decl.p_value,
2020                ast_to,
2021                ast_from,
2022                vars_to: decl.variables_to,
2023                vars_from: decl.variables_from,
2024                unit: decl.unit,
2025                predicates: decl.predicates,
2026                cache: std::cell::RefCell::new(None),
2027            };
2028            Ok((name, Node::IntConverter(node)))
2029        }
2030        NodeDecl::String(decl) => {
2031            let name = decl.name;
2032            register_addressing_dependency(dependents, &name, &decl.addressing);
2033            register_predicate_dependencies(dependents, &name, &decl.predicates);
2034            let node = StringNode {
2035                name: name.clone(),
2036                meta: decl.meta,
2037                addressing: decl.addressing,
2038                access: decl.access,
2039                predicates: decl.predicates,
2040                cache: std::cell::RefCell::new(None),
2041            };
2042            Ok((name, Node::String(node)))
2043        }
2044        NodeDecl::Register(decl) => {
2045            let name = decl.name;
2046            register_addressing_dependency(dependents, &name, &decl.addressing);
2047            register_predicate_dependencies(dependents, &name, &decl.predicates);
2048            let node = RegisterNode {
2049                name: name.clone(),
2050                meta: decl.meta,
2051                addressing: decl.addressing,
2052                access: decl.access,
2053                port: decl.port,
2054                predicates: decl.predicates,
2055                cache: std::cell::RefCell::new(None),
2056            };
2057            Ok((name, Node::Register(node)))
2058        }
2059        // `NodeDecl` is `#[non_exhaustive]`, so this crate can no longer match
2060        // it exhaustively and the compiler will not point at this function when
2061        // a variant is added. Fail loudly rather than defaulting: a node type
2062        // the XML layer understands but this one silently drops is precisely
2063        // the class of defect GA-02 existed to end.
2064        other => Err(GenApiError::Unsupported(format!(
2065            "node '{}' has declaration kind '{}', which this nodemap cannot build; \
2066             viva-genapi-xml understands it but viva-genapi has no arm for it",
2067            other.name(),
2068            other.kind()
2069        ))),
2070    }
2071}
2072
2073/// Effective signedness of an integer node's payload.
2074///
2075/// `<Sign>` is the only signal. `<Min>` says nothing about it: it constrains
2076/// the *feature* value, while `<Sign>` describes the *register payload*
2077/// encoding, and the two live on different nodes when a feature delegates
2078/// through `<pValue>`.
2079///
2080/// Inferring "signed" from a negative `<Min>` looks reasonable and is
2081/// unsalvageable in practice. `<Min>` is optional and defaults to `i64::MIN`,
2082/// which is negative — and across the whole vendor corpus **not one** of the
2083/// 4 300 `<IntReg>` or 5 479 `<MaskedIntReg>` nodes declares it. The
2084/// inference therefore fires on every register-backed integer on every real
2085/// camera, which is precisely the case `<Sign>` exists to decide.
2086fn integer_sign(node: &IntegerNode) -> Sign {
2087    node.sign
2088}
2089
2090/// Resolve a formula's `<Constant>` and named `<Expression>` declarations into
2091/// sub-ASTs ready for substitution.
2092///
2093/// Declaration order is significant: an `<Expression>` may reference constants
2094/// and expressions declared before it, so each is substituted against what has
2095/// been bound so far. That also makes a self- or forward-reference resolve to
2096/// nothing rather than looping.
2097fn formula_bindings(
2098    node: &str,
2099    declared: &FormulaBindings,
2100) -> Result<HashMap<String, SkAst>, GenApiError> {
2101    let mut bindings: HashMap<String, SkAst> = HashMap::new();
2102    for (name, literal) in &declared.constants {
2103        let value = parse_expression(literal).map_err(|err| GenApiError::ExprParse {
2104            name: node.to_string(),
2105            msg: format!("Constant {name}: {err}"),
2106        })?;
2107        bindings.insert(name.clone(), value);
2108    }
2109    for (name, formula) in &declared.expressions {
2110        let mut ast = parse_expression(formula).map_err(|err| GenApiError::ExprParse {
2111            name: node.to_string(),
2112            msg: format!("Expression {name}: {err}"),
2113        })?;
2114        substitute(&mut ast, &bindings);
2115        bindings.insert(name.clone(), ast);
2116    }
2117    Ok(bindings)
2118}
2119
2120/// Narrow a formula result to `i64` for an integer-typed feature.
2121///
2122/// An integer formula that stayed integral is exact; one that picked up a
2123/// float along the way (a fractional literal, a transcendental function)
2124/// rounds, as the GenApi integer output rule requires.
2125fn sk_to_i64(name: &str, value: SkValue) -> Result<i64, GenApiError> {
2126    match value {
2127        SkValue::Int(value) => Ok(value),
2128        SkValue::Float(value) => round_to_i64(name, value),
2129    }
2130}
2131
2132/// Arithmetic mode implied by a formula's declared output type.
2133fn eval_mode(output: SkOutput) -> EvalMode {
2134    match output {
2135        SkOutput::Integer => EvalMode::Integer,
2136        SkOutput::Float => EvalMode::Float,
2137    }
2138}
2139
2140/// Map a formula evaluation failure onto the public error type.
2141fn expr_error(name: &str, err: SkEvalError) -> GenApiError {
2142    match err {
2143        SkEvalError::UnknownVariable(var) => GenApiError::UnknownVariable {
2144            name: name.to_string(),
2145            var,
2146        },
2147        SkEvalError::DivisionByZero => GenApiError::ExprEval {
2148            name: name.to_string(),
2149            msg: "division by zero".into(),
2150        },
2151        SkEvalError::UnknownFunction(func) => GenApiError::ExprEval {
2152            name: name.to_string(),
2153            msg: format!("unknown function: {func}"),
2154        },
2155        SkEvalError::ArityMismatch {
2156            name: func,
2157            expected,
2158            got,
2159        } => GenApiError::ExprEval {
2160            name: name.to_string(),
2161            msg: format!("function {func} expects {expected} args, got {got}"),
2162        },
2163    }
2164}
2165
2166impl TryFrom<XmlModel> for NodeMap {
2167    type Error = GenApiError;
2168
2169    fn try_from(model: XmlModel) -> Result<Self, Self::Error> {
2170        NodeMap::try_from_xml(model)
2171    }
2172}