Skip to main content

vihaco_parser_core/container/
section.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4use std::ops::Range;
5
6/// The fully resolved path for a parsed section.
7///
8/// The root section has no components.
9#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub struct SectionPath {
11    components: Vec<String>,
12}
13
14impl SectionPath {
15    pub fn root() -> Self {
16        Self {
17            components: Vec::new(),
18        }
19    }
20
21    pub fn is_root(&self) -> bool {
22        self.components.is_empty()
23    }
24
25    pub fn components(&self) -> &[String] {
26        &self.components
27    }
28
29    pub fn local_name(&self) -> Option<&str> {
30        self.components.last().map(String::as_str)
31    }
32
33    pub(crate) fn child(&self, local_name: impl Into<String>) -> Self {
34        let mut components = self.components.clone();
35        components.push(local_name.into());
36        Self { components }
37    }
38}
39
40impl Default for SectionPath {
41    fn default() -> Self {
42        Self::root()
43    }
44}
45
46impl std::fmt::Display for SectionPath {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        if self.is_root() {
49            return f.write_str("<root>");
50        }
51
52        for (index, component) in self.components.iter().enumerate() {
53            if index != 0 {
54                f.write_str("/")?;
55            }
56            f.write_str(component)?;
57        }
58        Ok(())
59    }
60}
61
62impl std::fmt::Debug for SectionPath {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        std::fmt::Display::fmt(self, f)
65    }
66}
67
68/// The implementation-level representation of a parsed section.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct SectionNode {
71    pub path: SectionPath,
72    pub section: Range<usize>,
73    pub header: Range<usize>,
74    pub bytecode: Range<usize>,
75    pub children: Vec<SectionNode>,
76}
77
78pub(crate) fn validate_local_section_name(parent: &SectionPath, child: &str) -> eyre::Result<()> {
79    if child.is_empty() {
80        return Err(eyre::eyre!("section `{}` has an empty child name", parent));
81    }
82    if child.contains('/') {
83        return Err(eyre::eyre!(
84            "section `{}` child name `{}` must be a local name",
85            parent,
86            child
87        ));
88    }
89    Ok(())
90}