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::{advance, consume_line, starts_top_level_item};
14use crate::trivia::{is_trivia, line_contains_kind, line_has_non_trivia};
15use crate::{LexToken, SyntaxKind, lex};
16
17#[derive(Debug, Clone)]
18pub struct ParseResult {
19 pub root: SyntaxNode,
20 diagnostics: Vec<Diagnostic>,
21}
22
23impl ParseResult {
24 pub fn document(&self) -> DocumentNode {
32 DocumentNode::cast(self.root.clone()).expect("parse root must always be a document node")
33 }
34}
35
36pub trait ParseResultExt {
38 fn root_children(&self) -> SyntaxNodeChildren<crate::cst::OnlyLanguage>;
40
41 fn diagnostics(&self) -> &[Diagnostic];
43}
44
45impl ParseResultExt for ParseResult {
46 fn root_children(&self) -> SyntaxNodeChildren<crate::cst::OnlyLanguage> {
47 self.root.children()
48 }
49
50 fn diagnostics(&self) -> &[Diagnostic] {
51 &self.diagnostics
52 }
53}
54
55pub fn parse(source: &str) -> ParseResult {
63 let tokens = lex(source);
64 parse_tokens(&tokens)
65}
66
67pub(crate) fn parse_tokens(tokens: &[LexToken]) -> ParseResult {
68 let mut builder = ParseTreeBuilder::new();
69 let mut diagnostics = Vec::new();
70 let kinds = tokens.iter().map(|token| token.kind).collect::<Vec<_>>();
71 let mut cursor = TokenCursor::new(tokens, &kinds);
72
73 loop {
74 let trivia = cursor.skip_trivia();
75 builder.push_tokens(trivia);
76
77 let Some(token) = cursor.current() else {
78 break;
79 };
80 if token.kind == SyntaxKind::Eof {
81 break;
82 }
83
84 let mut input = cursor.remaining();
85 let (item, consumed) = parse_top_level_item
86 .with_taken()
87 .parse_next(&mut input)
88 .expect("top-level parser should always consume a non-EOF item");
89 let token_slice = cursor.consume(consumed.len());
90
91 match item {
92 ParsedTopLevelItem::Directive { malformed } => {
93 if malformed {
94 diagnostics.push(parse_error(
95 "parse.malformed-directive",
96 "invalid directive",
97 token.range,
98 ));
99 builder.push_node(SyntaxKind::Error, token_slice);
100 continue;
101 }
102 builder.push_node(SyntaxKind::Directive, token_slice);
103 }
104 ParsedTopLevelItem::DocComment => {
105 builder.push_node(SyntaxKind::DocComment, token_slice);
106 }
107 ParsedTopLevelItem::Namespace { malformed } => {
108 if malformed {
109 diagnostics.push(parse_error(
110 "parse.malformed-namespace-header",
111 "invalid namespace",
112 token.range,
113 ));
114 builder.push_node(SyntaxKind::Error, token_slice);
115 continue;
116 }
117 builder.push_node(SyntaxKind::NamespaceBlock, token_slice);
118 }
119 ParsedTopLevelItem::Task {
120 saw_colon,
121 malformed,
122 } => {
123 if !saw_colon || malformed {
124 diagnostics.push(parse_error(
125 "parse.malformed-task-header",
126 "invalid task header",
127 token.range,
128 ));
129 builder.push_node(SyntaxKind::Error, token_slice);
130 continue;
131 }
132 builder.push_node(SyntaxKind::TaskDecl, token_slice);
133 }
134 ParsedTopLevelItem::Unexpected => {
135 diagnostics.push(parse_error(
136 "parse.unexpected-token",
137 "unexpected text",
138 token.range,
139 ));
140 builder.push_node(SyntaxKind::Error, token_slice);
141 }
142 }
143 }
144
145 ParseResult {
146 root: builder.finish(),
147 diagnostics,
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152enum ParsedTopLevelItem {
153 Directive { malformed: bool },
154 DocComment,
155 Namespace { malformed: bool },
156 Task { saw_colon: bool, malformed: bool },
157 Unexpected,
158}
159
160fn parse_top_level_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
161 alt((
162 parse_directive_item,
163 parse_doc_comment_item,
164 parse_namespace_item,
165 parse_task_item,
166 parse_unexpected_item,
167 ))
168 .parse_next(input)
169}
170
171fn parse_directive_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
172 token_kind(input, SyntaxKind::Bang)?;
173 let malformed = !line_has_non_trivia(input) || line_contains_kind(input, SyntaxKind::Comment);
174 consume_line(input);
175 Ok(ParsedTopLevelItem::Directive { malformed })
176}
177
178fn parse_doc_comment_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
179 token_kind(input, SyntaxKind::Percent)?;
180 consume_line(input);
181 Ok(ParsedTopLevelItem::DocComment)
182}
183
184fn parse_namespace_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
185 token_kind(input, SyntaxKind::LBracket)?;
186 let malformed = !line_contains_kind(input, SyntaxKind::RBracket)
187 || line_contains_kind(input, SyntaxKind::Comment);
188 consume_line(input);
189 Ok(ParsedTopLevelItem::Namespace { malformed })
190}
191
192fn parse_task_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
193 token_kind(input, SyntaxKind::Ident)?;
194 let mut saw_colon = false;
195 let mut header_complete = false;
196 let mut line_start = false;
197 let mut malformed = false;
198 let mut expect_guard_at = false;
199 let mut phase = TaskHeaderPhase::BeforeTail;
200
201 while let Some(kind) = input.first().copied() {
202 if header_complete && line_start && starts_top_level_item(kind) {
203 break;
204 }
205
206 if !header_complete {
207 if kind == SyntaxKind::Comment {
208 malformed = true;
209 }
210
211 match &mut phase {
212 TaskHeaderPhase::BeforeTail => match kind {
213 SyntaxKind::LParen => {
214 phase = TaskHeaderPhase::Params { depth: 1 };
215 }
216 SyntaxKind::Question => {
217 phase = TaskHeaderPhase::Guard { depth: 0 };
218 expect_guard_at = true;
219 }
220 SyntaxKind::Amp => {
221 phase = TaskHeaderPhase::Dependencies {
222 group_depth: 0,
223 saw_group: false,
224 };
225 }
226 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
227 SyntaxKind::At if expect_guard_at => {
228 expect_guard_at = false;
229 }
230 _ => {
231 if expect_guard_at {
232 malformed = true;
233 expect_guard_at = false;
234 }
235 }
236 },
237 TaskHeaderPhase::Params { depth } => match kind {
238 SyntaxKind::LParen => *depth += 1,
239 SyntaxKind::RParen => {
240 if *depth == 0 {
241 malformed = true;
242 } else {
243 *depth -= 1;
244 if *depth == 0 {
245 phase = TaskHeaderPhase::BeforeTail;
246 }
247 }
248 }
249 _ => {}
250 },
251 TaskHeaderPhase::Guard { depth } => match kind {
252 SyntaxKind::LParen => *depth += 1,
253 SyntaxKind::RParen => {
254 if *depth > 0 {
255 *depth -= 1;
256 }
257 if *depth == 0 {
258 phase = TaskHeaderPhase::BeforeTail;
259 }
260 }
261 SyntaxKind::At if expect_guard_at => {
262 expect_guard_at = false;
263 }
264 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
265 _ => {
266 if expect_guard_at {
267 malformed = true;
268 expect_guard_at = false;
269 }
270 }
271 },
272 TaskHeaderPhase::Dependencies {
273 group_depth,
274 saw_group,
275 } => match kind {
276 SyntaxKind::LParen => {
277 if *group_depth > 0 {
278 malformed = true;
279 }
280 *group_depth += 1;
281 *saw_group = true;
282 }
283 SyntaxKind::RParen => {
284 if *group_depth == 0 {
285 malformed = true;
286 } else {
287 *group_depth -= 1;
288 }
289 }
290 SyntaxKind::Question | SyntaxKind::At => malformed = true,
291 SyntaxKind::ShellKw | SyntaxKind::ShellFallbackKw if *group_depth == 0 => {
292 phase = TaskHeaderPhase::Shell;
293 }
294 SyntaxKind::Unknown if kind == SyntaxKind::Unknown => {}
295 _ => {}
296 },
297 TaskHeaderPhase::Shell => {}
298 }
299 }
300
301 if kind == SyntaxKind::Colon {
302 saw_colon = true;
303 }
304 advance(input);
305
306 if kind == SyntaxKind::Eof {
307 break;
308 }
309
310 if kind == SyntaxKind::Newline && !saw_colon {
311 malformed |= !phase.is_balanced() || expect_guard_at;
312 break;
313 }
314
315 if kind == SyntaxKind::Newline && saw_colon {
316 malformed |= !phase.is_balanced() || expect_guard_at;
317 header_complete = true;
318 }
319
320 line_start = kind == SyntaxKind::Newline;
321 }
322
323 Ok(ParsedTopLevelItem::Task {
324 saw_colon,
325 malformed,
326 })
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330enum TaskHeaderPhase {
331 BeforeTail,
332 Params { depth: usize },
333 Guard { depth: usize },
334 Dependencies { group_depth: usize, saw_group: bool },
335 Shell,
336}
337
338impl TaskHeaderPhase {
339 fn is_balanced(self) -> bool {
340 match self {
341 TaskHeaderPhase::BeforeTail | TaskHeaderPhase::Shell => true,
342 TaskHeaderPhase::Params { depth } | TaskHeaderPhase::Guard { depth } => depth == 0,
343 TaskHeaderPhase::Dependencies { group_depth, .. } => group_depth == 0,
344 }
345 }
346}
347
348fn parse_unexpected_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
349 any::<_, ErrMode<ContextError>>
350 .verify(|kind: &SyntaxKind| !is_trivia(*kind) && *kind != SyntaxKind::Eof)
351 .value(ParsedTopLevelItem::Unexpected)
352 .parse_next(input)
353}
354
355fn token_kind(input: &mut &[SyntaxKind], kind: SyntaxKind) -> ModalResult<SyntaxKind> {
356 any::<_, ErrMode<ContextError>>
357 .verify(move |candidate: &SyntaxKind| *candidate == kind)
358 .parse_next(input)
359}
360
361fn parse_error(code: &str, message: &str, range: TextRange) -> Diagnostic {
362 Diagnostic::new(
363 DiagnosticSeverity::Error,
364 DiagnosticCode::new(code),
365 message,
366 DiagnosticPhase::Parse,
367 normalize_range(range),
368 )
369}
370
371fn normalize_range(range: TextRange) -> TextRange {
372 if range.is_empty() {
373 TextRange::new(range.start(), range.start() + TextSize::from(1))
374 } else {
375 range
376 }
377}