shuck_parser/parser/result.rs
1use shuck_ast::{File, Span};
2
3use crate::error::Error;
4
5/// Overall outcome of a parse attempt.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ParseStatus {
8 /// The parse completed without recovery diagnostics.
9 Clean,
10 /// The parse completed, but required recovery diagnostics.
11 Recovered,
12 /// The parse failed with a terminal error.
13 Fatal,
14}
15
16/// One branch separator recognized inside a zsh `case` pattern group.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct ZshCaseGroupPart {
19 /// Index of the owning pattern part within the parsed pattern.
20 pub pattern_part_index: usize,
21 /// Source span covering the separator syntax.
22 pub span: Span,
23}
24
25/// Additional parser-owned facts that are useful to downstream consumers.
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
27pub struct SyntaxFacts {
28 /// Spans of zsh brace-style `if` bodies.
29 pub zsh_brace_if_spans: Vec<Span>,
30 /// Spans of zsh `always` clauses.
31 pub zsh_always_spans: Vec<Span>,
32 /// Pattern-group separators collected from zsh `case` items.
33 pub zsh_case_group_parts: Vec<ZshCaseGroupPart>,
34}
35
36/// A parser diagnostic emitted while recovering from invalid input.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ParseDiagnostic {
39 /// Human-readable diagnostic message.
40 pub message: String,
41 /// Source span associated with the diagnostic.
42 pub span: Span,
43}
44
45/// The result of parsing a script, including any recovery diagnostics and
46/// syntax facts collected along the way.
47#[derive(Debug, Clone)]
48pub struct ParseResult {
49 /// Parsed syntax tree for the file.
50 pub file: File,
51 /// Recovery diagnostics emitted while producing the AST.
52 pub diagnostics: Vec<ParseDiagnostic>,
53 /// High-level parse status.
54 pub status: ParseStatus,
55 /// Terminal parse error, when recovery could not continue.
56 pub terminal_error: Option<Error>,
57 /// Additional syntax facts collected during parsing.
58 pub syntax_facts: SyntaxFacts,
59}
60
61impl ParseResult {
62 /// Returns `true` when the parse completed without recovery diagnostics.
63 pub fn is_ok(&self) -> bool {
64 self.status == ParseStatus::Clean
65 }
66
67 /// Returns `true` when the parse produced recovery diagnostics or a terminal error.
68 pub fn is_err(&self) -> bool {
69 !self.is_ok()
70 }
71
72 /// Convert this result into a strict parse error.
73 ///
74 /// If recovery diagnostics exist but no terminal error was recorded, the first recovery
75 /// diagnostic is converted into an [`Error`].
76 pub fn strict_error(&self) -> Error {
77 self.terminal_error.clone().unwrap_or_else(|| {
78 let Some(diagnostic) = self.diagnostics.first() else {
79 panic!("non-clean parse result should include a diagnostic or terminal error");
80 };
81 Error::parse_at(
82 diagnostic.message.clone(),
83 diagnostic.span.start.line,
84 diagnostic.span.start.column,
85 )
86 })
87 }
88
89 /// Return the parse result when it is clean, otherwise panic with the strict error.
90 pub fn unwrap(self) -> Self {
91 if self.is_ok() {
92 self
93 } else {
94 panic!(
95 "called `ParseResult::unwrap()` on a non-clean parse: {}",
96 self.strict_error()
97 )
98 }
99 }
100
101 /// Return the parse result when it is clean, otherwise panic with `message`.
102 pub fn expect(self, message: &str) -> Self {
103 if self.is_ok() {
104 self
105 } else {
106 panic!("{message}: {}", self.strict_error())
107 }
108 }
109
110 /// Return the strict parse error when the result is not clean, otherwise panic.
111 pub fn unwrap_err(self) -> Error {
112 if self.is_err() {
113 self.strict_error()
114 } else {
115 panic!("called `ParseResult::unwrap_err()` on a clean parse")
116 }
117 }
118
119 /// Return the strict parse error when the result is not clean, otherwise panic with
120 /// `message`.
121 pub fn expect_err(self, message: &str) -> Error {
122 if self.is_err() {
123 self.strict_error()
124 } else {
125 panic!("{message}")
126 }
127 }
128}