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::MetadataComment => {
110 builder.push_node(SyntaxKind::MetadataComment, 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 group",
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 malformed,
131 missing_colon,
132 } => {
133 if malformed {
134 let (code, message) = if missing_colon {
135 ("parse.missing-task-colon", "missing ':' before command")
136 } else {
137 ("parse.malformed-task-header", "invalid task header")
138 };
139 diagnostics.push(parse_error(code, message, token.range));
140 builder.push_node(SyntaxKind::Error, token_slice);
141 continue;
142 }
143 builder.push_task(token_slice);
144 }
145 ParsedTopLevelItem::Unexpected => {
146 diagnostics.push(parse_error(
147 "parse.unexpected-token",
148 "unexpected text",
149 token.range,
150 ));
151 builder.push_node(SyntaxKind::Error, token_slice);
152 }
153 }
154 }
155
156 ParseResult {
157 root: builder.finish(),
158 diagnostics,
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163enum ParsedTopLevelItem {
164 Directive {
165 malformed: bool,
166 },
167 MetadataComment,
168 Namespace {
169 malformed: bool,
170 is_close: bool,
171 has_open_brace: bool,
172 },
173 Task {
174 malformed: bool,
175 missing_colon: bool,
176 },
177 Unexpected,
178}
179
180fn parse_top_level_item(
181 input: &mut &[SyntaxKind],
182 in_braced_namespace: bool,
183) -> ModalResult<ParsedTopLevelItem> {
184 alt((
185 parse_directive_item,
186 parse_metadata_item,
187 parse_namespace_item,
188 |input: &mut &[SyntaxKind]| parse_task_item(input, in_braced_namespace),
189 parse_unexpected_item,
190 ))
191 .parse_next(input)
192}
193
194fn parse_directive_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
195 token_kind(input, SyntaxKind::Bang)?;
196 let malformed = !line_has_non_trivia(input) || line_contains_kind(input, SyntaxKind::Comment);
197 consume_line(input);
198 Ok(ParsedTopLevelItem::Directive { malformed })
199}
200
201fn parse_metadata_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
202 token_kind(input, SyntaxKind::LBracket)?;
203 token_kind(input, SyntaxKind::Ident)?;
204 token_kind(input, SyntaxKind::RBracket)?;
205
206 let mut rest = *input;
207 while matches!(rest.first(), Some(SyntaxKind::Whitespace)) {
208 advance(&mut rest);
209 }
210 let starts_namespace_brace =
211 rest.first() == Some(&SyntaxKind::LBrace) && rest.get(1) != Some(&SyntaxKind::LBrace);
212 let has_text = !matches!(rest.first(), Some(SyntaxKind::Newline | SyntaxKind::Eof))
213 && !starts_namespace_brace
214 && rest.first() != Some(&SyntaxKind::Comment);
215 if !has_text {
216 return Err(ErrMode::Backtrack(ContextError::new()));
217 }
218
219 consume_line(input);
220 Ok(ParsedTopLevelItem::MetadataComment)
221}
222
223fn parse_namespace_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
224 if input.first() == Some(&SyntaxKind::RBrace) {
225 advance(input);
226 let malformed =
227 line_has_non_trivia(input) || line_contains_kind(input, SyntaxKind::Comment);
228 consume_line(input);
229 return Ok(ParsedTopLevelItem::Namespace {
230 malformed,
231 is_close: true,
232 has_open_brace: false,
233 });
234 }
235
236 if input.first() == Some(&SyntaxKind::GroupKw)
237 && input.get(1) == Some(&SyntaxKind::Whitespace)
238 && input.get(2) == Some(&SyntaxKind::Ident)
239 {
240 let has_open_brace = line_contains_kind(input, SyntaxKind::LBrace);
241 let malformed = group_open_is_malformed(input);
242 consume_line(input);
243 return Ok(ParsedTopLevelItem::Namespace {
244 malformed,
245 is_close: false,
246 has_open_brace,
247 });
248 }
249
250 Err(ErrMode::Backtrack(ContextError::new()))
251}
252
253fn group_open_is_malformed(input: &[SyntaxKind]) -> bool {
254 let line = input
255 .iter()
256 .copied()
257 .take_while(|kind| !matches!(kind, SyntaxKind::Newline | SyntaxKind::Eof))
258 .collect::<Vec<_>>();
259 let mut index = 0;
260 if line.get(index) != Some(&SyntaxKind::GroupKw) {
261 return true;
262 }
263 index += 1;
264 while line.get(index) == Some(&SyntaxKind::Whitespace) {
265 index += 1;
266 }
267 if line.get(index) != Some(&SyntaxKind::Ident) {
268 return true;
269 }
270 index += 1;
271 while line.get(index) == Some(&SyntaxKind::Whitespace) {
272 index += 1;
273 }
274 if line.get(index) != Some(&SyntaxKind::LBrace) {
275 return true;
276 }
277 index += 1;
278 while line.get(index) == Some(&SyntaxKind::Whitespace) {
279 index += 1;
280 }
281 index != line.len()
282}
283
284fn parse_task_item(
285 input: &mut &[SyntaxKind],
286 in_braced_namespace: bool,
287) -> ModalResult<ParsedTopLevelItem> {
288 token_kind(input, SyntaxKind::Ident)?;
289 let mut saw_colon = false;
290 let mut header_complete = false;
291 let mut line_start = false;
292 let mut malformed = false;
293 let mut missing_colon = false;
294 let mut expect_guard_at = false;
295 let mut phase = TaskHeaderPhase::BeforeTail;
296 let mut saw_parameter_list = false;
297 let mut continuation_header = false;
298 let mut expect_clause_start = false;
299 let mut expect_param_indent = false;
300
301 while let Some(kind) = input.first().copied() {
302 if line_start
303 && (header_complete || (continuation_header && !saw_colon))
304 && (starts_top_level_item(kind)
305 || starts_indented_namespace_boundary(input)
306 || (in_braced_namespace && starts_indented_namespace_member(input)))
307 {
308 break;
309 }
310
311 if continuation_header && !saw_colon && line_start && kind == SyntaxKind::Newline {
315 break;
316 }
317
318 if saw_colon
319 && !header_complete
320 && !matches!(
321 kind,
322 SyntaxKind::Whitespace | SyntaxKind::Newline | SyntaxKind::Eof
323 )
324 {
325 malformed = true;
326 }
327
328 if !header_complete {
329 if expect_param_indent {
330 match kind {
331 SyntaxKind::Indent => expect_param_indent = false,
332 SyntaxKind::RParen => expect_param_indent = false,
333 _ => {
334 malformed = true;
335 break;
336 }
337 }
338 }
339
340 if kind == SyntaxKind::Comment {
341 malformed = true;
342 }
343
344 if continuation_header && expect_clause_start {
345 match kind {
346 SyntaxKind::Indent | SyntaxKind::Whitespace => {}
349 SyntaxKind::Question
350 | SyntaxKind::Amp
351 | SyntaxKind::ShellKw
352 | SyntaxKind::ShellFallbackKw => {
353 expect_clause_start = false;
354 }
355 SyntaxKind::Colon => {
356 expect_clause_start = false;
357 }
358 SyntaxKind::Newline => malformed = true,
359 SyntaxKind::Eof => {}
360 _ => {
361 malformed = true;
362 missing_colon = true;
363 expect_clause_start = false;
364 }
365 }
366 }
367
368 match &mut phase {
369 TaskHeaderPhase::BeforeTail => match kind {
370 SyntaxKind::LParen => {
371 saw_parameter_list = true;
372 phase = TaskHeaderPhase::Params { depth: 1 };
373 }
374 SyntaxKind::Question => {
375 phase = TaskHeaderPhase::Condition { depth: 0 };
376 expect_guard_at = true;
377 }
378 SyntaxKind::Amp => {
379 phase = TaskHeaderPhase::Dependencies {
380 group_depth: 0,
381 call_args: None,
382 previous: Some(SyntaxKind::Amp),
383 };
384 }
385 SyntaxKind::ShellKw | SyntaxKind::ShellFallbackKw => {
386 phase = TaskHeaderPhase::Shell;
387 }
388 SyntaxKind::Whitespace
389 | SyntaxKind::Indent
390 | SyntaxKind::Newline
391 | SyntaxKind::Eof => {}
392 SyntaxKind::At if expect_guard_at => {
393 expect_guard_at = false;
394 }
395 SyntaxKind::Colon => {}
396 _ => {
397 malformed = true;
398 if expect_guard_at {
399 expect_guard_at = false;
400 }
401 }
402 },
403 TaskHeaderPhase::Params { depth } => match kind {
404 SyntaxKind::LParen => *depth += 1,
405 SyntaxKind::RParen => {
406 if *depth == 0 {
407 malformed = true;
408 } else {
409 *depth -= 1;
410 if *depth == 0 {
411 phase = TaskHeaderPhase::BeforeTail;
412 }
413 }
414 }
415 _ => {}
416 },
417 TaskHeaderPhase::Condition { depth } => match kind {
418 SyntaxKind::LParen => *depth += 1,
419 SyntaxKind::RParen => {
420 if *depth > 0 {
421 *depth -= 1;
422 }
423 if *depth == 0 {
424 phase = TaskHeaderPhase::BeforeTail;
425 }
426 }
427 SyntaxKind::At if expect_guard_at => {
428 expect_guard_at = false;
429 }
430 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
431 _ => {
432 if expect_guard_at {
433 malformed = true;
434 expect_guard_at = false;
435 }
436 }
437 },
438 TaskHeaderPhase::Dependencies {
439 group_depth,
440 call_args,
441 previous,
442 } => match kind {
443 SyntaxKind::Whitespace | SyntaxKind::Indent | SyntaxKind::Newline => {}
444 SyntaxKind::LParen => {
445 if call_args.is_some() {
446 malformed = true;
447 } else if *previous == Some(SyntaxKind::Ident) {
448 *call_args = Some(DependencyArgState::FirstOrEnd);
449 } else {
450 if *group_depth > 0 {
451 malformed = true;
452 }
453 *group_depth += 1;
454 }
455 *previous = Some(kind);
456 }
457 SyntaxKind::RParen => {
458 if let Some(argument_state) = call_args {
459 if *argument_state == DependencyArgState::Value {
460 malformed = true;
461 }
462 *call_args = None;
463 } else if *group_depth > 0 {
464 *group_depth -= 1;
465 } else {
466 malformed = true;
467 }
468 *previous = Some(kind);
469 }
470 SyntaxKind::String => {
471 match call_args {
472 Some(DependencyArgState::FirstOrEnd | DependencyArgState::Value) => {
473 *call_args = Some(DependencyArgState::CommaOrEnd);
474 }
475 _ => malformed = true,
476 }
477 *previous = Some(kind);
478 }
479 SyntaxKind::Comma => {
480 match call_args {
481 Some(DependencyArgState::CommaOrEnd) => {
482 *call_args = Some(DependencyArgState::Value);
483 }
484 Some(_) => malformed = true,
485 None if *group_depth == 0 => malformed = true,
486 None => {}
487 }
488 *previous = Some(kind);
489 }
490 SyntaxKind::Question | SyntaxKind::At => malformed = true,
491 SyntaxKind::ShellKw | SyntaxKind::ShellFallbackKw
492 if *group_depth == 0 && call_args.is_none() =>
493 {
494 phase = TaskHeaderPhase::Shell;
495 }
496 _ => {
497 if call_args.is_some() {
498 malformed = true;
499 }
500 *previous = Some(kind);
501 }
502 },
503 TaskHeaderPhase::Shell => {
504 if kind == SyntaxKind::Question {
505 malformed = true;
506 }
507 }
508 }
509 }
510
511 if kind == SyntaxKind::Colon && phase.is_balanced() {
512 saw_colon = true;
513 }
514 advance(input);
515
516 if kind == SyntaxKind::Eof {
517 malformed |= !phase.is_balanced() || expect_guard_at;
518 break;
519 }
520
521 if kind == SyntaxKind::Newline && !saw_colon {
522 if matches!(phase, TaskHeaderPhase::Params { depth } if depth > 0) {
523 expect_param_indent = true;
524 line_start = true;
525 continue;
526 }
527 if saw_parameter_list && phase.is_balanced() && !expect_guard_at {
528 continuation_header = true;
529 expect_clause_start = true;
530 line_start = true;
531 continue;
532 }
533 malformed |= !phase.is_balanced() || expect_guard_at;
534 break;
535 }
536
537 if kind == SyntaxKind::Newline && saw_colon {
538 malformed |= !phase.is_balanced() || expect_guard_at;
539 header_complete = true;
540 }
541
542 line_start = kind == SyntaxKind::Newline;
543 }
544
545 Ok(ParsedTopLevelItem::Task {
546 malformed,
547 missing_colon,
548 })
549}
550
551#[derive(Debug, Clone, Copy, PartialEq, Eq)]
552enum TaskHeaderPhase {
553 BeforeTail,
554 Params {
555 depth: usize,
556 },
557 Condition {
558 depth: usize,
559 },
560 Dependencies {
561 group_depth: usize,
562 call_args: Option<DependencyArgState>,
563 previous: Option<SyntaxKind>,
564 },
565 Shell,
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
569enum DependencyArgState {
570 FirstOrEnd,
571 Value,
572 CommaOrEnd,
573}
574
575impl TaskHeaderPhase {
576 fn is_balanced(self) -> bool {
577 match self {
578 TaskHeaderPhase::BeforeTail | TaskHeaderPhase::Shell => true,
579 TaskHeaderPhase::Params { depth } | TaskHeaderPhase::Condition { depth } => depth == 0,
580 TaskHeaderPhase::Dependencies {
581 group_depth,
582 call_args,
583 ..
584 } => group_depth == 0 && call_args.is_none(),
585 }
586 }
587}
588
589fn parse_unexpected_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
590 any::<_, ErrMode<ContextError>>
591 .verify(|kind: &SyntaxKind| !is_trivia(*kind) && *kind != SyntaxKind::Eof)
592 .value(ParsedTopLevelItem::Unexpected)
593 .parse_next(input)
594}
595
596fn token_kind(input: &mut &[SyntaxKind], kind: SyntaxKind) -> ModalResult<SyntaxKind> {
597 any::<_, ErrMode<ContextError>>
598 .verify(move |candidate: &SyntaxKind| *candidate == kind)
599 .parse_next(input)
600}
601
602fn parse_error(code: &str, message: &str, range: TextRange) -> Diagnostic {
603 Diagnostic::new(
604 DiagnosticSeverity::Error,
605 DiagnosticCode::new(code),
606 message,
607 DiagnosticPhase::Parse,
608 normalize_range(range),
609 )
610}
611
612fn normalize_range(range: TextRange) -> TextRange {
613 if range.is_empty() {
614 TextRange::new(range.start(), range.start() + TextSize::from(1))
615 } else {
616 range
617 }
618}