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 "malformed 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 "malformed namespace header",
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 "malformed 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 top-level token",
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);
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 consume_line(input);
188 Ok(ParsedTopLevelItem::Namespace { malformed })
189}
190
191fn parse_task_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
192 token_kind(input, SyntaxKind::Ident)?;
193 let mut saw_colon = false;
194 let mut header_complete = false;
195 let mut line_start = false;
196 let mut malformed = false;
197 let mut expect_guard_at = false;
198 let mut phase = TaskHeaderPhase::BeforeTail;
199
200 while let Some(kind) = input.first().copied() {
201 if header_complete && line_start && starts_top_level_item(kind) {
202 break;
203 }
204
205 if !header_complete {
206 match &mut phase {
207 TaskHeaderPhase::BeforeTail => match kind {
208 SyntaxKind::LParen => {
209 phase = TaskHeaderPhase::Params { depth: 1 };
210 }
211 SyntaxKind::Question => {
212 phase = TaskHeaderPhase::Guard { depth: 0 };
213 expect_guard_at = true;
214 }
215 SyntaxKind::Amp => {
216 phase = TaskHeaderPhase::Dependencies {
217 group_depth: 0,
218 saw_group: false,
219 };
220 }
221 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
222 SyntaxKind::At if expect_guard_at => {
223 expect_guard_at = false;
224 }
225 _ => {
226 if expect_guard_at {
227 malformed = true;
228 expect_guard_at = false;
229 }
230 }
231 },
232 TaskHeaderPhase::Params { depth } => match kind {
233 SyntaxKind::LParen => *depth += 1,
234 SyntaxKind::RParen => {
235 if *depth == 0 {
236 malformed = true;
237 } else {
238 *depth -= 1;
239 if *depth == 0 {
240 phase = TaskHeaderPhase::BeforeTail;
241 }
242 }
243 }
244 _ => {}
245 },
246 TaskHeaderPhase::Guard { depth } => match kind {
247 SyntaxKind::LParen => *depth += 1,
248 SyntaxKind::RParen => {
249 if *depth > 0 {
250 *depth -= 1;
251 }
252 if *depth == 0 {
253 phase = TaskHeaderPhase::BeforeTail;
254 }
255 }
256 SyntaxKind::At if expect_guard_at => {
257 expect_guard_at = false;
258 }
259 SyntaxKind::Whitespace | SyntaxKind::Indent => {}
260 _ => {
261 if expect_guard_at {
262 malformed = true;
263 expect_guard_at = false;
264 }
265 }
266 },
267 TaskHeaderPhase::Dependencies {
268 group_depth,
269 saw_group,
270 } => match kind {
271 SyntaxKind::LParen => {
272 if *group_depth > 0 {
273 malformed = true;
274 }
275 *group_depth += 1;
276 *saw_group = true;
277 }
278 SyntaxKind::RParen => {
279 if *group_depth == 0 {
280 malformed = true;
281 } else {
282 *group_depth -= 1;
283 }
284 }
285 SyntaxKind::Question | SyntaxKind::At => malformed = true,
286 SyntaxKind::ShellKw | SyntaxKind::ShellFallbackKw if *group_depth == 0 => {
287 phase = TaskHeaderPhase::Shell;
288 }
289 SyntaxKind::Unknown if kind == SyntaxKind::Unknown => {}
290 _ => {}
291 },
292 TaskHeaderPhase::Shell => {}
293 }
294 }
295
296 if kind == SyntaxKind::Colon {
297 saw_colon = true;
298 }
299 advance(input);
300
301 if kind == SyntaxKind::Eof {
302 break;
303 }
304
305 if kind == SyntaxKind::Newline && !saw_colon {
306 malformed |= !phase.is_balanced() || expect_guard_at;
307 break;
308 }
309
310 if kind == SyntaxKind::Newline && saw_colon {
311 malformed |= !phase.is_balanced() || expect_guard_at;
312 header_complete = true;
313 }
314
315 line_start = kind == SyntaxKind::Newline;
316 }
317
318 Ok(ParsedTopLevelItem::Task {
319 saw_colon,
320 malformed,
321 })
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325enum TaskHeaderPhase {
326 BeforeTail,
327 Params { depth: usize },
328 Guard { depth: usize },
329 Dependencies { group_depth: usize, saw_group: bool },
330 Shell,
331}
332
333impl TaskHeaderPhase {
334 fn is_balanced(self) -> bool {
335 match self {
336 TaskHeaderPhase::BeforeTail | TaskHeaderPhase::Shell => true,
337 TaskHeaderPhase::Params { depth } | TaskHeaderPhase::Guard { depth } => depth == 0,
338 TaskHeaderPhase::Dependencies { group_depth, .. } => group_depth == 0,
339 }
340 }
341}
342
343fn parse_unexpected_item(input: &mut &[SyntaxKind]) -> ModalResult<ParsedTopLevelItem> {
344 any::<_, ErrMode<ContextError>>
345 .verify(|kind: &SyntaxKind| !is_trivia(*kind) && *kind != SyntaxKind::Eof)
346 .value(ParsedTopLevelItem::Unexpected)
347 .parse_next(input)
348}
349
350fn token_kind(input: &mut &[SyntaxKind], kind: SyntaxKind) -> ModalResult<SyntaxKind> {
351 any::<_, ErrMode<ContextError>>
352 .verify(move |candidate: &SyntaxKind| *candidate == kind)
353 .parse_next(input)
354}
355
356fn parse_error(code: &str, message: &str, range: TextRange) -> Diagnostic {
357 Diagnostic::new(
358 DiagnosticSeverity::Error,
359 DiagnosticCode::new(code),
360 message,
361 DiagnosticPhase::Parse,
362 normalize_range(range),
363 )
364}
365
366fn normalize_range(range: TextRange) -> TextRange {
367 if range.is_empty() {
368 TextRange::new(range.start(), range.start() + TextSize::from(1))
369 } else {
370 range
371 }
372}