luau_parser/impl/block/
impl.rs

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