1use only_diagnostic::{Diagnostic, DiagnosticCode, DiagnosticPhase, DiagnosticSeverity};
2use rowan::SyntaxNodeChildren;
3use text_size::{TextRange, TextSize};
4use winnow::Parser;
5use winnow::combinator::alt;
6use winnow::error::{ContextError, ErrMode, ModalResult};
7use winnow::token::any;
8
9use crate::ast_view::DocumentNode;
10use crate::builder::ParseTreeBuilder;
11use crate::cst::SyntaxNode;
12use crate::cursor::TokenCursor;
13use crate::recover::{
14 advance, consume_line, starts_indented_namespace_boundary, starts_indented_namespace_member,
15 starts_top_level_item,
16};
17use crate::trivia::{is_trivia, line_contains_kind, line_has_non_trivia};
18use crate::{LexToken, SyntaxKind, lex};
19
20#[derive(Debug, Clone)]
21pub struct ParseResult {
22 pub root: SyntaxNode,
23 diagnostics: Vec<Diagnostic>,
24}
25
26impl ParseResult {
27 pub fn document(&self) -> DocumentNode {
35 DocumentNode::cast(self.root.clone()).expect("parse root must always be a document node")
36 }
37}
38
39pub trait ParseResultExt {
41 fn root_children(&self) -> SyntaxNodeChildren<crate::cst::OnlyLanguage>;
43
44 fn diagnostics(&self) -> &[Diagnostic];
46}
47
48impl ParseResultExt for ParseResult {
49 fn root_children(&self) -> SyntaxNodeChildren<crate::cst::OnlyLanguage> {
50 self.root.children()
51 }
52
53 fn diagnostics(&self) -> &[Diagnostic] {
54 &self.diagnostics
55 }
56}
57
58pub fn parse(source: &str) -> ParseResult {
66 let tokens = lex(source);
67 parse_tokens(&tokens)
68}
69
70pub(crate) fn parse_tokens(tokens: &[LexToken]) -> ParseResult {
71 let mut builder = ParseTreeBuilder::new();
72 let mut diagnostics = Vec::new();
73 let kinds = tokens.iter().map(|token| token.kind).collect::<Vec<_>>();
74 let mut cursor = TokenCursor::new(tokens, &kinds);
75 let mut in_braced_namespace = false;
76
77 loop {
78 let trivia = cursor.skip_trivia();
79 builder.push_tokens(trivia);
80
81 let Some(token) = cursor.current() else {
82 break;
83 };
84 if token.kind == SyntaxKind::Eof {
85 break;
86 }
87
88 let mut input = cursor.remaining();
89 let (item, consumed) =
90 (|input: &mut &[SyntaxKind]| parse_top_level_item(input, in_braced_namespace))
91 .with_taken()
92 .parse_next(&mut input)
93 .expect("top-level parser should always consume a non-EOF item");
94 let token_slice = cursor.consume(consumed.len());
95
96 match item {
97 ParsedTopLevelItem::Directive { malformed } => {
98 if malformed {
99 diagnostics.push(parse_error(
100 "parse.malformed-directive",
101 "invalid directive",
102 token.range,
103 ));
104 builder.push_node(SyntaxKind::Error, token_slice);
105 continue;
106 }
107 builder.push_node(SyntaxKind::Directive, token_slice);
108 }
109 ParsedTopLevelItem::DocComment => {
110 builder.push_node(SyntaxKind::DocComment, token_slice);
111 }
112 ParsedTopLevelItem::Namespace {
113 malformed,
114 is_close,
115 has_open_brace,
116 } => {
117 if malformed {
118 diagnostics.push(parse_error(
119 "parse.malformed-namespace-header",
120 "invalid namespace",
121 token.range,
122 ));
123 builder.push_node(SyntaxKind::Error, token_slice);
124 continue;
125 }
126 builder.push_node(SyntaxKind::NamespaceBlock, token_slice);
127 in_braced_namespace = has_open_brace && !is_close;
128 }
129 ParsedTopLevelItem::Task {
130 saw_colon,
131 malformed,
132 } => {
133 if !saw_colon || malformed {
134 diagnostics.push(parse_error(
135 "parse.malformed-task-header",
136 "invalid task header",
137 token.range,
138 ));
139 builder.push_node(SyntaxKind::Error, token_slice);
140 continue;
141 }
142 builder.push_task(token_slice);
143 }
144 ParsedTopLevelItem::Unexpected => {
145 diagnostics.push(parse_error(
146 "parse.unexpected-token",
147 "unexpected text",
148 token.range,
149 ));
150 builder.push_node(SyntaxKind::Error, token_slice);
151 }
152 }
153 }
154
155 ParseResult {
156 root: builder.finish(),
157 diagnostics,
158 }
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162enum ParsedTopLevelItem {
163 Directive {
164 malformed: bool,
165 },
166 DocComment,
167 Namespace {
168 malformed: bool,
169 is_close: bool,
170 has_open_brace: bool,
171 },
172 Task {
173 saw_colon: bool,
174 malformed: bool,
175 },
176 Unexpected,
177}
178
179fn parse_top_level_item(
180 input: &mut &[SyntaxKind],
181 in_braced_namespace: bool,
182) -> ModalResult<ParsedTopLevelItem> {
183 alt((
184 parse_directive_item,
185 parse_doc_comment_item,
186 parse_namespace_item,
187 |input: &mut &[SyntaxKind]| parse_task_item(input, in_braced_namespace),
188 parse_unexpected_item,
189 ))
190 .parse_next(input)
191}
192
193fn parse_directive_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
194 token_kind(input, SyntaxKind::Bang)?;
195 let malformed = !line_has_non_trivia(input) || line_contains_kind(input, SyntaxKind::Comment);
196 consume_line(input);
197 Ok(ParsedTopLevelItem::Directive { malformed })
198}
199
200fn parse_doc_comment_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
201 token_kind(input, SyntaxKind::Percent)?;
202 consume_line(input);
203 Ok(ParsedTopLevelItem::DocComment)
204}
205
206fn parse_namespace_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
207 if input.first() == Some(&SyntaxKind::RBrace) {
208 advance(input);
209 let malformed =
210 line_has_non_trivia(input) || line_contains_kind(input, SyntaxKind::Comment);
211 consume_line(input);
212 return Ok(ParsedTopLevelItem::Namespace {
213 malformed,
214 is_close: true,
215 has_open_brace: false,
216 });
217 }
218
219 token_kind(input, SyntaxKind::LBracket)?;
220 let has_open_brace = line_contains_kind(input, SyntaxKind::LBrace);
221 let malformed = namespace_open_is_malformed(input);
222 consume_line(input);
223 Ok(ParsedTopLevelItem::Namespace {
224 malformed,
225 is_close: false,
226 has_open_brace,
227 })
228}
229
230fn namespace_open_is_malformed(input: &[SyntaxKind]) -> bool {
231 let line = input
232 .iter()
233 .copied()
234 .take_while(|kind| !matches!(kind, SyntaxKind::Newline | SyntaxKind::Eof))
235 .collect::<Vec<_>>();
236 let mut index = 0;
237
238 while line.get(index) == Some(&SyntaxKind::Whitespace) {
239 index += 1;
240 }
241 if line.get(index) == Some(&SyntaxKind::Ident) {
242 index += 1;
243 }
244 while line.get(index) == Some(&SyntaxKind::Whitespace) {
245 index += 1;
246 }
247 if line.get(index) != Some(&SyntaxKind::RBracket) {
248 return true;
249 }
250 index += 1;
251 while line.get(index) == Some(&SyntaxKind::Whitespace) {
252 index += 1;
253 }
254 if index == line.len() {
255 return false;
256 }
257 if line.get(index) != Some(&SyntaxKind::LBrace) {
258 return true;
259 }
260 index += 1;
261 while line.get(index) == Some(&SyntaxKind::Whitespace) {
262 index += 1;
263 }
264 index != line.len()
265}
266
267fn parse_task_item(
268 input: &mut &[SyntaxKind],
269 in_braced_namespace: bool,
270) -> ModalResult<ParsedTopLevelItem> {
271 token_kind(input, SyntaxKind::Ident)?;
272 let mut saw_colon = false;
273 let mut header_complete = false;
274 let mut line_start = false;
275 let mut malformed = false;
276 let mut expect_guard_at = false;
277 let mut phase = TaskHeaderPhase::BeforeTail;
278 let mut saw_parameter_list = false;
279 let mut continuation_header = false;
280 let mut expect_clause_start = false;
281 let mut expect_param_indent = false;
282
283 while let Some(kind) = input.first().copied() {
284 if header_complete
285 && line_start
286 && (starts_top_level_item(kind)
287 || starts_indented_namespace_boundary(input)
288 || (in_braced_namespace && starts_indented_namespace_member(input)))
289 {
290 break;
291 }
292
293 if saw_colon
294 && !header_complete
295 && !matches!(kind, SyntaxKind::Whitespace | SyntaxKind::Newline)
296 {
297 malformed = true;
298 }
299
300 if !header_complete {
301 if expect_param_indent {
302 match kind {
303 SyntaxKind::Indent => expect_param_indent = false,
304 SyntaxKind::RParen => expect_param_indent = false,
305 _ => {
306 malformed = true;
307 break;
308 }
309 }
310 }
311
312 if kind == SyntaxKind::Comment {
313 malformed = true;
314 }
315
316 if continuation_header && expect_clause_start {
317 match kind {
318 SyntaxKind::Indent | SyntaxKind::Whitespace => {}
321 SyntaxKind::Question
322 | SyntaxKind::Amp
323 | SyntaxKind::ShellKw
324 | SyntaxKind::ShellFallbackKw => {
325 expect_clause_start = false;
326 }
327 SyntaxKind::Colon => {
328 expect_clause_start = false;
329 }
330 SyntaxKind::Newline => malformed = true,
331 _ => {
332 malformed = true;
333 expect_clause_start = false;
334 }
335 }
336 }
337
338 match &mut phase {
339 TaskHeaderPhase::BeforeTail => match kind {
340 SyntaxKind::LParen => {
341 saw_parameter_list = true;
342 phase = TaskHeaderPhase::Params { depth: 1 };
343 }
344 SyntaxKind::Question => {
345 phase = TaskHeaderPhase::Guard { depth: 0 };
346 expect_guard_at = true;
347 }
348 SyntaxKind::Amp => {
349 phase = TaskHeaderPhase::Dependencies {
350 group_depth: 0,
351 saw_group: false,
352 };
353 }
354 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
355 SyntaxKind::At if expect_guard_at => {
356 expect_guard_at = false;
357 }
358 _ => {
359 if expect_guard_at {
360 malformed = true;
361 expect_guard_at = false;
362 }
363 }
364 },
365 TaskHeaderPhase::Params { depth } => match kind {
366 SyntaxKind::LParen => *depth += 1,
367 SyntaxKind::RParen => {
368 if *depth == 0 {
369 malformed = true;
370 } else {
371 *depth -= 1;
372 if *depth == 0 {
373 phase = TaskHeaderPhase::BeforeTail;
374 }
375 }
376 }
377 _ => {}
378 },
379 TaskHeaderPhase::Guard { depth } => match kind {
380 SyntaxKind::LParen => *depth += 1,
381 SyntaxKind::RParen => {
382 if *depth > 0 {
383 *depth -= 1;
384 }
385 if *depth == 0 {
386 phase = TaskHeaderPhase::BeforeTail;
387 }
388 }
389 SyntaxKind::At if expect_guard_at => {
390 expect_guard_at = false;
391 }
392 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
393 _ => {
394 if expect_guard_at {
395 malformed = true;
396 expect_guard_at = false;
397 }
398 }
399 },
400 TaskHeaderPhase::Dependencies {
401 group_depth,
402 saw_group,
403 } => match kind {
404 SyntaxKind::LParen => {
405 if *group_depth > 0 {
406 malformed = true;
407 }
408 *group_depth += 1;
409 *saw_group = true;
410 }
411 SyntaxKind::RParen => {
412 if *group_depth == 0 {
413 malformed = true;
414 } else {
415 *group_depth -= 1;
416 }
417 }
418 SyntaxKind::Question | SyntaxKind::At => malformed = true,
419 SyntaxKind::ShellKw | SyntaxKind::ShellFallbackKw if *group_depth == 0 => {
420 phase = TaskHeaderPhase::Shell;
421 }
422 SyntaxKind::Unknown if kind == SyntaxKind::Unknown => {}
423 _ => {}
424 },
425 TaskHeaderPhase::Shell => {}
426 }
427 }
428
429 if kind == SyntaxKind::Colon && phase.is_balanced() {
430 saw_colon = true;
431 }
432 advance(input);
433
434 if kind == SyntaxKind::Eof {
435 break;
436 }
437
438 if kind == SyntaxKind::Newline && !saw_colon {
439 if matches!(phase, TaskHeaderPhase::Params { depth } if depth > 0) {
440 expect_param_indent = true;
441 line_start = true;
442 continue;
443 }
444 if saw_parameter_list && phase.is_balanced() && !expect_guard_at {
445 continuation_header = true;
446 expect_clause_start = true;
447 line_start = true;
448 continue;
449 }
450 malformed |= !phase.is_balanced() || expect_guard_at;
451 break;
452 }
453
454 if kind == SyntaxKind::Newline && saw_colon {
455 malformed |= !phase.is_balanced() || expect_guard_at;
456 header_complete = true;
457 }
458
459 line_start = kind == SyntaxKind::Newline;
460 }
461
462 Ok(ParsedTopLevelItem::Task {
463 saw_colon,
464 malformed,
465 })
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
469enum TaskHeaderPhase {
470 BeforeTail,
471 Params { depth: usize },
472 Guard { depth: usize },
473 Dependencies { group_depth: usize, saw_group: bool },
474 Shell,
475}
476
477impl TaskHeaderPhase {
478 fn is_balanced(self) -> bool {
479 match self {
480 TaskHeaderPhase::BeforeTail | TaskHeaderPhase::Shell => true,
481 TaskHeaderPhase::Params { depth } | TaskHeaderPhase::Guard { depth } => depth == 0,
482 TaskHeaderPhase::Dependencies { group_depth, .. } => group_depth == 0,
483 }
484 }
485}
486
487fn parse_unexpected_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
488 any::<_, ErrMode<ContextError>>
489 .verify(|kind: &SyntaxKind| !is_trivia(*kind) && *kind != SyntaxKind::Eof)
490 .value(ParsedTopLevelItem::Unexpected)
491 .parse_next(input)
492}
493
494fn token_kind(input: &mut &[SyntaxKind], kind: SyntaxKind) -> ModalResult<SyntaxKind> {
495 any::<_, ErrMode<ContextError>>
496 .verify(move |candidate: &SyntaxKind| *candidate == kind)
497 .parse_next(input)
498}
499
500fn parse_error(code: &str, message: &str, range: TextRange) -> Diagnostic {
501 Diagnostic::new(
502 DiagnosticSeverity::Error,
503 DiagnosticCode::new(code),
504 message,
505 DiagnosticPhase::Parse,
506 normalize_range(range),
507 )
508}
509
510fn normalize_range(range: TextRange) -> TextRange {
511 if range.is_empty() {
512 TextRange::new(range.start(), range.start() + TextSize::from(1))
513 } else {
514 range
515 }
516}