Skip to main content

m2_syn/
body.rs

1//! Value-returning bodies with semicolon-terminated discarded statements.
2
3use crate::cst::Token;
4use crate::{Parse, ParseStream, Spanned, ToTokens, TokenParseError, TokenStream};
5
6type Semicolon = Token![;];
7
8/// A statement whose value is discarded by a trailing semicolon.
9#[derive(Debug, Clone, PartialEq, Eq, Spanned)]
10pub struct Terminated<S> {
11    pub statement: S,
12    pub semicolon: Semicolon,
13}
14
15impl<S> Terminated<S> {
16    /// Creates a value-discarding terminated statement.
17    pub fn new(statement: S, semicolon: Semicolon) -> Self {
18        Self {
19            statement,
20            semicolon,
21        }
22    }
23}
24
25impl<S: Parse> Parse for Terminated<S> {
26    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
27        reject_empty_statement(input)?;
28        Ok(Self::new(S::parse(input)?, Parse::parse(input)?))
29    }
30}
31
32impl<S: ToTokens> ToTokens for Terminated<S> {
33    fn to_tokens(&self, output: &mut TokenStream) {
34        self.statement.to_tokens(output);
35        self.semicolon.to_tokens(output);
36    }
37}
38
39/// Statements discarded by semicolons followed by an optional returned tail.
40///
41/// This is analogous to a Rust block: every terminated statement is evaluated
42/// for effects, while the unterminated tail supplies the body's value.
43#[derive(Debug, Clone, PartialEq, Eq, Spanned)]
44pub struct Body<S> {
45    pub statements: Vec<Terminated<S>>,
46    pub tail: Option<S>,
47}
48
49impl<S> Body<S> {
50    /// Creates a body from discarded statements and its returned tail.
51    pub fn new(statements: Vec<Terminated<S>>, tail: Option<S>) -> Self {
52        Self { statements, tail }
53    }
54}
55
56impl<S: Parse> Parse for Body<S> {
57    fn parse(input: &mut ParseStream) -> Result<Self, TokenParseError> {
58        let mut statements = Vec::new();
59        while !input.is_eof() {
60            reject_empty_statement(input)?;
61            let statement = S::parse(input)?;
62            if input
63                .peek()
64                .is_some_and(<Semicolon as Token>::matches_token_tree)
65            {
66                statements.push(Terminated::new(statement, Parse::parse(input)?));
67            } else {
68                return Ok(Self::new(statements, Some(statement)));
69            }
70        }
71        Ok(Self::new(statements, None))
72    }
73}
74
75fn reject_empty_statement(input: &ParseStream) -> Result<(), TokenParseError> {
76    let Some(token) = input
77        .peek()
78        .filter(|token| <Semicolon as Token>::matches_token_tree(token))
79    else {
80        return Ok(());
81    };
82    Err(TokenParseError::UnexpectedToken {
83        expected: "a statement before `;`",
84        found: ";".to_owned(),
85        span: token.span(),
86    })
87}
88
89impl<S: ToTokens> ToTokens for Body<S> {
90    fn to_tokens(&self, output: &mut TokenStream) {
91        self.statements.to_tokens(output);
92        self.tail.to_tokens(output);
93    }
94}