parse_dockerfile/
error.rs1use core::{fmt, marker::PhantomData, ops::Range, str};
4use std::borrow::Cow;
5
6use super::ParseIter;
7
8pub(crate) type Result<T, E = Error> = core::result::Result<T, E>;
9
10pub struct Error(Box<ErrorInner>, PhantomData<Box<dyn Send + Sync>>);
14
15impl Error {
16 #[must_use]
18 pub fn line(&self) -> usize {
19 self.0.line
20 }
21 #[must_use]
23 pub fn column(&self) -> usize {
24 self.0.column
25 }
26}
27
28#[derive(Debug)]
29struct ErrorInner {
30 msg: Cow<'static, str>,
31 line: usize,
32 column: usize,
33}
34
35#[cfg_attr(test, derive(Debug))]
36pub(crate) enum ErrorKind {
37 Expected(&'static str, usize),
38 ExpectedOwned(String, usize),
39 AtLeastOneArgument { instruction_start: usize },
40 AtLeastTwoArguments { instruction_start: usize },
41 ExactlyOneArgument { instruction_start: usize },
42 UnknownInstruction { instruction_start: usize },
43 InvalidEscape { escape_start: usize },
44 DuplicateName { first: Range<usize>, second: Range<usize> },
45 NoStages,
46 Json { arguments_start: usize },
47}
48
49impl ErrorKind {
50 #[cold]
51 #[inline(never)]
52 pub(crate) fn into_error(self, p: &ParseIter<'_>) -> Error {
53 let msg = match self {
54 Self::Expected(msg, ..) => format!("expected {msg}").into(),
55 Self::ExpectedOwned(ref msg, ..) => format!("expected {msg}").into(),
56 Self::AtLeastOneArgument { instruction_start: pos }
57 | Self::AtLeastTwoArguments { instruction_start: pos }
58 | Self::ExactlyOneArgument { instruction_start: pos }
59 | Self::UnknownInstruction { instruction_start: pos }
60 | Self::DuplicateName { first: Range { start: pos, .. }, .. } => {
61 let mut s = &p.text.as_bytes()[pos..];
62 let word =
63 super::collect_non_whitespace_unescaped(&mut s, p.text, p.escape_byte).value;
64 match self {
65 Self::AtLeastOneArgument { .. } => {
66 format!("{word} instruction requires at least one argument").into()
67 }
68 Self::AtLeastTwoArguments { .. } => {
69 format!("{word} instruction requires at least two arguments").into()
70 }
71 Self::ExactlyOneArgument { .. } => {
72 format!("{word} instruction requires exactly one argument").into()
73 }
74 Self::UnknownInstruction { .. } => {
75 format!("unknown instruction '{word}'").into()
76 }
77 Self::DuplicateName { .. } => format!("duplicate name '{word}'").into(),
78 _ => unreachable!(),
79 }
80 }
81 Self::NoStages => "expected at least one FROM instruction".into(),
82 Self::Json { .. } => "invalid JSON".into(),
83 Self::InvalidEscape { escape_start } => {
84 let mut s = &p.text.as_bytes()[escape_start..];
85 super::skip_non_whitespace_no_escape(&mut s);
86 let escape = &p.text[escape_start..p.text.len() - s.len()];
87 format!("invalid escape '{escape}'").into()
88 }
89 };
90 let (line, column) = match self {
91 Self::Expected(_, pos)
92 | Self::ExpectedOwned(_, pos)
93 | Self::AtLeastOneArgument { instruction_start: pos }
94 | Self::AtLeastTwoArguments { instruction_start: pos }
95 | Self::ExactlyOneArgument { instruction_start: pos }
96 | Self::UnknownInstruction { instruction_start: pos, .. }
97 | Self::InvalidEscape { escape_start: pos }
98 | Self::DuplicateName { second: Range { start: pos, .. }, .. }
99 | Self::Json { arguments_start: pos } => find_location_from_pos(pos, p.text.as_bytes()),
100 Self::NoStages => (0, 0),
101 };
102 Error(Box::new(ErrorInner { msg, line, column }), PhantomData)
103 }
104}
105
106impl fmt::Debug for Error {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 fmt::Debug::fmt(&self.0, f)
109 }
110}
111
112impl fmt::Display for Error {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 if self.0.line == 0 || f.alternate() {
115 fmt::Display::fmt(&self.0.msg, f)
116 } else {
117 write!(f, "{} at line {} column {}", self.0.msg, self.0.line, self.0.column)
118 }
119 }
120}
121
122impl std::error::Error for Error {}
123
124#[cold]
125fn find_location_from_pos(pos: usize, text: &[u8]) -> (usize, usize) {
126 let line = find_line_from_pos(pos, text);
127 let column = memrchr(b'\n', text.get(..pos).unwrap_or_default()).unwrap_or(pos) + 1;
128 (line, column)
129}
130
131#[cold]
132fn find_line_from_pos(pos: usize, text: &[u8]) -> usize {
133 bytecount(b'\n', text.get(..pos).unwrap_or_default()) + 1
134}
135
136#[inline]
137const fn memrchr_naive(needle: u8, mut s: &[u8]) -> Option<usize> {
138 let start = s;
139 while let Some((&b, s_next)) = s.split_last() {
140 if b == needle {
141 return Some(start.len() - s.len());
142 }
143 s = s_next;
144 }
145 None
146}
147use self::memrchr_naive as memrchr;
148
149#[inline]
150const fn bytecount_naive(needle: u8, mut s: &[u8]) -> usize {
151 let mut n = 0;
152 while let Some((&b, s_next)) = s.split_first() {
153 n += (b == needle) as usize;
154 s = s_next;
155 }
156 n
157}
158use self::bytecount_naive as bytecount;