luau_parser/impl/block/
impl.rs

1//! All `impl` blocks for [`Block`].
2
3use lsp_types::Range;
4use luau_lexer::prelude::{Error, Lexer, Symbol, Token, TokenType};
5
6use crate::{
7    types::{
8        Block, GetRange, GetRangeError, Parse, ParseWithArgs, Pointer, Print, Statement,
9        TerminationStatement,
10    },
11    utils::get_token_type_display_extended,
12};
13
14/// A private helper trait for [`Block::parse_with`].
15trait MatchesToken {
16    /// Whether or not the current item matches the passed [`token`](Token).
17    fn matches(&self, token: &Token) -> bool;
18}
19
20impl<T: MatchesToken> MatchesToken for Option<T> {
21    #[inline]
22    fn matches(&self, token: &Token) -> bool {
23        self.as_ref().is_some_and(|value| value.matches(token))
24    }
25}
26
27impl MatchesToken for TokenType {
28    #[inline]
29    fn matches(&self, token: &Token) -> bool {
30        token == self
31    }
32}
33impl MatchesToken for Token {
34    #[inline]
35    fn matches(&self, token: &Token) -> bool {
36        token == self
37    }
38}
39
40impl MatchesToken for Vec<TokenType> {
41    #[inline]
42    fn matches(&self, token: &Token) -> bool {
43        self.contains(&token.token_type)
44    }
45}
46impl MatchesToken for Vec<Token> {
47    #[inline]
48    fn matches(&self, token: &Token) -> bool {
49        self.contains(token)
50    }
51}
52
53impl<const T: usize> MatchesToken for [Token; T] {
54    #[inline]
55    fn matches(&self, token: &Token) -> bool {
56        self.contains(token)
57    }
58}
59impl<const T: usize> MatchesToken for [TokenType; T] {
60    #[inline]
61    fn matches(&self, token: &Token) -> bool {
62        self.contains(&token.token_type)
63    }
64}
65
66impl<T: MatchesToken> ParseWithArgs<T> for Block {
67    fn parse_with(
68        mut token: Token,
69        lexer: &mut Lexer,
70        errors: &mut Vec<Error>,
71        stop_at: T,
72    ) -> Option<Self> {
73        let mut statements = Vec::new();
74        let mut last_statement = None;
75        let mut is_done = false;
76
77        if stop_at.matches(&token) {
78            return (!statements.is_empty() || last_statement.is_some()).then_some(Self {
79                statements,
80                last_statement,
81            });
82        }
83
84        loop {
85            if token.token_type == TokenType::EndOfFile {
86                is_done = true;
87            }
88            let mut failed_parsing = false;
89
90            if let Some(statement) = Statement::parse(token.clone(), lexer, errors) {
91                if last_statement.is_some() {
92                    // We will still continue parsing so LSPs, formatters, etc.
93                    // can still produce "correct" outputs.
94
95                    if let Ok(range) = statement.get_range() {
96                        errors.push(Error::new(
97                            range.start,
98                            "Statements after a termination statement are not allowed.".to_string(),
99                            Some(range.end),
100                        ));
101                    }
102                }
103
104                maybe_next_token!(lexer, maybe_semicolon, TokenType::Symbol(Symbol::Semicolon));
105                statements.push((Pointer::new(statement), maybe_semicolon))
106            } else if let Some(statement) =
107                TerminationStatement::parse(token.clone(), lexer, errors)
108            {
109                maybe_next_token!(lexer, maybe_semicolon, TokenType::Symbol(Symbol::Semicolon));
110                last_statement = Some((Pointer::new(statement), maybe_semicolon));
111            } else {
112                failed_parsing = true;
113            }
114
115            if is_done {
116                break;
117            }
118
119            let state = lexer.save_state();
120            let next_token = lexer.next_token();
121
122            if stop_at.matches(&next_token) {
123                lexer.set_state(state);
124
125                break;
126            } else if failed_parsing {
127                errors.push(Error::new(
128                    state.lexer_position(),
129                    format!(
130                        "Unexpected {}",
131                        get_token_type_display_extended(&token.token_type)
132                    ),
133                    Some(state.lexer_position()),
134                ));
135            }
136
137            token = next_token;
138        }
139
140        (!statements.is_empty() || last_statement.is_some()).then_some(Self {
141            statements,
142            last_statement,
143        })
144    }
145}
146
147impl Block {
148    /// Whether or not this block is empty.
149    pub const fn is_empty(&self) -> bool {
150        self.statements.is_empty() && self.last_statement.is_none()
151    }
152}
153
154/// A helper function to get the range of a [`Statement`] or [`TerminationStatement`]
155/// which accounts for the optional [`;`](Symbol::Semicolon) at the end.
156fn get_range<T: GetRange>(
157    statement: &T,
158    semi_colon: &Option<Token>,
159) -> Result<Range, GetRangeError> {
160    let statement_range = statement.get_range();
161
162    if let Some(semicolon) = semi_colon {
163        Ok(Range::new(
164            statement_range?.start,
165            semicolon.get_range()?.end,
166        ))
167    } else {
168        statement_range
169    }
170}
171
172impl GetRange for Block {
173    fn get_range(&self) -> Result<Range, GetRangeError> {
174        if self.is_empty() {
175            return Err(GetRangeError::EmptyBlock);
176        }
177        if let Some((first_statement, semi_colon)) = self.statements.first() {
178            let last_statement_range = match &self.last_statement {
179                Some((statement, semi_colon)) => get_range(statement, semi_colon),
180                None => get_range(first_statement, semi_colon),
181            };
182
183            return Ok(Range::new(
184                get_range(first_statement, semi_colon)?.start,
185                last_statement_range?.end,
186            ));
187        }
188
189        match &self.last_statement {
190            Some((statement, semi_colon)) => get_range(statement, semi_colon),
191            None => Err(GetRangeError::EmptyBlock),
192            // `None` should be `unreachable!()`.
193        }
194    }
195}
196
197impl Print for Block {
198    fn print_final_trivia(&self) -> String {
199        if self.is_empty() {
200            String::new()
201        } else if let Some(last_statement) = self.last_statement.as_ref() {
202            last_statement.print_final_trivia()
203        } else {
204            self.statements.print_final_trivia()
205        }
206    }
207
208    fn print_without_final_trivia(&self) -> String {
209        if self.is_empty() {
210            String::new()
211        } else if self.statements.is_empty() {
212            // SAFETY: Since `is_empty` is false and no statements exist, a
213            //         last_statement must exit.
214            #[allow(clippy::unwrap_used)]
215            self.last_statement
216                .as_ref()
217                .unwrap()
218                .print_without_final_trivia()
219        } else if self.last_statement.is_none() {
220            self.statements.print_without_final_trivia()
221        } else {
222            // SAFETY: Checked in the previous `else if` arm.
223            #[allow(clippy::unwrap_used)]
224            {
225                self.statements.print_without_final_trivia()
226                    + &self
227                        .last_statement
228                        .as_ref()
229                        .unwrap()
230                        .print_without_final_trivia()
231            }
232        }
233    }
234}