1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
//! Items relating to puzzle input.
use std::error::Error;
use std::fmt::{Display, Formatter};
/// Enum for distinguishing between example and real inputs.
///
/// Some puzzles require this as different constants may be used for example inputs to simplify the
/// problem. For example [2022 day 15](https://adventofcode.com/2022/day/15) part 1, which uses
/// `y=10` in the example, but `y=2000000` for real inputs.
///
/// Most puzzle solutions should ignore this value.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub enum InputType {
Example,
Real,
}
/// Error type that shows the error's location in the input, returned by puzzle `new` functions.
///
/// # Examples
///
/// ```
/// # use utils::input::InputError;
/// let input = "12 34\n56 78\n90 abc";
/// let error = InputError::new(input, 15, "expected number");
/// assert_eq!(error.to_string(), "
/// invalid input: expected number
/// --> line 3 column 4
/// |
/// 3 | 90 abc
/// | ^
/// ".trim_start());
/// ```
#[derive(Debug)]
pub struct InputError {
line_number: usize,
column_number: usize,
line: String,
source: Box<dyn Error>,
}
impl InputError {
/// Create a new [`InputError`].
///
/// See [`ToIndex`] implementations for details on supported indexes.
#[cold]
pub fn new(input: &str, index: impl ToIndex, source: impl Into<Box<dyn Error>>) -> Self {
let index = index.input_index(input);
let (line_number, column_number, line) = Self::line_position(input, index);
let line = line.replace('\t', " ");
InputError {
line_number,
column_number,
line,
source: source.into(),
}
}
#[cold]
fn line_position(input: &str, index: usize) -> (usize, usize, String) {
let start = input[..index].rfind('\n').map_or(0, |p| p + 1);
let end = input[start..].find('\n').map_or(input.len(), |p| p + start);
let line = input[start..end].trim_end_matches('\r');
let line_number = input[..start].matches('\n').count() + 1;
let column_number = index - start + 1;
(line_number, column_number, line.to_string())
}
}
impl Display for InputError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let pad = " ".repeat(self.line_number.to_string().len());
write!(
f,
"invalid input: {}\n --> line {} column {}\n{pad} |\n{} | {}\n{pad} |{}^\n",
self.source,
self.line_number,
self.column_number,
self.line_number,
self.line,
" ".repeat(self.column_number),
)
}
}
impl Error for InputError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&*self.source)
}
}
/// Helper trait to simplify error location tracking.
///
/// Used in [`InputError::new`].
pub trait ToIndex {
fn input_index(self, input: &str) -> usize;
}
impl ToIndex for &str {
/// Find index of this substring in the provided input.
///
/// Uses the pointer offset, meaning it works if this substring is not the first occurrence in
/// the string. This allows recovering the error position without tracking an offset into the
/// string, which is useful when using [`Iterator`]s such as [`str::lines`] on an input.
///
/// # Panics
///
/// This function panics if this string is not a substring inside the provided string.
///
/// # Examples
///
/// ```
/// # use utils::input::ToIndex;
/// let string = "abcabc";
/// assert_eq!(string[4..].input_index(string), 4);
/// ```
///
/// ```should_panic
/// # use utils::input::ToIndex;
/// let string = "abcabc";
/// let mut other = String::new();
/// other.push('b');
/// other.push('c');
/// other.input_index(string);
/// ```
fn input_index(self, input: &str) -> usize {
self.as_bytes().input_index(input)
}
}
impl ToIndex for &[u8] {
/// Find index of this subslice in the provided input.
///
/// For use with functions that iterate over a string's bytes.
/// See the [`&str`](#impl-ToIndex-for-%26str) implementation.
fn input_index(self, input: &str) -> usize {
let self_ptr = self.as_ptr() as usize;
let input_ptr = input.as_ptr() as usize;
match self_ptr.checked_sub(input_ptr) {
Some(offset) if offset + self.len() <= input.len() => offset,
_ => panic!("invalid string index: {self_ptr:#x} is not a substring of {input_ptr:#x}"),
}
}
}
impl ToIndex for char {
/// Find the first instance of this character in the string.
///
/// Intended for puzzles where the entire input should be a certain set of characters, so
/// if an invalid character is found, the instance in the error doesn't matter.
///
/// # Panics
///
/// This function panics if this character is not present in the string
///
/// # Examples
///
/// ```
/// # use utils::input::ToIndex;
/// let string = "abca bc";
/// assert_eq!(' '.input_index(string), 4);
/// ```
///
/// ```should_panic
/// # use utils::input::ToIndex;
/// let string = "abcdef";
/// ' '.input_index(string);
/// ```
fn input_index(self, input: &str) -> usize {
input
.find(self)
.unwrap_or_else(|| panic!("invalid string index: char {self:?} not found in {input:?}"))
}
}
impl ToIndex for usize {
/// Index into the input string.
///
/// # Panics
///
/// This function panics if the index is out of range for the provided string.
///
/// # Examples
///
/// ```
/// # use utils::input::ToIndex;
/// let string = "abcdef";
/// assert_eq!(4.input_index(string), 4);
/// ```
///
/// ```should_panic
/// # use utils::input::ToIndex;
/// let string = "abcdef";
/// 10.input_index(string);
/// ```
fn input_index(self, input: &str) -> usize {
assert!(
self <= input.len(),
"invalid string index: index {self} out of range"
);
self
}
}
/// Extension trait to simplify converting errors and locations into [`InputError`]s.
///
/// Note that constructing [`InputError`] is expensive, and therefore conversion should be done as
/// late as possible, to avoid unnecessary work if the error is discarded (for example,
/// by [`Parser::or`](crate::parser::Parser::or)).
pub trait MapWithInputExt {
type Output;
fn map_with_input(self, input: &str) -> Self::Output;
}
impl<E: Into<Box<dyn Error>>, I: ToIndex> MapWithInputExt for (E, I) {
type Output = InputError;
#[cold]
fn map_with_input(self, input: &str) -> Self::Output {
InputError::new(input, self.1, self.0)
}
}
impl<T, E: Into<Box<dyn Error>>, I: ToIndex> MapWithInputExt for Result<T, (E, I)> {
type Output = Result<T, InputError>;
#[inline]
fn map_with_input(self, input: &str) -> Self::Output {
self.map_err(|err| err.map_with_input(input))
}
}