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