luau_parser/impl/block/
impl.rs

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