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