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
78        if stop_at.matches(&token) {
79            return (!statements.is_empty() || last_statement.is_some()).then_some(Self {
80                statements,
81                last_statement,
82            });
83        }
84
85        loop {
86            if token.token_type == TokenType::EndOfFile {
87                break;
88            }
89            let mut failed_parsing = false;
90
91            if let Some(statement) = Statement::parse(token.clone(), lexer, errors) {
92                if last_statement.is_some() {
93                    // We will still continue parsing so LSPs, formatters, etc.
94                    // can still produce "correct" outputs.
95
96                    if let Ok(range) = statement.get_range() {
97                        errors.push(ParseError::new(
98                            range.start,
99                            "Statements after a termination statement are not allowed.".to_string(),
100                            Some(range.end),
101                        ));
102                    }
103                }
104
105                maybe_next_token!(lexer, maybe_semicolon, TokenType::Symbol(Symbol::Semicolon));
106                statements.push((Pointer::new(statement), maybe_semicolon))
107            } else if let Some(statement) =
108                TerminationStatement::parse(token.clone(), lexer, errors)
109            {
110                maybe_next_token!(lexer, maybe_semicolon, TokenType::Symbol(Symbol::Semicolon));
111                last_statement = Some((Pointer::new(statement), maybe_semicolon));
112            } else {
113                failed_parsing = true;
114            }
115
116            let state = lexer.save_state();
117            let next_token = lexer.next_token();
118
119            if stop_at.matches(&next_token) {
120                lexer.set_state(state);
121
122                break;
123            } else if token.token_type != TokenType::EndOfFile && failed_parsing {
124                errors.push(ParseError::new(
125                    state.lexer_position(),
126                    format!(
127                        "Unexpected {}",
128                        get_token_type_display_extended(&token.token_type)
129                    ),
130                    Some(state.lexer_position()),
131                ));
132            }
133
134            token = next_token;
135        }
136
137        (!statements.is_empty() || last_statement.is_some()).then_some(Self {
138            statements,
139            last_statement,
140        })
141    }
142}
143
144impl Block {
145    /// Whether or not this block is empty.
146    pub fn is_empty(&self) -> bool {
147        self.statements.is_empty() && self.last_statement.is_none()
148    }
149}
150
151/// A helper function to get the range of a [`Statement`] or [`TerminationStatement`]
152/// which accounts for the optional [`;`](Symbol::Semicolon) at the end.
153fn get_range<T: GetRange>(
154    statement: &T,
155    semi_colon: &Option<Token>,
156) -> Result<Range, GetRangeError> {
157    let statement_range = statement.get_range();
158
159    if let Some(semicolon) = semi_colon {
160        Ok(Range::new(
161            statement_range?.start,
162            semicolon.get_range()?.end,
163        ))
164    } else {
165        statement_range
166    }
167}
168
169impl GetRange for Block {
170    fn get_range(&self) -> Result<Range, GetRangeError> {
171        if self.is_empty() {
172            return Err(GetRangeError::EmptyBlock);
173        }
174        if let Some((first_statement, semi_colon)) = self.statements.first() {
175            let last_statement_range = match &self.last_statement {
176                Some((statement, semi_colon)) => get_range(statement, semi_colon),
177                None => self
178                    .statements
179                    .first()
180                    .map(|(statement, semi_colon)| get_range(statement, semi_colon))
181                    .unwrap(), // We're sure that at least one statement exists.
182            };
183
184            return Ok(Range::new(
185                get_range(first_statement, semi_colon)?.start,
186                last_statement_range?.end,
187            ));
188        }
189
190        match &self.last_statement {
191            Some((statement, semi_colon)) => get_range(statement, semi_colon),
192            None => Err(GetRangeError::EmptyBlock),
193            // `None` should be `unreachable!()`.
194        }
195    }
196}
197
198impl Print for Block {
199    fn print_final_trivia(&self) -> String {
200        if self.is_empty() {
201            String::new()
202        } else if let Some(last_statement) = self.last_statement.as_ref() {
203            last_statement.print_final_trivia()
204        } else {
205            self.statements.print_final_trivia()
206        }
207    }
208
209    fn print_without_final_trivia(&self) -> String {
210        if self.is_empty() {
211            String::new()
212        } else if self.statements.is_empty() {
213            self.last_statement
214                .as_ref()
215                .unwrap()
216                .print_without_final_trivia()
217        } else if self.last_statement.is_none() {
218            self.statements.print_without_final_trivia()
219        } else {
220            self.statements.print_without_final_trivia()
221                + &self
222                    .last_statement
223                    .as_ref()
224                    .unwrap()
225                    .print_without_final_trivia()
226        }
227    }
228}