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}