Skip to main content

vihaco_syntax/
parse.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4//! chumsky-0.10 combinators for the parsed-syntax shape.
5//!
6//! `Parse` impls for `ParsedModule`/`ParsedFunction` are generic over the
7//! consumer's instruction type `I`, source type `Ty`, and device-header type
8//! `H`.
9
10use chumsky::error::Simple;
11use chumsky::extra;
12use chumsky::prelude::*;
13use vihaco_parser::{Ident, Parse, QuotedString};
14
15use vihaco_bytecode::SstHeader;
16use vihaco_bytecode::SstSectionView;
17
18#[cfg(feature = "typed-declarations")]
19use crate::ParsedLabel;
20use crate::{Param, ParsedFunction, ParsedModule};
21
22#[cfg(feature = "typed-declarations")]
23enum BodyItem<I> {
24    Instruction(I),
25    Label(Ident),
26}
27
28type E<'src> = extra::Err<Simple<'src, char>>;
29
30/// Whitespace and `//`-to-end-of-line comments. Zero-or-more.
31pub fn skip<'src>() -> impl Parser<'src, &'src str, (), E<'src>> + Clone {
32    vihaco_parser::trivia()
33}
34
35/// `"…"` with `\\`, `\"`, `\n`, `\t`, `\r` escape sequences.
36pub fn string_literal<'src>() -> impl Parser<'src, &'src str, QuotedString, E<'src>> + Clone {
37    let escape = just('\\').ignore_then(choice((
38        just('"').to('"'),
39        just('\\').to('\\'),
40        just('n').to('\n'),
41        just('t').to('\t'),
42        just('r').to('\r'),
43        just('0').to('\0'),
44    )));
45    let char_or_escape = choice((
46        escape,
47        any().and_is(just('"').not()).and_is(just('\\').not()),
48    ));
49    just('"')
50        .ignore_then(char_or_escape.repeated().collect::<String>())
51        .then_ignore(just('"'))
52        .map(QuotedString)
53}
54
55/// `@name` — leading `@` consumed; name is `ident()`-shaped.
56pub fn symbol_ref<'src>() -> impl Parser<'src, &'src str, Ident, E<'src>> + Clone {
57    just('@').ignore_then(vihaco_parser::ident().map(Ident))
58}
59
60/// Block-body helper: parse a sequence of whitespace-separated `i64`s as
61/// **flat rows** between an outer `{` … `}` provided by the caller. The body
62/// itself is a sequence of `i64`s with any whitespace (including newlines)
63/// between them.
64///
65/// Real usage: `device slm.filling { 0 1 2 3 };`.
66pub fn block_i64_flat<'src>() -> impl Parser<'src, &'src str, Vec<i64>, E<'src>> + Clone {
67    let item = just('-')
68        .or_not()
69        .then(text::int(10))
70        .to_slice()
71        .map(|s: &str| s.parse::<i64>().unwrap());
72    // Allow any whitespace (incl. newlines) between numbers and around them.
73    let ws = any().filter(|c: &char| c.is_whitespace()).repeated();
74    ws.ignore_then(item)
75        .repeated()
76        .collect::<Vec<_>>()
77        .then_ignore(ws)
78}
79
80/// Block-body helper: parse rows of `i64 i64` pairs separated by whitespace.
81/// Within a row the two ints are whitespace-separated; rows themselves are
82/// also just whitespace-separated (newlines or other ws — the helper doesn't
83/// require row alignment).
84///
85/// Real usage: `device slm.traps { 1 1\n 5 1\n ... };`,
86/// `device camera.detect_sites { ... };`.
87pub fn block_i64_pairs<'src>() -> impl Parser<'src, &'src str, Vec<(i64, i64)>, E<'src>> + Clone {
88    let signed_int = just('-')
89        .or_not()
90        .then(text::int(10))
91        .to_slice()
92        .map(|s: &str| s.parse::<i64>().unwrap());
93    let inline_ws = any()
94        .filter(|c: &char| c.is_whitespace() && *c != '\n')
95        .repeated()
96        .at_least(1);
97    let ws = any().filter(|c: &char| c.is_whitespace()).repeated();
98
99    let pair = signed_int.then_ignore(inline_ws).then(signed_int);
100    ws.ignore_then(pair)
101        .repeated()
102        .collect::<Vec<_>>()
103        .then_ignore(ws)
104}
105
106/// Parse a parameter name, excluding `:` from the identifier grammar so it
107/// can serve as the parameter/type separator.
108fn parameter_name<'src>() -> impl Parser<'src, &'src str, Ident, E<'src>> + Clone {
109    any()
110        .filter(|c: &char| {
111            !c.is_whitespace()
112                && !matches!(
113                    *c,
114                    ':' | ',' | ';' | '(' | ')' | '{' | '}' | '[' | ']' | '"' | '\'' | '`'
115                )
116                && *c != '@'
117        })
118        .repeated()
119        .at_least(1)
120        .collect::<String>()
121        .map(Ident)
122        .padded()
123}
124
125/// Parse a comma-separated list of `name: type` parameters.
126fn param_list<'src, Ty>() -> impl Parser<'src, &'src str, Vec<Param<Ty>>, E<'src>>
127where
128    Ty: Parse<'src> + 'src,
129{
130    let parameter = parameter_name()
131        .then_ignore(just(':').padded())
132        .then(Ty::parser().padded())
133        .map(|(name, ty)| Param { name, ty });
134
135    parameter
136        .separated_by(just(',').padded())
137        .collect::<Vec<_>>()
138        .delimited_by(just('(').padded(), just(')').padded())
139}
140
141fn functions<'src, I, Ty>() -> impl Parser<'src, &'src str, Vec<ParsedFunction<I, Ty>>, E<'src>>
142where
143    I: Parse<'src> + 'src,
144    Ty: Parse<'src> + 'src,
145{
146    skip()
147        .ignore_then(ParsedFunction::<I, Ty>::parser())
148        .repeated()
149        .collect::<Vec<_>>()
150        .then_ignore(skip())
151}
152
153impl<'src, I, Ty> Parse<'src> for ParsedFunction<I, Ty>
154where
155    I: Parse<'src> + 'src,
156    Ty: Parse<'src> + 'src,
157{
158    fn parser() -> impl Parser<'src, &'src str, Self, E<'src>> {
159        let returns = Ty::parser()
160            .padded()
161            .separated_by(just(',').padded())
162            .allow_trailing()
163            .collect::<Vec<_>>()
164            .delimited_by(just('(').padded(), just(')').padded())
165            .or(Ty::parser().map(|ty| vec![ty]));
166        let return_types = just("->")
167            .padded()
168            .ignore_then(returns)
169            .or_not()
170            .map(Option::unwrap_or_default);
171        #[cfg(not(feature = "typed-declarations"))]
172        let body = skip()
173            .ignore_then(I::parser())
174            .repeated()
175            .collect::<Vec<_>>()
176            .then_ignore(skip());
177        #[cfg(feature = "typed-declarations")]
178        let body = {
179            let label = just("label")
180                .then_ignore(text::whitespace().at_least(1))
181                .ignore_then(just('@'))
182                .ignore_then(Ident::parser())
183                .map(BodyItem::Label);
184            skip()
185                .ignore_then(label.or(I::parser().map(BodyItem::Instruction)))
186                .repeated()
187                .collect::<Vec<_>>()
188                .map(|items| {
189                    let mut body = Vec::new();
190                    let mut labels = Vec::new();
191                    for item in items {
192                        match item {
193                            BodyItem::Instruction(instruction) => body.push(instruction),
194                            BodyItem::Label(name) => labels.push(ParsedLabel {
195                                name,
196                                instruction_index: body.len(),
197                            }),
198                        }
199                    }
200                    (body, labels)
201                })
202                .then_ignore(skip())
203        };
204
205        just("fn")
206            .padded()
207            .ignore_then(just('@'))
208            .ignore_then(Ident::parser())
209            .then(param_list::<Ty>())
210            .then(return_types)
211            .then_ignore(just('{').padded())
212            .then(body)
213            .then_ignore(just('}').padded())
214            .map(|(((name, params), return_types), body)| {
215                #[cfg(feature = "typed-declarations")]
216                let (body, labels) = body;
217                ParsedFunction {
218                    name,
219                    params,
220                    return_types,
221                    body,
222                    #[cfg(feature = "typed-declarations")]
223                    labels,
224                }
225            })
226    }
227}
228
229impl<I, Ty, H> ParsedModule<I, Ty, H> {
230    /// Parse a source section into its consumer instruction representation.
231    pub fn parse_section<'src, C>(section: SstSectionView<'src, C>) -> eyre::Result<Self>
232    where
233        H: SstHeader,
234        I: vihaco_parser::Parse<'src> + 'src,
235        Ty: vihaco_parser::Parse<'src> + 'src,
236    {
237        let header = section.parse_header::<H>()?;
238        let text = section.sst();
239        let functions = functions::<I, Ty>()
240            .parse(text)
241            .into_result()
242            .map_err(|errors| eyre::eyre!("failed to parse SST functions: {:?}", errors))?;
243
244        Ok(Self { header, functions })
245    }
246}