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 continuation_indent = false;
282 let mut expect_param_indent = false;
283
284 while let Some(kind) = input.first().copied() {
285 if header_complete
286 && line_start
287 && (starts_top_level_item(kind)
288 || starts_indented_namespace_boundary(input)
289 || (in_braced_namespace && starts_indented_namespace_member(input)))
290 {
291 break;
292 }
293
294 if saw_colon
295 && !header_complete
296 && !matches!(kind, SyntaxKind::Whitespace | SyntaxKind::Newline)
297 {
298 malformed = true;
299 }
300
301 if !header_complete {
302 if expect_param_indent {
303 match kind {
304 SyntaxKind::Indent => expect_param_indent = false,
305 SyntaxKind::RParen => expect_param_indent = false,
306 _ => {
307 malformed = true;
308 break;
309 }
310 }
311 }
312
313 if kind == SyntaxKind::Comment {
314 malformed = true;
315 }
316
317 if continuation_header && expect_clause_start {
318 match kind {
319 SyntaxKind::Indent | SyntaxKind::Whitespace => {
320 continuation_indent = true;
321 }
322 SyntaxKind::Question
323 | SyntaxKind::Amp
324 | SyntaxKind::ShellKw
325 | SyntaxKind::ShellFallbackKw => {
326 malformed |= !continuation_indent;
327 expect_clause_start = false;
328 }
329 SyntaxKind::Colon => {
330 malformed |= continuation_indent;
331 expect_clause_start = false;
332 }
333 SyntaxKind::Newline => malformed = true,
334 _ => {
335 malformed = true;
336 expect_clause_start = false;
337 }
338 }
339 }
340
341 match &mut phase {
342 TaskHeaderPhase::BeforeTail => match kind {
343 SyntaxKind::LParen => {
344 saw_parameter_list = true;
345 phase = TaskHeaderPhase::Params { depth: 1 };
346 }
347 SyntaxKind::Question => {
348 phase = TaskHeaderPhase::Guard { depth: 0 };
349 expect_guard_at = true;
350 }
351 SyntaxKind::Amp => {
352 phase = TaskHeaderPhase::Dependencies {
353 group_depth: 0,
354 saw_group: false,
355 };
356 }
357 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
358 SyntaxKind::At if expect_guard_at => {
359 expect_guard_at = false;
360 }
361 _ => {
362 if expect_guard_at {
363 malformed = true;
364 expect_guard_at = false;
365 }
366 }
367 },
368 TaskHeaderPhase::Params { depth } => match kind {
369 SyntaxKind::LParen => *depth += 1,
370 SyntaxKind::RParen => {
371 if *depth == 0 {
372 malformed = true;
373 } else {
374 *depth -= 1;
375 if *depth == 0 {
376 phase = TaskHeaderPhase::BeforeTail;
377 }
378 }
379 }
380 _ => {}
381 },
382 TaskHeaderPhase::Guard { depth } => match kind {
383 SyntaxKind::LParen => *depth += 1,
384 SyntaxKind::RParen => {
385 if *depth > 0 {
386 *depth -= 1;
387 }
388 if *depth == 0 {
389 phase = TaskHeaderPhase::BeforeTail;
390 }
391 }
392 SyntaxKind::At if expect_guard_at => {
393 expect_guard_at = false;
394 }
395 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
396 _ => {
397 if expect_guard_at {
398 malformed = true;
399 expect_guard_at = false;
400 }
401 }
402 },
403 TaskHeaderPhase::Dependencies {
404 group_depth,
405 saw_group,
406 } => match kind {
407 SyntaxKind::LParen => {
408 if *group_depth > 0 {
409 malformed = true;
410 }
411 *group_depth += 1;
412 *saw_group = true;
413 }
414 SyntaxKind::RParen => {
415 if *group_depth == 0 {
416 malformed = true;
417 } else {
418 *group_depth -= 1;
419 }
420 }
421 SyntaxKind::Question | SyntaxKind::At => malformed = true,
422 SyntaxKind::ShellKw | SyntaxKind::ShellFallbackKw if *group_depth == 0 => {
423 phase = TaskHeaderPhase::Shell;
424 }
425 SyntaxKind::Unknown if kind == SyntaxKind::Unknown => {}
426 _ => {}
427 },
428 TaskHeaderPhase::Shell => {}
429 }
430 }
431
432 if kind == SyntaxKind::Colon && phase.is_balanced() {
433 saw_colon = true;
434 }
435 advance(input);
436
437 if kind == SyntaxKind::Eof {
438 break;
439 }
440
441 if kind == SyntaxKind::Newline && !saw_colon {
442 if matches!(phase, TaskHeaderPhase::Params { depth } if depth > 0) {
443 expect_param_indent = true;
444 line_start = true;
445 continue;
446 }
447 if saw_parameter_list && phase.is_balanced() && !expect_guard_at {
448 continuation_header = true;
449 expect_clause_start = true;
450 continuation_indent = false;
451 line_start = true;
452 continue;
453 }
454 malformed |= !phase.is_balanced() || expect_guard_at;
455 break;
456 }
457
458 if kind == SyntaxKind::Newline && saw_colon {
459 malformed |= !phase.is_balanced() || expect_guard_at;
460 header_complete = true;
461 }
462
463 line_start = kind == SyntaxKind::Newline;
464 }
465
466 Ok(ParsedTopLevelItem::Task {
467 saw_colon,
468 malformed,
469 })
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq)]
473enum TaskHeaderPhase {
474 BeforeTail,
475 Params { depth: usize },
476 Guard { depth: usize },
477 Dependencies { group_depth: usize, saw_group: bool },
478 Shell,
479}
480
481impl TaskHeaderPhase {
482 fn is_balanced(self) -> bool {
483 match self {
484 TaskHeaderPhase::BeforeTail | TaskHeaderPhase::Shell => true,
485 TaskHeaderPhase::Params { depth } | TaskHeaderPhase::Guard { depth } => depth == 0,
486 TaskHeaderPhase::Dependencies { group_depth, .. } => group_depth == 0,
487 }
488 }
489}
490
491fn parse_unexpected_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
492 any::<_, ErrMode<ContextError>>
493 .verify(|kind: &SyntaxKind| !is_trivia(*kind) && *kind != SyntaxKind::Eof)
494 .value(ParsedTopLevelItem::Unexpected)
495 .parse_next(input)
496}
497
498fn token_kind(input: &mut &[SyntaxKind], kind: SyntaxKind) -> ModalResult<SyntaxKind> {
499 any::<_, ErrMode<ContextError>>
500 .verify(move |candidate: &SyntaxKind| *candidate == kind)
501 .parse_next(input)
502}
503
504fn parse_error(code: &str, message: &str, range: TextRange) -> Diagnostic {
505 Diagnostic::new(
506 DiagnosticSeverity::Error,
507 DiagnosticCode::new(code),
508 message,
509 DiagnosticPhase::Parse,
510 normalize_range(range),
511 )
512}
513
514fn normalize_range(range: TextRange) -> TextRange {
515 if range.is_empty() {
516 TextRange::new(range.start(), range.start() + TextSize::from(1))
517 } else {
518 range
519 }
520}