Skip to main content

luau_syntax/parser/
model.rs

1use crate::ast::{Block, Local};
2use crate::ast_names::{AstName, AstNameDenseHasher};
3use crate::cst::CstNodeMap;
4use crate::location::{Location, Position};
5use luau_common::{BStr, BString, ByteSlice, DenseHashMap};
6use std::fmt;
7
8#[derive(Debug, Clone, Default)]
9pub struct ParseOptions {
10    allow_declaration_syntax: bool,
11    capture_comments: bool,
12    no_error_limit: bool,
13    store_cst_data: bool,
14}
15
16#[derive(Debug, Clone)]
17pub struct FragmentParseResumeSettings<'ast> {
18    pub local_map: DenseHashMap<AstName<'ast>, Option<&'ast Local<'ast>>, AstNameDenseHasher>,
19    pub local_stack: Vec<&'ast Local<'ast>>,
20    pub resume_position: Position,
21}
22
23impl ParseOptions {
24    pub fn allow_declaration_syntax(&self) -> bool {
25        self.allow_declaration_syntax
26    }
27
28    pub fn capture_comments(&self) -> bool {
29        self.capture_comments
30    }
31
32    pub fn no_error_limit(&self) -> bool {
33        self.no_error_limit
34    }
35
36    pub fn store_cst_data(&self) -> bool {
37        self.store_cst_data
38    }
39
40    pub fn with_declaration_syntax(mut self, allow: bool) -> Self {
41        self.allow_declaration_syntax = allow;
42        self
43    }
44
45    pub fn with_comment_capture(mut self, capture: bool) -> Self {
46        self.capture_comments = capture;
47        self
48    }
49
50    pub fn without_error_limit(mut self) -> Self {
51        self.no_error_limit = true;
52        self
53    }
54
55    pub fn with_cst_data(mut self, store: bool) -> Self {
56        self.store_cst_data = store;
57        self
58    }
59}
60
61#[derive(Debug, PartialEq)]
62pub struct ParseMetadata<'ast> {
63    pub lines: usize,
64    pub hotcomments: Vec<HotComment>,
65    pub errors: Vec<ParseError>,
66    pub comment_locations: Vec<Comment>,
67    pub cst_nodes: CstNodeMap<'ast>,
68}
69
70impl<'ast> ParseMetadata<'ast> {
71    pub(crate) fn new(
72        lines: usize,
73        hotcomments: Vec<HotComment>,
74        errors: Vec<ParseError>,
75        comment_locations: Vec<Comment>,
76        cst_nodes: CstNodeMap<'ast>,
77    ) -> Self {
78        Self {
79            lines,
80            hotcomments,
81            errors,
82            comment_locations,
83            cst_nodes,
84        }
85    }
86
87    pub fn mode(&self) -> Option<Mode> {
88        mode_from_hotcomments(&self.hotcomments)
89    }
90
91    pub fn compiler_directives(&self) -> Vec<CompileDirective> {
92        compiler_directives(&self.hotcomments)
93    }
94}
95
96#[derive(Debug, PartialEq)]
97pub struct ParseResult<'ast> {
98    pub root: Block<'ast>,
99    pub metadata: ParseMetadata<'ast>,
100}
101
102impl<'ast> ParseResult<'ast> {
103    pub(crate) fn new(root: Block<'ast>, metadata: ParseMetadata<'ast>) -> Self {
104        Self { root, metadata }
105    }
106
107    pub fn is_within_comment(&self, position: Position) -> bool {
108        self.metadata
109            .comment_locations
110            .iter()
111            .any(|comment| comment.contains_position(position))
112    }
113}
114
115fn mode_from_hotcomments(hotcomments: &[HotComment]) -> Option<Mode> {
116    hotcomments.iter().find_map(|comment| {
117        if !comment.header {
118            return None;
119        }
120
121        match comment.content.as_slice() {
122            b"nocheck" => Some(Mode::NoCheck),
123            b"nonstrict" => Some(Mode::Nonstrict),
124            b"strict" => Some(Mode::Strict),
125            _ => None,
126        }
127    })
128}
129
130#[derive(Debug, PartialEq)]
131pub struct ParseNodeResult<'ast, T> {
132    pub node: T,
133    pub metadata: ParseMetadata<'ast>,
134}
135
136impl<'ast, T> ParseNodeResult<'ast, T> {
137    pub(crate) fn new(node: T, metadata: ParseMetadata<'ast>) -> Self {
138        Self { node, metadata }
139    }
140}
141
142#[derive(Debug, Clone, PartialEq)]
143pub struct ParseError {
144    pub location: Location,
145    pub message: ParseMessage,
146}
147
148impl ParseError {
149    pub fn new(location: Location, message: impl Into<ParseMessage>) -> Self {
150        Self {
151            location,
152            message: message.into(),
153        }
154    }
155
156    pub fn new_bytes(location: Location, message: Vec<u8>) -> Self {
157        Self {
158            location,
159            message: ParseMessage::from(message),
160        }
161    }
162}
163
164impl fmt::Display for ParseError {
165    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
166        self.message.fmt(formatter)
167    }
168}
169
170impl std::error::Error for ParseError {}
171
172#[derive(Debug, Clone, PartialEq)]
173pub struct ParseErrors {
174    errors: Vec<ParseError>,
175    message: ParseMessage,
176}
177
178impl ParseErrors {
179    pub fn new(errors: Vec<ParseError>) -> Option<Self> {
180        if errors.is_empty() {
181            return None;
182        }
183
184        let message = parse_errors_message(&errors);
185        Some(Self { errors, message })
186    }
187
188    pub(crate) fn single(error: ParseError) -> Self {
189        Self {
190            message: error.message.clone(),
191            errors: vec![error],
192        }
193    }
194
195    pub fn first(&self) -> &ParseError {
196        &self.errors[0]
197    }
198
199    pub fn errors(&self) -> &[ParseError] {
200        &self.errors
201    }
202
203    pub fn message(&self) -> &ParseMessage {
204        &self.message
205    }
206
207    pub fn into_errors(self) -> Vec<ParseError> {
208        self.errors
209    }
210}
211
212impl std::ops::Deref for ParseErrors {
213    type Target = [ParseError];
214
215    fn deref(&self) -> &Self::Target {
216        &self.errors
217    }
218}
219
220impl fmt::Display for ParseErrors {
221    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
222        self.message.fmt(formatter)
223    }
224}
225
226impl std::error::Error for ParseErrors {}
227
228fn parse_errors_message(errors: &[ParseError]) -> ParseMessage {
229    match errors {
230        [] => ParseMessage::from(""),
231        [error] => error.message.clone(),
232        errors => errors
233            .iter()
234            .find(|error| {
235                error
236                    .message
237                    .starts_with("Exceeded allowed recursion depth;")
238            })
239            .map(|error| error.message.clone())
240            .unwrap_or_else(|| ParseMessage::from(format!("{} parse errors", errors.len()))),
241    }
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
245pub struct ParseMessage(BString);
246
247impl ParseMessage {
248    pub fn as_bstr(&self) -> &BStr {
249        self.0.as_bstr()
250    }
251
252    pub fn as_bytes(&self) -> &[u8] {
253        self.0.as_bytes()
254    }
255
256    pub fn starts_with(&self, prefix: impl AsRef<[u8]>) -> bool {
257        self.as_bytes().starts_with(prefix.as_ref())
258    }
259}
260
261impl AsRef<[u8]> for ParseMessage {
262    fn as_ref(&self) -> &[u8] {
263        self.as_bytes()
264    }
265}
266
267impl fmt::Display for ParseMessage {
268    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
269        write!(formatter, "{}", self.0.as_bstr())
270    }
271}
272
273impl From<&str> for ParseMessage {
274    fn from(value: &str) -> Self {
275        Self(BString::from(value))
276    }
277}
278
279impl From<String> for ParseMessage {
280    fn from(value: String) -> Self {
281        Self(BString::from(value))
282    }
283}
284
285impl From<Vec<u8>> for ParseMessage {
286    fn from(bytes: Vec<u8>) -> Self {
287        Self(BString::new(bytes))
288    }
289}
290
291impl From<ParseMessage> for Vec<u8> {
292    fn from(value: ParseMessage) -> Self {
293        value.0.into()
294    }
295}
296
297impl From<ParseMessage> for BString {
298    fn from(value: ParseMessage) -> Self {
299        value.0
300    }
301}
302
303impl From<&ParseMessage> for BString {
304    fn from(value: &ParseMessage) -> Self {
305        value.0.clone()
306    }
307}
308
309impl PartialEq<&str> for ParseMessage {
310    fn eq(&self, other: &&str) -> bool {
311        self.as_bytes() == other.as_bytes()
312    }
313}
314
315impl PartialEq<str> for ParseMessage {
316    fn eq(&self, other: &str) -> bool {
317        self.as_bytes() == other.as_bytes()
318    }
319}
320
321impl PartialEq<[u8]> for ParseMessage {
322    fn eq(&self, other: &[u8]) -> bool {
323        self.as_bytes() == other
324    }
325}
326
327impl PartialEq<&[u8]> for ParseMessage {
328    fn eq(&self, other: &&[u8]) -> bool {
329        self.as_bytes() == *other
330    }
331}
332
333impl<const N: usize> PartialEq<&[u8; N]> for ParseMessage {
334    fn eq(&self, other: &&[u8; N]) -> bool {
335        self.as_bytes() == *other
336    }
337}
338
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340pub enum Mode {
341    NoCheck,
342    Nonstrict,
343    Strict,
344    Definition,
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
348pub enum CompileDirective {
349    Native,
350    Optimize(u8),
351}
352
353#[derive(Debug, Clone, PartialEq)]
354pub struct HotComment {
355    pub header: bool,
356    pub location: Location,
357    pub content: Vec<u8>,
358}
359
360pub(super) fn compiler_directives(hotcomments: &[HotComment]) -> Vec<CompileDirective> {
361    hotcomments
362        .iter()
363        .filter(|comment| comment.header)
364        .filter_map(|comment| match comment.content.as_slice() {
365            b"native" => Some(CompileDirective::Native),
366            content => content
367                .strip_prefix(b"optimize ")
368                .map(atoi_clamped_optimization_level)
369                .map(CompileDirective::Optimize),
370        })
371        .collect()
372}
373
374fn atoi_clamped_optimization_level(bytes: &[u8]) -> u8 {
375    let negative = bytes.first() == Some(&b'-');
376    let digits = bytes
377        .iter()
378        .skip(usize::from(matches!(bytes.first(), Some(b'-' | b'+'))))
379        .take_while(|byte| byte.is_ascii_digit())
380        .fold(0i32, |value, byte| {
381            value
382                .saturating_mul(10)
383                .saturating_add(i32::from(byte - b'0'))
384        });
385    digits
386        .checked_neg()
387        .filter(|_| negative)
388        .unwrap_or(digits)
389        .clamp(0, 2) as u8
390}
391
392#[derive(Debug, Clone, PartialEq)]
393pub struct Comment {
394    pub kind: CommentKind,
395    pub location: Location,
396}
397
398impl Comment {
399    pub fn contains_position(&self, position: Position) -> bool {
400        self.location.contains(position)
401            || (self.kind == CommentKind::Broken && self.location.begin <= position)
402            || (self.kind == CommentKind::Line
403                && self.location.end.line == position.line
404                && self.location.begin <= position)
405    }
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
409pub enum CommentKind {
410    Line,
411    Block,
412    Broken,
413}