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, Addressing, EnumEntryDecl, EnumValueSrc, FloatEncoding, NodeDecl, PredicateRefs,
9    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, SkNode, StringNode,
20};
21use crate::swissknife::{
22    EvalError as SkEvalError, collect_identifiers, evaluate as eval_ast, parse_expression,
23};
24use crate::{GenApiError, RegisterIo, SkOutput};
25
26/// Runtime nodemap built from an [`XmlModel`] capable of reading and writing
27/// feature values via a [`RegisterIo`] transport.
28#[derive(Debug)]
29pub struct NodeMap {
30    version: String,
31    nodes: HashMap<String, Node>,
32    dependents: HashMap<String, Vec<String>>,
33    generation: Cell<u64>,
34}
35
36fn register_addressing_dependency(
37    dependents: &mut HashMap<String, Vec<String>>,
38    node_name: &str,
39    addressing: &Addressing,
40) {
41    match addressing {
42        Addressing::Fixed { .. } => {}
43        Addressing::BySelector { selector, .. } => {
44            dependents
45                .entry(selector.clone())
46                .or_default()
47                .push(node_name.to_string());
48        }
49        Addressing::Indirect { p_address_node, .. } => {
50            dependents
51                .entry(p_address_node.clone())
52                .or_default()
53                .push(node_name.to_string());
54        }
55    }
56}
57
58fn register_predicate_dependencies(
59    dependents: &mut HashMap<String, Vec<String>>,
60    node_name: &str,
61    predicates: &PredicateRefs,
62) {
63    for provider in predicates.references() {
64        dependents
65            .entry(provider.to_string())
66            .or_default()
67            .push(node_name.to_string());
68    }
69}
70
71fn ensure_readable(access: &AccessMode, name: &str) -> Result<(), GenApiError> {
72    if matches!(access, AccessMode::WO) {
73        return Err(GenApiError::Access(name.to_string()));
74    }
75    Ok(())
76}
77
78fn ensure_writable(access: &AccessMode, name: &str) -> Result<(), GenApiError> {
79    if matches!(access, AccessMode::RO) {
80        return Err(GenApiError::Access(name.to_string()));
81    }
82    Ok(())
83}
84
85impl NodeMap {
86    /// Return the schema version string associated with the XML description.
87    pub fn version(&self) -> &str {
88        &self.version
89    }
90
91    /// Fetch a node by name for inspection.
92    pub fn node(&self, name: &str) -> Option<&Node> {
93        self.nodes.get(name)
94    }
95
96    /// Return an iterator over all node names in the map.
97    pub fn node_names(&self) -> impl Iterator<Item = &str> {
98        self.nodes.keys().map(|s| s.as_str())
99    }
100
101    /// Return the list of nodes that should be invalidated when `name` changes.
102    ///
103    /// Returns an empty slice if the node has no dependents.
104    pub fn dependents(&self, name: &str) -> &[String] {
105        self.dependents
106            .get(name)
107            .map(|v| v.as_slice())
108            .unwrap_or(&[])
109    }
110
111    /// Return all category nodes as `(name, children)` pairs.
112    pub fn categories(&self) -> Vec<(&str, &[String])> {
113        self.nodes
114            .values()
115            .filter_map(|node| match node {
116                Node::Category(cat) => Some((cat.name.as_str(), cat.children.as_slice())),
117                _ => None,
118            })
119            .collect()
120    }
121
122    /// Return names of nodes visible at the given level or below.
123    ///
124    /// A node with `Visibility::Expert` is visible at level `Expert` and `Guru`,
125    /// but not at `Beginner`.
126    pub fn nodes_at_visibility(&self, level: Visibility) -> Vec<&str> {
127        self.nodes
128            .iter()
129            .filter(|(_, node)| node.visibility() <= level)
130            .map(|(name, _)| name.as_str())
131            .collect()
132    }
133
134    /// Construct a [`NodeMap`] from an [`XmlModel`], validating SwissKnife expressions.
135    pub fn try_from_xml(model: XmlModel) -> Result<Self, GenApiError> {
136        let mut nodes = HashMap::new();
137        let mut dependents: HashMap<String, Vec<String>> = HashMap::new();
138        for decl in model.nodes {
139            match decl {
140                NodeDecl::Integer {
141                    name,
142                    meta,
143                    addressing,
144                    len,
145                    access,
146                    min,
147                    max,
148                    inc,
149                    unit,
150                    bitfield,
151                    selectors,
152                    selected_if,
153                    pvalue,
154                    p_max,
155                    p_min,
156                    value,
157                    predicates,
158                } => {
159                    if let Some(ref addr) = addressing {
160                        register_addressing_dependency(&mut dependents, &name, addr);
161                    }
162                    if let Some(ref pv) = pvalue {
163                        dependents.entry(pv.clone()).or_default().push(name.clone());
164                    }
165                    if let Some(ref pm) = p_max {
166                        dependents.entry(pm.clone()).or_default().push(name.clone());
167                    }
168                    if let Some(ref pm) = p_min {
169                        dependents.entry(pm.clone()).or_default().push(name.clone());
170                    }
171                    for (selector, _) in &selected_if {
172                        dependents
173                            .entry(selector.clone())
174                            .or_default()
175                            .push(name.clone());
176                    }
177                    register_predicate_dependencies(&mut dependents, &name, &predicates);
178                    let node = IntegerNode {
179                        name: name.clone(),
180                        meta,
181                        addressing,
182                        len,
183                        access,
184                        min,
185                        max,
186                        inc,
187                        unit,
188                        bitfield,
189                        selectors,
190                        selected_if,
191                        pvalue,
192                        p_max,
193                        p_min,
194                        value,
195                        predicates,
196                        cache: std::cell::RefCell::new(None),
197                        raw_cache: std::cell::RefCell::new(None),
198                    };
199                    nodes.insert(name, Node::Integer(node));
200                }
201                NodeDecl::Float {
202                    name,
203                    meta,
204                    addressing,
205                    access,
206                    min,
207                    max,
208                    unit,
209                    scale,
210                    offset,
211                    selectors,
212                    selected_if,
213                    pvalue,
214                    encoding,
215                    byte_order,
216                    predicates,
217                } => {
218                    if let Some(ref addr) = addressing {
219                        register_addressing_dependency(&mut dependents, &name, addr);
220                    }
221                    if let Some(ref pv) = pvalue {
222                        dependents.entry(pv.clone()).or_default().push(name.clone());
223                    }
224                    for (selector, _) in &selected_if {
225                        dependents
226                            .entry(selector.clone())
227                            .or_default()
228                            .push(name.clone());
229                    }
230                    register_predicate_dependencies(&mut dependents, &name, &predicates);
231                    let node = FloatNode {
232                        name: name.clone(),
233                        meta,
234                        addressing,
235                        access,
236                        min,
237                        max,
238                        unit,
239                        scale,
240                        offset,
241                        selectors,
242                        selected_if,
243                        pvalue,
244                        encoding,
245                        byte_order,
246                        predicates,
247                        cache: std::cell::RefCell::new(None),
248                    };
249                    nodes.insert(name, Node::Float(node));
250                }
251                NodeDecl::Enum {
252                    name,
253                    meta,
254                    addressing,
255                    access,
256                    entries,
257                    default,
258                    selectors,
259                    selected_if,
260                    pvalue,
261                    predicates,
262                } => {
263                    if let Some(ref addr) = addressing {
264                        register_addressing_dependency(&mut dependents, &name, addr);
265                    }
266                    if let Some(ref pv) = pvalue {
267                        dependents.entry(pv.clone()).or_default().push(name.clone());
268                    }
269                    for (selector, _) in &selected_if {
270                        dependents
271                            .entry(selector.clone())
272                            .or_default()
273                            .push(name.clone());
274                    }
275                    register_predicate_dependencies(&mut dependents, &name, &predicates);
276                    let mut providers = Vec::new();
277                    let mut provider_set = HashSet::new();
278                    for entry in &entries {
279                        if let EnumValueSrc::FromNode(node_name) = &entry.value {
280                            dependents
281                                .entry(node_name.clone())
282                                .or_default()
283                                .push(name.clone());
284                            if provider_set.insert(node_name.clone()) {
285                                providers.push(node_name.clone());
286                            }
287                        }
288                        register_predicate_dependencies(&mut dependents, &name, &entry.predicates);
289                    }
290                    providers.sort();
291                    let node = EnumNode {
292                        name: name.clone(),
293                        meta,
294                        addressing,
295                        access,
296                        pvalue,
297                        entries,
298                        default,
299                        selectors,
300                        selected_if,
301                        providers,
302                        predicates,
303                        value_cache: std::cell::RefCell::new(None),
304                        mapping_cache: std::cell::RefCell::new(None),
305                    };
306                    nodes.insert(name, Node::Enum(node));
307                }
308                NodeDecl::Boolean {
309                    name,
310                    meta,
311                    addressing,
312                    len,
313                    access,
314                    bitfield,
315                    selectors,
316                    selected_if,
317                    pvalue,
318                    on_value,
319                    off_value,
320                    predicates,
321                } => {
322                    if let Some(ref addr) = addressing {
323                        register_addressing_dependency(&mut dependents, &name, addr);
324                    }
325                    if let Some(ref pv) = pvalue {
326                        dependents.entry(pv.clone()).or_default().push(name.clone());
327                    }
328                    for (selector, _) in &selected_if {
329                        dependents
330                            .entry(selector.clone())
331                            .or_default()
332                            .push(name.clone());
333                    }
334                    register_predicate_dependencies(&mut dependents, &name, &predicates);
335                    let node = BooleanNode {
336                        name: name.clone(),
337                        meta,
338                        addressing,
339                        len,
340                        access,
341                        bitfield,
342                        selectors,
343                        selected_if,
344                        pvalue,
345                        on_value,
346                        off_value,
347                        predicates,
348                        cache: std::cell::RefCell::new(None),
349                        raw_cache: std::cell::RefCell::new(None),
350                    };
351                    nodes.insert(name, Node::Boolean(node));
352                }
353                NodeDecl::Command {
354                    name,
355                    meta,
356                    address,
357                    len,
358                    pvalue,
359                    command_value,
360                    predicates,
361                } => {
362                    if let Some(ref pv) = pvalue {
363                        dependents.entry(pv.clone()).or_default().push(name.clone());
364                    }
365                    register_predicate_dependencies(&mut dependents, &name, &predicates);
366                    let node = CommandNode {
367                        name: name.clone(),
368                        meta,
369                        address,
370                        len,
371                        pvalue,
372                        command_value,
373                        predicates,
374                    };
375                    nodes.insert(name, Node::Command(node));
376                }
377                NodeDecl::Category {
378                    name,
379                    meta,
380                    children,
381                    predicates,
382                } => {
383                    register_predicate_dependencies(&mut dependents, &name, &predicates);
384                    let node = CategoryNode {
385                        name: name.clone(),
386                        meta,
387                        children,
388                        predicates,
389                    };
390                    nodes.insert(name, Node::Category(node));
391                }
392                NodeDecl::SwissKnife(decl) => {
393                    let name = decl.name;
394                    let meta = decl.meta;
395                    let expr = decl.expr;
396                    let variables = decl.variables;
397                    let output = decl.output;
398                    let predicates = decl.predicates;
399                    let ast = parse_expression(&expr).map_err(|err| GenApiError::ExprParse {
400                        name: name.clone(),
401                        msg: err.to_string(),
402                    })?;
403                    let mut used = HashSet::new();
404                    collect_identifiers(&ast, &mut used);
405                    for ident in &used {
406                        if !variables.iter().any(|(var, _)| var == ident) {
407                            return Err(GenApiError::UnknownVariable {
408                                name: name.clone(),
409                                var: ident.clone(),
410                            });
411                        }
412                    }
413                    for (_, provider) in &variables {
414                        dependents
415                            .entry(provider.clone())
416                            .or_default()
417                            .push(name.clone());
418                    }
419                    register_predicate_dependencies(&mut dependents, &name, &predicates);
420                    let node = SkNode {
421                        name: name.clone(),
422                        meta,
423                        output,
424                        ast,
425                        vars: variables,
426                        predicates,
427                        cache: std::cell::RefCell::new(None),
428                    };
429                    nodes.insert(name, Node::SwissKnife(node));
430                }
431                NodeDecl::Converter(decl) => {
432                    let name = decl.name;
433                    let ast_to = parse_expression(&decl.formula_to).map_err(|err| {
434                        GenApiError::ExprParse {
435                            name: name.clone(),
436                            msg: format!("FormulaTo: {err}"),
437                        }
438                    })?;
439                    let ast_from = parse_expression(&decl.formula_from).map_err(|err| {
440                        GenApiError::ExprParse {
441                            name: name.clone(),
442                            msg: format!("FormulaFrom: {err}"),
443                        }
444                    })?;
445                    // Register dependencies for all variable providers
446                    for (_, provider) in &decl.variables_to {
447                        dependents
448                            .entry(provider.clone())
449                            .or_default()
450                            .push(name.clone());
451                    }
452                    for (_, provider) in &decl.variables_from {
453                        if !decl.variables_to.iter().any(|(_, p)| p == provider) {
454                            dependents
455                                .entry(provider.clone())
456                                .or_default()
457                                .push(name.clone());
458                        }
459                    }
460                    // Also depend on p_value
461                    dependents
462                        .entry(decl.p_value.clone())
463                        .or_default()
464                        .push(name.clone());
465                    register_predicate_dependencies(&mut dependents, &name, &decl.predicates);
466                    let node = ConverterNode {
467                        name: name.clone(),
468                        meta: decl.meta,
469                        p_value: decl.p_value,
470                        ast_to,
471                        ast_from,
472                        vars_to: decl.variables_to,
473                        vars_from: decl.variables_from,
474                        unit: decl.unit,
475                        output: decl.output,
476                        predicates: decl.predicates,
477                        cache: std::cell::RefCell::new(None),
478                    };
479                    nodes.insert(name, Node::Converter(node));
480                }
481                NodeDecl::IntConverter(decl) => {
482                    let name = decl.name;
483                    let ast_to = parse_expression(&decl.formula_to).map_err(|err| {
484                        GenApiError::ExprParse {
485                            name: name.clone(),
486                            msg: format!("FormulaTo: {err}"),
487                        }
488                    })?;
489                    let ast_from = parse_expression(&decl.formula_from).map_err(|err| {
490                        GenApiError::ExprParse {
491                            name: name.clone(),
492                            msg: format!("FormulaFrom: {err}"),
493                        }
494                    })?;
495                    for (_, provider) in &decl.variables_to {
496                        dependents
497                            .entry(provider.clone())
498                            .or_default()
499                            .push(name.clone());
500                    }
501                    for (_, provider) in &decl.variables_from {
502                        if !decl.variables_to.iter().any(|(_, p)| p == provider) {
503                            dependents
504                                .entry(provider.clone())
505                                .or_default()
506                                .push(name.clone());
507                        }
508                    }
509                    dependents
510                        .entry(decl.p_value.clone())
511                        .or_default()
512                        .push(name.clone());
513                    register_predicate_dependencies(&mut dependents, &name, &decl.predicates);
514                    let node = IntConverterNode {
515                        name: name.clone(),
516                        meta: decl.meta,
517                        p_value: decl.p_value,
518                        ast_to,
519                        ast_from,
520                        vars_to: decl.variables_to,
521                        vars_from: decl.variables_from,
522                        unit: decl.unit,
523                        predicates: decl.predicates,
524                        cache: std::cell::RefCell::new(None),
525                    };
526                    nodes.insert(name, Node::IntConverter(node));
527                }
528                NodeDecl::String(decl) => {
529                    let name = decl.name;
530                    register_addressing_dependency(&mut dependents, &name, &decl.addressing);
531                    register_predicate_dependencies(&mut dependents, &name, &decl.predicates);
532                    let node = StringNode {
533                        name: name.clone(),
534                        meta: decl.meta,
535                        addressing: decl.addressing,
536                        access: decl.access,
537                        predicates: decl.predicates,
538                        cache: std::cell::RefCell::new(None),
539                    };
540                    nodes.insert(name, Node::String(node));
541                }
542            }
543        }
544
545        Ok(NodeMap {
546            version: model.version,
547            nodes,
548            dependents,
549            generation: Cell::new(0),
550        })
551    }
552
553    /// Read an integer feature value using the provided transport.
554    pub fn get_integer(&self, name: &str, io: &dyn RegisterIo) -> Result<i64, GenApiError> {
555        if let Some(output) = self.nodes.get(name).and_then(|node| match node {
556            Node::SwissKnife(sk) => Some(sk.output),
557            _ => None,
558        }) {
559            return match output {
560                SkOutput::Integer => {
561                    let node = match self.nodes.get(name) {
562                        Some(Node::SwissKnife(node)) => node,
563                        _ => unreachable!("node vanished during lookup"),
564                    };
565                    let mut stack = HashSet::new();
566                    let value = self.evaluate_swissknife(node, io, &mut stack)?;
567                    round_to_i64(name, value)
568                }
569                SkOutput::Float => Err(GenApiError::Type(name.to_string())),
570            };
571        }
572        let node = self.get_integer_node(name)?;
573        ensure_readable(&node.access, name)?;
574        self.ensure_selectors(name, &node.selected_if, io)?;
575        // Return static value if present.
576        if let Some(v) = node.value {
577            return Ok(v);
578        }
579        // Delegate to pValue node if present.
580        if let Some(ref pv) = node.pvalue {
581            let pv = pv.clone();
582            return self.get_integer(&pv, io);
583        }
584        let addressing = node
585            .addressing
586            .as_ref()
587            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
588        let (address, len) = self.resolve_address(name, addressing, io)?;
589        if let Some(value) = *node.cache.borrow() {
590            return Ok(value);
591        }
592        let raw = io.read(address, len as usize).map_err(|err| match err {
593            GenApiError::Io(_) => err,
594            other => other,
595        })?;
596        let value = if let Some(bitfield) = node.bitfield {
597            let extracted = extract(&raw, bitfield).map_err(|err| map_bitops_error(name, err))?;
598            interpret_bitfield_value(name, extracted, bitfield.bit_length, node.min < 0)?
599        } else {
600            bytes_to_i64(name, &raw)?
601        };
602        debug!(node = %name, raw = value, "read integer feature");
603        node.cache.replace(Some(value));
604        node.raw_cache.replace(Some(raw));
605        Ok(value)
606    }
607
608    /// Write an integer feature and update dependent caches.
609    pub fn set_integer(
610        &mut self,
611        name: &str,
612        value: i64,
613        io: &dyn RegisterIo,
614    ) -> Result<(), GenApiError> {
615        let node = self.get_integer_node(name)?;
616        ensure_writable(&node.access, name)?;
617        self.ensure_selectors(name, &node.selected_if, io)?;
618        if let Some(ref pv) = node.pvalue {
619            let pv = pv.clone();
620            return self.set_integer(&pv, value, io);
621        }
622        let addressing = node
623            .addressing
624            .as_ref()
625            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
626        let (address, len) = self.resolve_address(name, addressing, io)?;
627        if value < node.min || value > node.max {
628            return Err(GenApiError::Range(name.to_string()));
629        }
630        if let Some(inc) = node.inc
631            && inc != 0
632            && (value - node.min) % inc != 0
633        {
634            return Err(GenApiError::Range(name.to_string()));
635        }
636        if let Some(bitfield) = node.bitfield {
637            let encoded = encode_bitfield_value(name, value, bitfield.bit_length, node.min < 0)?;
638            let mut raw = get_raw_or_read(&node.raw_cache, io, address, len)?;
639            insert(&mut raw, bitfield, encoded).map_err(|err| map_bitops_error(name, err))?;
640            debug!(node = %name, raw = value, "write integer feature");
641            io.write(address, &raw).map_err(|err| match err {
642                GenApiError::Io(_) => err,
643                other => other,
644            })?;
645            node.cache.replace(Some(value));
646            node.raw_cache.replace(Some(raw));
647        } else {
648            let bytes = i64_to_bytes(name, value, len)?;
649            debug!(node = %name, raw = value, "write integer feature");
650            io.write(address, &bytes).map_err(|err| match err {
651                GenApiError::Io(_) => err,
652                other => other,
653            })?;
654            node.cache.replace(Some(value));
655            node.raw_cache.replace(Some(bytes));
656        }
657        self.invalidate_dependents(name);
658        Ok(())
659    }
660
661    /// Read a floating point feature.
662    pub fn get_float(&self, name: &str, io: &dyn RegisterIo) -> Result<f64, GenApiError> {
663        if let Some(output) = self.nodes.get(name).and_then(|node| match node {
664            Node::SwissKnife(sk) => Some(sk.output),
665            _ => None,
666        }) {
667            return match output {
668                SkOutput::Float => {
669                    let node = match self.nodes.get(name) {
670                        Some(Node::SwissKnife(node)) => node,
671                        _ => unreachable!("node vanished during lookup"),
672                    };
673                    let mut stack = HashSet::new();
674                    let value = self.evaluate_swissknife(node, io, &mut stack)?;
675                    Ok(value)
676                }
677                SkOutput::Integer => self.get_integer(name, io).map(|v| v as f64),
678            };
679        }
680        let node = self.get_float_node(name)?;
681        ensure_readable(&node.access, name)?;
682        self.ensure_selectors(name, &node.selected_if, io)?;
683        if let Some(ref pv) = node.pvalue {
684            let pv = pv.clone();
685            return self.get_float(&pv, io);
686        }
687        let addressing = node
688            .addressing
689            .as_ref()
690            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
691        let (address, len) = self.resolve_address(name, addressing, io)?;
692        if let Some(value) = *node.cache.borrow() {
693            return Ok(value);
694        }
695        let raw = io.read(address, len as usize).map_err(|err| match err {
696            GenApiError::Io(_) => err,
697            other => other,
698        })?;
699        let value = match node.encoding {
700            FloatEncoding::Ieee754 => {
701                let v = decode_ieee754(name, &raw, node.byte_order)?;
702                debug!(node = %name, value = v, "read float feature (ieee754)");
703                v
704            }
705            FloatEncoding::ScaledInteger => {
706                let raw_value = bytes_to_i64(name, &raw)?;
707                let v = apply_scale(node, raw_value as f64);
708                debug!(node = %name, raw = raw_value, value = v, "read float feature (scaled)");
709                v
710            }
711        };
712        node.cache.replace(Some(value));
713        Ok(value)
714    }
715
716    /// Write a floating point feature using the scale/offset conversion.
717    pub fn set_float(
718        &mut self,
719        name: &str,
720        value: f64,
721        io: &dyn RegisterIo,
722    ) -> Result<(), GenApiError> {
723        let node = self.get_float_node(name)?;
724        ensure_writable(&node.access, name)?;
725        self.ensure_selectors(name, &node.selected_if, io)?;
726        if let Some(ref pv) = node.pvalue {
727            let pv = pv.clone();
728            return self.set_float(&pv, value, io);
729        }
730        let addressing = node
731            .addressing
732            .as_ref()
733            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
734        let (address, len) = self.resolve_address(name, addressing, io)?;
735        if value < node.min || value > node.max {
736            return Err(GenApiError::Range(name.to_string()));
737        }
738        let bytes = match node.encoding {
739            FloatEncoding::Ieee754 => {
740                let bytes = encode_ieee754(name, value, len, node.byte_order)?;
741                debug!(node = %name, value, "write float feature (ieee754)");
742                bytes
743            }
744            FloatEncoding::ScaledInteger => {
745                let raw = encode_float(node, value)?;
746                let bytes = i64_to_bytes(name, raw, len)?;
747                debug!(node = %name, raw, value, "write float feature (scaled)");
748                bytes
749            }
750        };
751        io.write(address, &bytes).map_err(|err| match err {
752            GenApiError::Io(_) => err,
753            other => other,
754        })?;
755        node.cache.replace(Some(value));
756        self.invalidate_dependents(name);
757        Ok(())
758    }
759
760    /// Read an enumeration feature returning the symbolic entry name.
761    pub fn get_enum(&self, name: &str, io: &dyn RegisterIo) -> Result<String, GenApiError> {
762        let node = self.get_enum_node(name)?;
763        ensure_readable(&node.access, name)?;
764        self.ensure_selectors(name, &node.selected_if, io)?;
765        // When pValue is set, read the integer from the delegate node.
766        if let Some(ref pv) = node.pvalue {
767            let pv = pv.clone();
768            if let Some(value) = node.value_cache.borrow().clone() {
769                return Ok(value);
770            }
771            let raw_value = self.get_integer(&pv, io)?;
772            let entry = self.lookup_enum_entry(node, raw_value, io)?;
773            node.value_cache.replace(Some(entry.clone()));
774            return Ok(entry);
775        }
776        let addressing = node
777            .addressing
778            .as_ref()
779            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing")))?;
780        let (address, len) = self.resolve_address(name, addressing, io)?;
781        if let Some(value) = node.value_cache.borrow().clone() {
782            return Ok(value);
783        }
784        let raw = io.read(address, len as usize).map_err(|err| match err {
785            GenApiError::Io(_) => err,
786            other => other,
787        })?;
788        let raw_value = bytes_to_i64(name, &raw)?;
789        let entry = self.lookup_enum_entry(node, raw_value, io)?;
790        debug!(node = %name, raw = raw_value, entry = %entry, "read enum feature");
791        node.value_cache.replace(Some(entry.clone()));
792        Ok(entry)
793    }
794
795    /// Write an enumeration entry.
796    pub fn set_enum(
797        &mut self,
798        name: &str,
799        entry: &str,
800        io: &dyn RegisterIo,
801    ) -> Result<(), GenApiError> {
802        let node = self.get_enum_node(name)?;
803        ensure_writable(&node.access, name)?;
804        self.ensure_selectors(name, &node.selected_if, io)?;
805        if let Some(ref pv) = node.pvalue {
806            let pv = pv.clone();
807            let entry_decl = node
808                .entries
809                .iter()
810                .find(|candidate| candidate.name == entry)
811                .ok_or_else(|| GenApiError::EnumNoSuchEntry {
812                    node: name.to_string(),
813                    entry: entry.to_string(),
814                })?;
815            let raw_value = self.resolve_enum_entry_value(node, entry_decl, io)?;
816            let entry_str = entry.to_string();
817            // Re-borrow node after mutable self call.
818            self.set_integer(&pv, raw_value, io)?;
819            let node = self.get_enum_node(name)?;
820            node.value_cache.replace(Some(entry_str));
821            node.invalidate();
822            self.invalidate_dependents(name);
823            return Ok(());
824        }
825        let addressing = node
826            .addressing
827            .as_ref()
828            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing")))?;
829        let (address, len) = self.resolve_address(name, addressing, io)?;
830        let entry_decl = node
831            .entries
832            .iter()
833            .find(|candidate| candidate.name == entry)
834            .ok_or_else(|| GenApiError::EnumNoSuchEntry {
835                node: name.to_string(),
836                entry: entry.to_string(),
837            })?;
838        let raw = self.resolve_enum_entry_value(node, entry_decl, io)?;
839        let bytes = i64_to_bytes(name, raw, len)?;
840        debug!(node = %name, raw, entry, "write enum feature");
841        io.write(address, &bytes).map_err(|err| match err {
842            GenApiError::Io(_) => err,
843            other => other,
844        })?;
845        node.value_cache.replace(None);
846        self.invalidate_dependents(name);
847        Ok(())
848    }
849
850    /// List the available entry names for an enumeration feature.
851    pub fn enum_entries(&self, name: &str) -> Result<Vec<String>, GenApiError> {
852        let node = self.get_enum_node(name)?;
853        if let Some(mapping) = node.mapping_cache.borrow().as_ref() {
854            let mut names: Vec<_> = mapping.by_name.keys().cloned().collect();
855            names.sort();
856            names.dedup();
857            return Ok(names);
858        }
859        let mut names: Vec<_> = node
860            .entries
861            .iter()
862            .map(|entry| entry.name.clone())
863            .collect();
864        names.sort();
865        names.dedup();
866        Ok(names)
867    }
868
869    /// Evaluate `pIsImplemented` for `name`, returning `true` when the feature
870    /// is implemented by the device.
871    ///
872    /// Absent `pIsImplemented` defaults to `true` (matching the GenICam spec:
873    /// an undeclared predicate means "always implemented"). Evaluation errors
874    /// propagate to the caller so bad XML is visible rather than silently
875    /// reported as implemented.
876    pub fn is_implemented(&self, name: &str, io: &dyn RegisterIo) -> Result<bool, GenApiError> {
877        let prefs = self.predicate_refs(name)?;
878        match &prefs.p_is_implemented {
879            None => Ok(true),
880            Some(provider) => self.eval_predicate_ref(name, provider, io),
881        }
882    }
883
884    /// Evaluate `pIsAvailable` plus selector gating for `name`.
885    ///
886    /// Returns `false` when the feature is not implemented, when
887    /// `pIsAvailable` evaluates to zero, or when any `selected_if` rule is
888    /// violated by the current selector value. Callers that want pure XML
889    /// gating without selector checks should use [`NodeMap::is_implemented`]
890    /// instead.
891    pub fn is_available(&self, name: &str, io: &dyn RegisterIo) -> Result<bool, GenApiError> {
892        if !self.is_implemented(name, io)? {
893            return Ok(false);
894        }
895        let prefs = self.predicate_refs(name)?;
896        if let Some(provider) = &prefs.p_is_available
897            && !self.eval_predicate_ref(name, provider, io)?
898        {
899            return Ok(false);
900        }
901        let selected_if = self
902            .nodes
903            .get(name)
904            .and_then(Self::selected_if_slice)
905            .unwrap_or(&[]);
906        self.selectors_allow(selected_if, io)
907    }
908
909    /// Return the effective [`AccessMode`] for `name` given the current
910    /// device state.
911    ///
912    /// - If the feature is unavailable (see [`NodeMap::is_available`]), the
913    ///   function returns `AccessMode::RO` — we cannot report "NA" without
914    ///   introducing a new variant, and Studio's wire protocol carries the
915    ///   availability flag separately.
916    /// - If `pIsLocked` evaluates truthy, `RW` downgrades to `RO`; `RO` and
917    ///   `WO` are unaffected.
918    /// - Otherwise the statically declared access mode applies.
919    pub fn effective_access_mode(
920        &self,
921        name: &str,
922        io: &dyn RegisterIo,
923    ) -> Result<AccessMode, GenApiError> {
924        let node = self
925            .nodes
926            .get(name)
927            .ok_or_else(|| GenApiError::NodeNotFound(name.to_string()))?;
928        let base = node.access_mode().unwrap_or(AccessMode::RO);
929        if !self.is_available(name, io)? {
930            return Ok(AccessMode::RO);
931        }
932        let prefs = self.predicate_refs(name)?;
933        if let Some(provider) = &prefs.p_is_locked
934            && self.eval_predicate_ref(name, provider, io)?
935        {
936            return Ok(match base {
937                AccessMode::RW => AccessMode::RO,
938                other => other,
939            });
940        }
941        Ok(base)
942    }
943
944    /// Return the subset of enum entries currently reported as available by
945    /// the device, or the full static list when no entry declares an
946    /// `pIsImplemented`/`pIsAvailable`.
947    ///
948    /// Falling back to the full list preserves current behaviour for XMLs
949    /// that don't gate individual entries, so callers stop seeing the stale
950    /// static list when the new predicates are added and otherwise behave as
951    /// before.
952    pub fn available_enum_entries(
953        &self,
954        name: &str,
955        io: &dyn RegisterIo,
956    ) -> Result<Vec<String>, GenApiError> {
957        let node = self.get_enum_node(name)?;
958        let any_entry_predicate = node.entries.iter().any(|e| !e.predicates.is_empty());
959        if !any_entry_predicate {
960            return self.enum_entries(name);
961        }
962        let mut out = Vec::new();
963        for entry in &node.entries {
964            if let Some(provider) = &entry.predicates.p_is_implemented
965                && !self.eval_predicate_ref(name, provider, io)?
966            {
967                continue;
968            }
969            if let Some(provider) = &entry.predicates.p_is_available
970                && !self.eval_predicate_ref(name, provider, io)?
971            {
972                continue;
973            }
974            out.push(entry.name.clone());
975        }
976        out.sort();
977        out.dedup();
978        Ok(out)
979    }
980
981    fn predicate_refs(&self, name: &str) -> Result<&PredicateRefs, GenApiError> {
982        self.nodes
983            .get(name)
984            .map(Node::predicates)
985            .ok_or_else(|| GenApiError::NodeNotFound(name.to_string()))
986    }
987
988    /// Evaluate a `pIs*` reference by reading the target node as an integer
989    /// truthy value.
990    ///
991    /// `ctx` is the node that owns the predicate; it is used for diagnostics
992    /// and cycle detection so a predicate that accidentally resolves back to
993    /// its own owner fails fast rather than recursing. Providers can be any
994    /// numeric-resolvable node: Integer, Boolean, Enum (integer form),
995    /// SwissKnife, or a Converter.
996    fn eval_predicate_ref(
997        &self,
998        ctx: &str,
999        provider: &str,
1000        io: &dyn RegisterIo,
1001    ) -> Result<bool, GenApiError> {
1002        if provider == ctx {
1003            return Err(GenApiError::ExprEval {
1004                name: ctx.to_string(),
1005                msg: "predicate references the node it gates".into(),
1006            });
1007        }
1008        let mut stack = HashSet::new();
1009        stack.insert(ctx.to_string());
1010        let value = self.resolve_numeric(provider, io, &mut stack)?;
1011        trace!(node = %ctx, provider, value, "predicate eval");
1012        Ok(value != 0.0)
1013    }
1014
1015    /// Non-erroring cousin of [`NodeMap::ensure_selectors`] — returns `Ok(false)`
1016    /// when a selector gating rule rejects the current state, rather than
1017    /// converting that into a [`GenApiError::Unavailable`].
1018    fn selectors_allow(
1019        &self,
1020        rules: &[(String, Vec<String>)],
1021        io: &dyn RegisterIo,
1022    ) -> Result<bool, GenApiError> {
1023        for (selector, allowed) in rules {
1024            if allowed.is_empty() {
1025                continue;
1026            }
1027            let current = self.get_selector_value(selector, io)?;
1028            if !allowed.iter().any(|v| v == &current) {
1029                return Ok(false);
1030            }
1031        }
1032        Ok(true)
1033    }
1034
1035    fn selected_if_slice(node: &Node) -> Option<&[(String, Vec<String>)]> {
1036        match node {
1037            Node::Integer(n) => Some(&n.selected_if),
1038            Node::Float(n) => Some(&n.selected_if),
1039            Node::Enum(n) => Some(&n.selected_if),
1040            Node::Boolean(n) => Some(&n.selected_if),
1041            _ => None,
1042        }
1043    }
1044
1045    /// Read a boolean feature.
1046    pub fn get_bool(&self, name: &str, io: &dyn RegisterIo) -> Result<bool, GenApiError> {
1047        let node = self.get_bool_node(name)?;
1048        ensure_readable(&node.access, name)?;
1049        self.ensure_selectors(name, &node.selected_if, io)?;
1050        if let Some(ref pv) = node.pvalue {
1051            let pv = pv.clone();
1052            let raw = self.get_integer(&pv, io)?;
1053            let on = node.on_value.unwrap_or(1);
1054            return Ok(raw == on);
1055        }
1056        let addressing = node
1057            .addressing
1058            .as_ref()
1059            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
1060        let bitfield = node
1061            .bitfield
1062            .ok_or_else(|| GenApiError::Parse(format!("{name}: boolean without bitfield")))?;
1063        let (address, len) = self.resolve_address(name, addressing, io)?;
1064        if let Some(value) = *node.cache.borrow() {
1065            return Ok(value);
1066        }
1067        let raw = io.read(address, len as usize).map_err(|err| match err {
1068            GenApiError::Io(_) => err,
1069            other => other,
1070        })?;
1071        let raw_value = extract(&raw, bitfield).map_err(|err| map_bitops_error(name, err))?;
1072        let value = raw_value != 0;
1073        debug!(node = %name, raw = raw_value, value, "read boolean feature");
1074        node.cache.replace(Some(value));
1075        node.raw_cache.replace(Some(raw));
1076        Ok(value)
1077    }
1078
1079    /// Write a boolean feature.
1080    pub fn set_bool(
1081        &mut self,
1082        name: &str,
1083        value: bool,
1084        io: &dyn RegisterIo,
1085    ) -> Result<(), GenApiError> {
1086        let node = self.get_bool_node(name)?;
1087        ensure_writable(&node.access, name)?;
1088        self.ensure_selectors(name, &node.selected_if, io)?;
1089        if let Some(ref pv) = node.pvalue {
1090            let pv = pv.clone();
1091            let on = node.on_value.unwrap_or(1);
1092            let off = node.off_value.unwrap_or(0);
1093            let raw = if value { on } else { off };
1094            return self.set_integer(&pv, raw, io);
1095        }
1096        let addressing = node
1097            .addressing
1098            .as_ref()
1099            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no addressing or pValue")))?;
1100        let bitfield = node
1101            .bitfield
1102            .ok_or_else(|| GenApiError::Parse(format!("{name}: boolean without bitfield")))?;
1103        let (address, len) = self.resolve_address(name, addressing, io)?;
1104        let encoded = if value { 1 } else { 0 };
1105        let mut raw = get_raw_or_read(&node.raw_cache, io, address, len)?;
1106        insert(&mut raw, bitfield, encoded).map_err(|err| map_bitops_error(name, err))?;
1107        debug!(node = %name, raw = encoded, value, "write boolean feature");
1108        io.write(address, &raw).map_err(|err| match err {
1109            GenApiError::Io(_) => err,
1110            other => other,
1111        })?;
1112        node.cache.replace(Some(value));
1113        node.raw_cache.replace(Some(raw));
1114        self.invalidate_dependents(name);
1115        Ok(())
1116    }
1117
1118    /// Execute a command feature by writing a value to the command register.
1119    pub fn exec_command(&mut self, name: &str, io: &dyn RegisterIo) -> Result<(), GenApiError> {
1120        let node = self.get_command_node(name)?;
1121        // Determine the value to write and the target.
1122        let cmd_value = node.command_value.unwrap_or(1);
1123
1124        if let Some(ref pv) = node.pvalue {
1125            // Delegate to the pValue node.
1126            let pv = pv.clone();
1127            debug!(node = %name, "execute command via pValue");
1128            return self.set_integer(&pv, cmd_value, io);
1129        }
1130
1131        let address = node
1132            .address
1133            .ok_or_else(|| GenApiError::NodeNotFound(format!("{name}: no address or pValue")))?;
1134        if node.len == 0 {
1135            return Err(GenApiError::Parse(format!(
1136                "command node {name} has zero length"
1137            )));
1138        }
1139        let data = i64_to_bytes(name, cmd_value, node.len)?;
1140        debug!(node = %name, "execute command");
1141        io.write(address, &data).map_err(|err| match err {
1142            GenApiError::Io(_) => err,
1143            other => other,
1144        })?;
1145        self.invalidate_dependents(name);
1146        Ok(())
1147    }
1148
1149    fn get_integer_node(&self, name: &str) -> Result<&IntegerNode, GenApiError> {
1150        match self.nodes.get(name) {
1151            Some(Node::Integer(node)) => Ok(node),
1152            Some(_) => Err(GenApiError::Type(name.to_string())),
1153            None => Err(GenApiError::NodeNotFound(name.to_string())),
1154        }
1155    }
1156
1157    fn get_float_node(&self, name: &str) -> Result<&FloatNode, GenApiError> {
1158        match self.nodes.get(name) {
1159            Some(Node::Float(node)) => Ok(node),
1160            Some(_) => Err(GenApiError::Type(name.to_string())),
1161            None => Err(GenApiError::NodeNotFound(name.to_string())),
1162        }
1163    }
1164
1165    fn get_enum_node(&self, name: &str) -> Result<&EnumNode, GenApiError> {
1166        match self.nodes.get(name) {
1167            Some(Node::Enum(node)) => Ok(node),
1168            Some(_) => Err(GenApiError::Type(name.to_string())),
1169            None => Err(GenApiError::NodeNotFound(name.to_string())),
1170        }
1171    }
1172
1173    fn get_bool_node(&self, name: &str) -> Result<&BooleanNode, GenApiError> {
1174        match self.nodes.get(name) {
1175            Some(Node::Boolean(node)) => Ok(node),
1176            Some(_) => Err(GenApiError::Type(name.to_string())),
1177            None => Err(GenApiError::NodeNotFound(name.to_string())),
1178        }
1179    }
1180
1181    fn get_command_node(&self, name: &str) -> Result<&CommandNode, GenApiError> {
1182        match self.nodes.get(name) {
1183            Some(Node::Command(node)) => Ok(node),
1184            Some(_) => Err(GenApiError::Type(name.to_string())),
1185            None => Err(GenApiError::NodeNotFound(name.to_string())),
1186        }
1187    }
1188
1189    fn ensure_selectors(
1190        &self,
1191        node_name: &str,
1192        rules: &[(String, Vec<String>)],
1193        io: &dyn RegisterIo,
1194    ) -> Result<(), GenApiError> {
1195        for (selector, allowed) in rules {
1196            if allowed.is_empty() {
1197                continue;
1198            }
1199            let current = self.get_selector_value(selector, io)?;
1200            if !allowed.iter().any(|value| value == &current) {
1201                return Err(GenApiError::Unavailable(format!(
1202                    "node '{node_name}' unavailable for selector '{selector}={current}'"
1203                )));
1204            }
1205        }
1206        Ok(())
1207    }
1208
1209    fn lookup_enum_entry(
1210        &self,
1211        node: &EnumNode,
1212        raw_value: i64,
1213        io: &dyn RegisterIo,
1214    ) -> Result<String, GenApiError> {
1215        {
1216            let mut cache = node.mapping_cache.borrow_mut();
1217            if cache.is_none() {
1218                *cache = Some(self.build_enum_mapping(node, io)?);
1219            }
1220            if let Some(mapping) = cache.as_ref()
1221                && let Some(entry) = mapping.by_value.get(&raw_value)
1222            {
1223                return Ok(entry.clone());
1224            }
1225            *cache = Some(self.build_enum_mapping(node, io)?);
1226            if let Some(mapping) = cache.as_ref()
1227                && let Some(entry) = mapping.by_value.get(&raw_value)
1228            {
1229                return Ok(entry.clone());
1230            }
1231        }
1232        Err(GenApiError::EnumValueUnknown {
1233            node: node.name.clone(),
1234            value: raw_value,
1235        })
1236    }
1237
1238    fn build_enum_mapping(
1239        &self,
1240        node: &EnumNode,
1241        io: &dyn RegisterIo,
1242    ) -> Result<EnumMapping, GenApiError> {
1243        let mut by_value = HashMap::new();
1244        let mut by_name = HashMap::new();
1245
1246        for entry in &node.entries {
1247            let value = self.resolve_enum_entry_value(node, entry, io)?;
1248            match by_value.entry(value) {
1249                HashMapEntry::Vacant(slot) => {
1250                    slot.insert(entry.name.clone());
1251                }
1252                HashMapEntry::Occupied(existing) => {
1253                    warn!(
1254                        enum_node = %node.name,
1255                        value,
1256                        kept = %existing.get(),
1257                        dropped = %entry.name,
1258                        "duplicate enum value"
1259                    );
1260                }
1261            }
1262            by_name.insert(entry.name.clone(), value);
1263        }
1264
1265        let mut summary: Vec<_> = by_value
1266            .iter()
1267            .map(|(value, name)| (*value, name.clone()))
1268            .collect();
1269        summary.sort_by_key(|(value, _)| *value);
1270        debug!(node = %node.name, entries = ?summary, "build enum mapping");
1271
1272        Ok(EnumMapping { by_value, by_name })
1273    }
1274
1275    fn resolve_enum_entry_value(
1276        &self,
1277        node: &EnumNode,
1278        entry: &EnumEntryDecl,
1279        io: &dyn RegisterIo,
1280    ) -> Result<i64, GenApiError> {
1281        match &entry.value {
1282            EnumValueSrc::Literal(value) => Ok(*value),
1283            EnumValueSrc::FromNode(provider) => {
1284                let value = self.get_integer(provider, io)?;
1285                trace!(
1286                    enum_node = %node.name,
1287                    entry = %entry.name,
1288                    provider = %provider,
1289                    value,
1290                    "resolved enum entry from provider"
1291                );
1292                Ok(value)
1293            }
1294        }
1295    }
1296
1297    fn resolve_address(
1298        &self,
1299        node_name: &str,
1300        addressing: &Addressing,
1301        io: &dyn RegisterIo,
1302    ) -> Result<(u64, u32), GenApiError> {
1303        match addressing {
1304            Addressing::Fixed { address, len } => Ok((*address, *len)),
1305            Addressing::BySelector { selector, map } => {
1306                let value = self.get_selector_value(selector, io)?;
1307                if let Some((_, (address, len))) = map.iter().find(|(name, _)| name == &value) {
1308                    let addr = *address;
1309                    let len = *len;
1310                    debug!(
1311                        node = %node_name,
1312                        selector = %selector,
1313                        value = %value,
1314                        address = format_args!("0x{addr:X}"),
1315                        len,
1316                        "resolve address via selector"
1317                    );
1318                    Ok((addr, len))
1319                } else {
1320                    Err(GenApiError::Unavailable(format!(
1321                        "node '{node_name}' unavailable for selector '{selector}={value}'"
1322                    )))
1323                }
1324            }
1325            Addressing::Indirect {
1326                p_address_node,
1327                len,
1328            } => {
1329                let addr_value = self.get_integer(p_address_node, io)?;
1330                if addr_value <= 0 {
1331                    return Err(GenApiError::BadIndirectAddress {
1332                        name: node_name.to_string(),
1333                        addr: addr_value,
1334                    });
1335                }
1336                let addr =
1337                    u64::try_from(addr_value).map_err(|_| GenApiError::BadIndirectAddress {
1338                        name: node_name.to_string(),
1339                        addr: addr_value,
1340                    })?;
1341                if addr == 0 {
1342                    return Err(GenApiError::BadIndirectAddress {
1343                        name: node_name.to_string(),
1344                        addr: addr_value,
1345                    });
1346                }
1347                debug!(
1348                    node = %node_name,
1349                    source = %p_address_node,
1350                    address = format_args!("0x{addr:X}"),
1351                    len = *len,
1352                    "resolve address via pAddress"
1353                );
1354                Ok((addr, *len))
1355            }
1356        }
1357    }
1358
1359    fn get_selector_value(
1360        &self,
1361        selector: &str,
1362        io: &dyn RegisterIo,
1363    ) -> Result<String, GenApiError> {
1364        match self.nodes.get(selector) {
1365            Some(Node::Enum(_)) => self.get_enum(selector, io),
1366            Some(Node::Boolean(_)) => Ok(self.get_bool(selector, io)?.to_string()),
1367            Some(Node::Integer(_)) => Ok(self.get_integer(selector, io)?.to_string()),
1368            Some(_) => Err(GenApiError::Parse(format!(
1369                "selector {selector} has unsupported type"
1370            ))),
1371            None => Err(GenApiError::NodeNotFound(selector.to_string())),
1372        }
1373    }
1374
1375    fn evaluate_swissknife(
1376        &self,
1377        node: &SkNode,
1378        io: &dyn RegisterIo,
1379        stack: &mut HashSet<String>,
1380    ) -> Result<f64, GenApiError> {
1381        if let Some((value, generation)) = *node.cache.borrow()
1382            && generation == self.generation.get()
1383        {
1384            return Ok(value);
1385        }
1386        if !stack.insert(node.name.clone()) {
1387            stack.remove(&node.name);
1388            return Err(GenApiError::ExprEval {
1389                name: node.name.clone(),
1390                msg: "cyclic dependency".into(),
1391            });
1392        }
1393        let current_gen = self.generation.get();
1394        let result = (|| {
1395            let mut values: HashMap<String, f64> = HashMap::new();
1396            let mut inputs = Vec::new();
1397            for (var, provider) in &node.vars {
1398                let value = self.resolve_numeric(provider, io, stack)?;
1399                values.insert(var.clone(), value);
1400                inputs.push((var.clone(), value));
1401            }
1402            let mut resolver = |ident: &str| -> Result<f64, SkEvalError> {
1403                values
1404                    .get(ident)
1405                    .copied()
1406                    .ok_or_else(|| SkEvalError::UnknownVariable(ident.to_string()))
1407            };
1408            let value = match eval_ast(&node.ast, &mut resolver) {
1409                Ok(value) => value,
1410                Err(SkEvalError::UnknownVariable(var)) => {
1411                    return Err(GenApiError::UnknownVariable {
1412                        name: node.name.clone(),
1413                        var,
1414                    });
1415                }
1416                Err(SkEvalError::DivisionByZero) => {
1417                    return Err(GenApiError::ExprEval {
1418                        name: node.name.clone(),
1419                        msg: "division by zero".into(),
1420                    });
1421                }
1422                Err(SkEvalError::UnknownFunction(func)) => {
1423                    return Err(GenApiError::ExprEval {
1424                        name: node.name.clone(),
1425                        msg: format!("unknown function: {func}"),
1426                    });
1427                }
1428                Err(SkEvalError::ArityMismatch {
1429                    name: func,
1430                    expected,
1431                    got,
1432                }) => {
1433                    return Err(GenApiError::ExprEval {
1434                        name: node.name.clone(),
1435                        msg: format!("function {func} expects {expected} args, got {got}"),
1436                    });
1437                }
1438            };
1439            debug!(node = %node.name, inputs = ?inputs, output = value, "evaluate SwissKnife");
1440            Ok(value)
1441        })();
1442        stack.remove(&node.name);
1443        match result {
1444            Ok(value) => {
1445                node.cache.replace(Some((value, current_gen)));
1446                Ok(value)
1447            }
1448            Err(err) => Err(err),
1449        }
1450    }
1451
1452    fn resolve_numeric(
1453        &self,
1454        provider: &str,
1455        io: &dyn RegisterIo,
1456        stack: &mut HashSet<String>,
1457    ) -> Result<f64, GenApiError> {
1458        match self.nodes.get(provider) {
1459            Some(Node::Integer(_)) => self.get_integer(provider, io).map(|v| v as f64),
1460            Some(Node::Float(_)) => self.get_float(provider, io),
1461            Some(Node::Boolean(_)) => Ok(if self.get_bool(provider, io)? {
1462                1.0
1463            } else {
1464                0.0
1465            }),
1466            Some(Node::Enum(_)) => self.get_enum_numeric(provider, io).map(|v| v as f64),
1467            Some(Node::SwissKnife(node)) => self.evaluate_swissknife(node, io, stack),
1468            Some(Node::Converter(node)) => self.evaluate_converter(node, io, stack),
1469            Some(Node::IntConverter(node)) => self
1470                .evaluate_int_converter(node, io, stack)
1471                .map(|v| v as f64),
1472            Some(_) => Err(GenApiError::Type(provider.to_string())),
1473            None => Err(GenApiError::NodeNotFound(provider.to_string())),
1474        }
1475    }
1476
1477    fn get_enum_numeric(&self, name: &str, io: &dyn RegisterIo) -> Result<i64, GenApiError> {
1478        let entry = self.get_enum(name, io)?;
1479        let node = self.get_enum_node(name)?;
1480        {
1481            let mut mapping = node.mapping_cache.borrow_mut();
1482            if mapping.is_none() {
1483                *mapping = Some(self.build_enum_mapping(node, io)?);
1484            }
1485            if let Some(map) = mapping.as_ref()
1486                && let Some(value) = map.by_name.get(&entry)
1487            {
1488                return Ok(*value);
1489            }
1490        }
1491        Err(GenApiError::EnumNoSuchEntry {
1492            node: name.to_string(),
1493            entry,
1494        })
1495    }
1496
1497    fn invalidate_dependents(&self, name: &str) {
1498        self.bump_generation();
1499        if let Some(children) = self.dependents.get(name) {
1500            let mut visited = HashSet::new();
1501            for child in children {
1502                self.invalidate_recursive(child, &mut visited);
1503            }
1504        }
1505    }
1506
1507    fn invalidate_recursive(&self, name: &str, visited: &mut HashSet<String>) {
1508        if !visited.insert(name.to_string()) {
1509            return;
1510        }
1511        if let Some(node) = self.nodes.get(name) {
1512            node.invalidate_cache();
1513        }
1514        if let Some(children) = self.dependents.get(name) {
1515            for child in children {
1516                self.invalidate_recursive(child, visited);
1517            }
1518        }
1519    }
1520
1521    fn bump_generation(&self) {
1522        let current = self.generation.get();
1523        self.generation.set(current.wrapping_add(1));
1524    }
1525
1526    // ========================================================================
1527    // Converter/IntConverter/String support
1528    // ========================================================================
1529
1530    fn get_converter_node(&self, name: &str) -> Result<&ConverterNode, GenApiError> {
1531        match self.nodes.get(name) {
1532            Some(Node::Converter(node)) => Ok(node),
1533            Some(_) => Err(GenApiError::Type(name.to_string())),
1534            None => Err(GenApiError::NodeNotFound(name.to_string())),
1535        }
1536    }
1537
1538    fn get_int_converter_node(&self, name: &str) -> Result<&IntConverterNode, GenApiError> {
1539        match self.nodes.get(name) {
1540            Some(Node::IntConverter(node)) => Ok(node),
1541            Some(_) => Err(GenApiError::Type(name.to_string())),
1542            None => Err(GenApiError::NodeNotFound(name.to_string())),
1543        }
1544    }
1545
1546    fn get_string_node(&self, name: &str) -> Result<&StringNode, GenApiError> {
1547        match self.nodes.get(name) {
1548            Some(Node::String(node)) => Ok(node),
1549            Some(_) => Err(GenApiError::Type(name.to_string())),
1550            None => Err(GenApiError::NodeNotFound(name.to_string())),
1551        }
1552    }
1553
1554    /// Read a Converter feature value (float) using the provided transport.
1555    pub fn get_converter(&self, name: &str, io: &dyn RegisterIo) -> Result<f64, GenApiError> {
1556        let node = self.get_converter_node(name)?;
1557        if let Some((value, generation)) = *node.cache.borrow()
1558            && generation == self.generation.get()
1559        {
1560            return Ok(value);
1561        }
1562        let mut stack = HashSet::new();
1563        let value = self.evaluate_converter(node, io, &mut stack)?;
1564        node.cache.replace(Some((value, self.generation.get())));
1565        Ok(value)
1566    }
1567
1568    /// Read an IntConverter feature value (integer) using the provided transport.
1569    pub fn get_int_converter(&self, name: &str, io: &dyn RegisterIo) -> Result<i64, GenApiError> {
1570        let node = self.get_int_converter_node(name)?;
1571        if let Some((value, generation)) = *node.cache.borrow()
1572            && generation == self.generation.get()
1573        {
1574            return Ok(value);
1575        }
1576        let mut stack = HashSet::new();
1577        let value = self.evaluate_int_converter(node, io, &mut stack)?;
1578        node.cache.replace(Some((value, self.generation.get())));
1579        Ok(value)
1580    }
1581
1582    /// Read a String feature value using the provided transport.
1583    pub fn get_string(&self, name: &str, io: &dyn RegisterIo) -> Result<String, GenApiError> {
1584        let node = self.get_string_node(name)?;
1585        ensure_readable(&node.access, name)?;
1586        if let Some((ref value, generation)) = *node.cache.borrow()
1587            && generation == self.generation.get()
1588        {
1589            return Ok(value.clone());
1590        }
1591        let (address, len) = self.resolve_address(name, &node.addressing, io)?;
1592        let raw = io.read(address, len as usize)?;
1593        // Convert bytes to string, stopping at first null byte
1594        let end = raw.iter().position(|&b| b == 0).unwrap_or(raw.len());
1595        let value = String::from_utf8_lossy(&raw[..end]).to_string();
1596        node.cache
1597            .replace(Some((value.clone(), self.generation.get())));
1598        debug!(node = %name, value = %value, "get_string");
1599        Ok(value)
1600    }
1601
1602    /// Write a String feature value using the provided transport.
1603    pub fn set_string(
1604        &self,
1605        name: &str,
1606        value: &str,
1607        io: &dyn RegisterIo,
1608    ) -> Result<(), GenApiError> {
1609        let node = self.get_string_node(name)?;
1610        ensure_writable(&node.access, name)?;
1611        let (address, len) = self.resolve_address(name, &node.addressing, io)?;
1612        // Build byte buffer with null termination
1613        let mut buf = vec![0u8; len as usize];
1614        let bytes = value.as_bytes();
1615        let copy_len = bytes.len().min(len as usize);
1616        buf[..copy_len].copy_from_slice(&bytes[..copy_len]);
1617        io.write(address, &buf)?;
1618        node.cache
1619            .replace(Some((value.to_string(), self.generation.get())));
1620        self.invalidate_dependents(name);
1621        debug!(node = %name, value = %value, "set_string");
1622        Ok(())
1623    }
1624
1625    fn evaluate_converter(
1626        &self,
1627        node: &ConverterNode,
1628        io: &dyn RegisterIo,
1629        stack: &mut HashSet<String>,
1630    ) -> Result<f64, GenApiError> {
1631        if !stack.insert(node.name.clone()) {
1632            stack.remove(&node.name);
1633            return Err(GenApiError::ExprEval {
1634                name: node.name.clone(),
1635                msg: "cyclic dependency".into(),
1636            });
1637        }
1638
1639        let result = (|| {
1640            // Build variable map for formula evaluation
1641            let mut values: HashMap<String, f64> = HashMap::new();
1642            for (var, provider) in &node.vars_to {
1643                let value = self.resolve_numeric(provider, io, stack)?;
1644                values.insert(var.clone(), value);
1645            }
1646            // Evaluate the formula
1647            let mut resolver = |ident: &str| -> Result<f64, SkEvalError> {
1648                values
1649                    .get(ident)
1650                    .copied()
1651                    .ok_or_else(|| SkEvalError::UnknownVariable(ident.to_string()))
1652            };
1653            match eval_ast(&node.ast_to, &mut resolver) {
1654                Ok(value) => {
1655                    debug!(node = %node.name, value, "evaluate Converter");
1656                    Ok(value)
1657                }
1658                Err(SkEvalError::UnknownVariable(var)) => Err(GenApiError::UnknownVariable {
1659                    name: node.name.clone(),
1660                    var,
1661                }),
1662                Err(SkEvalError::DivisionByZero) => Err(GenApiError::ExprEval {
1663                    name: node.name.clone(),
1664                    msg: "division by zero".into(),
1665                }),
1666                Err(SkEvalError::UnknownFunction(func)) => Err(GenApiError::ExprEval {
1667                    name: node.name.clone(),
1668                    msg: format!("unknown function: {func}"),
1669                }),
1670                Err(SkEvalError::ArityMismatch {
1671                    name: func,
1672                    expected,
1673                    got,
1674                }) => Err(GenApiError::ExprEval {
1675                    name: node.name.clone(),
1676                    msg: format!("function {func} expects {expected} args, got {got}"),
1677                }),
1678            }
1679        })();
1680
1681        stack.remove(&node.name);
1682        result
1683    }
1684
1685    fn evaluate_int_converter(
1686        &self,
1687        node: &IntConverterNode,
1688        io: &dyn RegisterIo,
1689        stack: &mut HashSet<String>,
1690    ) -> Result<i64, GenApiError> {
1691        if !stack.insert(node.name.clone()) {
1692            stack.remove(&node.name);
1693            return Err(GenApiError::ExprEval {
1694                name: node.name.clone(),
1695                msg: "cyclic dependency".into(),
1696            });
1697        }
1698
1699        let result = (|| {
1700            let mut values: HashMap<String, f64> = HashMap::new();
1701            for (var, provider) in &node.vars_to {
1702                let value = self.resolve_numeric(provider, io, stack)?;
1703                values.insert(var.clone(), value);
1704            }
1705            let mut resolver = |ident: &str| -> Result<f64, SkEvalError> {
1706                values
1707                    .get(ident)
1708                    .copied()
1709                    .ok_or_else(|| SkEvalError::UnknownVariable(ident.to_string()))
1710            };
1711            match eval_ast(&node.ast_to, &mut resolver) {
1712                Ok(value) => {
1713                    let int_value = round_to_i64(&node.name, value)?;
1714                    debug!(node = %node.name, int_value, "evaluate IntConverter");
1715                    Ok(int_value)
1716                }
1717                Err(SkEvalError::UnknownVariable(var)) => Err(GenApiError::UnknownVariable {
1718                    name: node.name.clone(),
1719                    var,
1720                }),
1721                Err(SkEvalError::DivisionByZero) => Err(GenApiError::ExprEval {
1722                    name: node.name.clone(),
1723                    msg: "division by zero".into(),
1724                }),
1725                Err(SkEvalError::UnknownFunction(func)) => Err(GenApiError::ExprEval {
1726                    name: node.name.clone(),
1727                    msg: format!("unknown function: {func}"),
1728                }),
1729                Err(SkEvalError::ArityMismatch {
1730                    name: func,
1731                    expected,
1732                    got,
1733                }) => Err(GenApiError::ExprEval {
1734                    name: node.name.clone(),
1735                    msg: format!("function {func} expects {expected} args, got {got}"),
1736                }),
1737            }
1738        })();
1739
1740        stack.remove(&node.name);
1741        result
1742    }
1743}
1744
1745impl From<XmlModel> for NodeMap {
1746    fn from(model: XmlModel) -> Self {
1747        NodeMap::try_from_xml(model).expect("invalid GenApi model")
1748    }
1749}