Skip to main content

vihaco_syntax/
types.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4//! Parsed-syntax data shapes. See module docs in [`super`].
5
6use vihaco_parser::Ident;
7
8/// Parsed SST module containing consumer instruction values.
9///
10/// `I` is the consumer's instruction syntax type, `Ty` is its source type
11/// syntax, and `H` is its section-header type.
12#[derive(Debug, Clone, PartialEq)]
13pub struct ParsedModule<I, Ty, H> {
14    pub header: H,
15    pub functions: Vec<ParsedFunction<I, Ty>>,
16}
17
18#[derive(Debug, Clone, PartialEq)]
19pub struct ParsedFunction<I, Ty> {
20    /// Function name with the leading `@` stripped (`@main` → `"main"`).
21    pub name: Ident,
22    /// Function parameters parsed from `name: type` declarations.
23    pub params: Vec<Param<Ty>>,
24    /// Return types parsed with the consumer-provided `Ty` syntax.
25    pub return_types: Vec<Ty>,
26    pub body: Vec<I>,
27    /// Labels and their positions in the executable body, for loader preparation.
28    #[cfg(feature = "typed-declarations")]
29    pub labels: Vec<ParsedLabel>,
30}
31
32/// A function-local label that occupies no executable instruction slot.
33#[cfg(feature = "typed-declarations")]
34#[derive(Debug, Clone, PartialEq)]
35pub struct ParsedLabel {
36    pub name: Ident,
37    /// Index of the next instruction in this function, possibly `body.len()`.
38    pub instruction_index: usize,
39}
40
41#[derive(Debug, Clone, PartialEq)]
42pub struct Param<Ty> {
43    pub name: Ident,
44    pub ty: Ty,
45}
46
47impl<I, Ty: PartialEq, H> ParsedModule<I, Ty, H> {
48    /// Check `fn` declarations against supplied metadata without changing either.
49    /// Table bounds and runtime constant references are checked during assembly.
50    pub fn validate_metadata<V, Info>(
51        &self,
52        metadata: &vihaco_module::module::ProgramMetadata<V, Ty, Info>,
53    ) -> eyre::Result<()> {
54        validate_functions(&self.functions, metadata)
55    }
56}
57
58/// Check function declarations independently of the consumer's header shape.
59pub(crate) fn validate_functions<I, Ty: PartialEq, V, Info>(
60    functions: &[ParsedFunction<I, Ty>],
61    metadata: &vihaco_module::module::ProgramMetadata<V, Ty, Info>,
62) -> eyre::Result<()> {
63    eyre::ensure!(
64        functions.len() == metadata.functions.len(),
65        "function declarations and metadata must contain the same functions"
66    );
67    let mut seen = std::collections::BTreeSet::new();
68    let mut address = 0usize;
69    for function in functions {
70        let name = function.name.as_str();
71        eyre::ensure!(
72            seen.insert(name),
73            "duplicate function declaration `@{name}`"
74        );
75        let mut matches = metadata.functions.iter().filter(|info| {
76            metadata
77                .strings
78                .get(info.name as usize)
79                .is_some_and(|value| value == name)
80        });
81        let info = matches
82            .next()
83            .ok_or_else(|| eyre::eyre!("function `@{name}` has no metadata"))?;
84        eyre::ensure!(
85            matches.next().is_none(),
86            "function `@{name}` has duplicate metadata"
87        );
88        let end = address
89            .checked_add(function.body.len())
90            .ok_or_else(|| eyre::eyre!("instruction count overflow"))?;
91        eyre::ensure!(
92            info.start_address as usize == address && info.end_address as usize == end,
93            "function `@{name}`: supplied range {}..{} disagrees with body range {address}..{end}",
94            info.start_address,
95            info.end_address
96        );
97        eyre::ensure!(
98            function.params.len() == info.signature.params.len(),
99            "function `@{name}`: parameter count disagrees with metadata"
100        );
101        for (parameter, declared) in function.params.iter().zip(&info.signature.params) {
102            eyre::ensure!(
103                metadata
104                    .strings
105                    .get(declared.name as usize)
106                    .is_some_and(|value| value == parameter.name.as_str())
107                    && parameter.ty == declared.ty,
108                "function `@{name}`: parameter `{}` disagrees with metadata",
109                parameter.name
110            );
111        }
112        eyre::ensure!(
113            function.return_types == info.signature.ret,
114            "function `@{name}`: return types disagree with metadata"
115        );
116        #[cfg(feature = "typed-declarations")]
117        {
118            let mut labels = std::collections::BTreeSet::new();
119            for label in &function.labels {
120                eyre::ensure!(
121                    labels.insert(label.name.as_str()),
122                    "function `@{name}`: duplicate label `@{}`",
123                    label.name
124                );
125                eyre::ensure!(
126                    label.instruction_index <= function.body.len(),
127                    "function `@{name}`: label `@{}` is outside its body",
128                    label.name
129                );
130                let label_address = address + label.instruction_index;
131                eyre::ensure!(
132                    metadata.labels.iter().any(|info| {
133                        metadata
134                            .strings
135                            .get(info.name as usize)
136                            .is_some_and(|value| value == label.name.as_str())
137                            && info.address as usize == label_address
138                    }),
139                    "function `@{name}`: label `@{}` at {label_address} requires matching metadata",
140                    label.name
141                );
142            }
143        }
144        address = end;
145    }
146    Ok(())
147}