1 //! # Chapter 7: Error Reporting 2 //! 3 //! ## Context 4 //! 5 //! With [`Parser::parse`] we get errors that point to the failure but don't explain the reason for 6 //! the failure: 7 //! ```rust 8 //! # use winnow::prelude::*; 9 //! # use winnow::token::take_while; 10 //! # use winnow::combinator::alt; 11 //! # use winnow::token::take; 12 //! # use winnow::combinator::fail; 13 //! # use winnow::Parser; 14 //! # 15 //! # #[derive(Debug, PartialEq, Eq)] 16 //! # pub struct Hex(usize); 17 //! # 18 //! # impl std::str::FromStr for Hex { 19 //! # type Err = String; 20 //! # 21 //! # fn from_str(input: &str) -> Result<Self, Self::Err> { 22 //! # parse_digits 23 //! # .try_map(|(t, v)| match t { 24 //! # "0b" => usize::from_str_radix(v, 2), 25 //! # "0o" => usize::from_str_radix(v, 8), 26 //! # "0d" => usize::from_str_radix(v, 10), 27 //! # "0x" => usize::from_str_radix(v, 16), 28 //! # _ => unreachable!("`parse_digits` doesn't return `{t}`"), 29 //! # }) 30 //! # .map(Hex) 31 //! # .parse(input) 32 //! # .map_err(|e| e.to_string()) 33 //! # } 34 //! # } 35 //! # 36 //! // ... 37 //! 38 //! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> { 39 //! # alt(( 40 //! # ("0b", parse_bin_digits), 41 //! # ("0o", parse_oct_digits), 42 //! # ("0d", parse_dec_digits), 43 //! # ("0x", parse_hex_digits), 44 //! # )).parse_next(input) 45 //! # } 46 //! # 47 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 48 //! # take_while(1.., ( 49 //! # ('0'..='1'), 50 //! # )).parse_next(input) 51 //! # } 52 //! # 53 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 54 //! # take_while(1.., ( 55 //! # ('0'..='7'), 56 //! # )).parse_next(input) 57 //! # } 58 //! # 59 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 60 //! # take_while(1.., ( 61 //! # ('0'..='9'), 62 //! # )).parse_next(input) 63 //! # } 64 //! # 65 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 66 //! # take_while(1.., ( 67 //! # ('0'..='9'), 68 //! # ('A'..='F'), 69 //! # ('a'..='f'), 70 //! # )).parse_next(input) 71 //! # } 72 //! fn main() { 73 //! let input = "0xZZ"; 74 //! let error = "\ 75 //! 0xZZ 76 //! ^ 77 //! "; 78 //! assert_eq!(input.parse::<Hex>().unwrap_err(), error); 79 //! } 80 //! ``` 81 //! 82 //! Back in [`chapter_1`], we glossed over the `Err` variant of [`PResult`]. `PResult<O>` is 83 //! actually short for `PResult<O, E=ContextError>` where [`ContextError`] is a relatively cheap 84 //! way of building up reasonable errors for humans. 85 //! 86 //! You can use [`Parser::context`] to annotate the error with custom types 87 //! while unwinding to further clarify the error: 88 //! ```rust 89 //! # use winnow::prelude::*; 90 //! # use winnow::token::take_while; 91 //! # use winnow::combinator::alt; 92 //! # use winnow::token::take; 93 //! # use winnow::combinator::fail; 94 //! # use winnow::Parser; 95 //! use winnow::error::StrContext; 96 //! use winnow::error::StrContextValue; 97 //! 98 //! # 99 //! # #[derive(Debug, PartialEq, Eq)] 100 //! # pub struct Hex(usize); 101 //! # 102 //! # impl std::str::FromStr for Hex { 103 //! # type Err = String; 104 //! # 105 //! # fn from_str(input: &str) -> Result<Self, Self::Err> { 106 //! # parse_digits 107 //! # .try_map(|(t, v)| match t { 108 //! # "0b" => usize::from_str_radix(v, 2), 109 //! # "0o" => usize::from_str_radix(v, 8), 110 //! # "0d" => usize::from_str_radix(v, 10), 111 //! # "0x" => usize::from_str_radix(v, 16), 112 //! # _ => unreachable!("`parse_digits` doesn't return `{t}`"), 113 //! # }) 114 //! # .map(Hex) 115 //! # .parse(input) 116 //! # .map_err(|e| e.to_string()) 117 //! # } 118 //! # } 119 //! # 120 //! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> { 121 //! alt(( 122 //! ("0b", parse_bin_digits) 123 //! .context(StrContext::Label("digit")) 124 //! .context(StrContext::Expected(StrContextValue::Description("binary"))), 125 //! ("0o", parse_oct_digits) 126 //! .context(StrContext::Label("digit")) 127 //! .context(StrContext::Expected(StrContextValue::Description("octal"))), 128 //! ("0d", parse_dec_digits) 129 //! .context(StrContext::Label("digit")) 130 //! .context(StrContext::Expected(StrContextValue::Description("decimal"))), 131 //! ("0x", parse_hex_digits) 132 //! .context(StrContext::Label("digit")) 133 //! .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))), 134 //! )).parse_next(input) 135 //! } 136 //! 137 //! // ... 138 //! 139 //! # 140 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 141 //! # take_while(1.., ( 142 //! # ('0'..='1'), 143 //! # )).parse_next(input) 144 //! # } 145 //! # 146 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 147 //! # take_while(1.., ( 148 //! # ('0'..='7'), 149 //! # )).parse_next(input) 150 //! # } 151 //! # 152 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 153 //! # take_while(1.., ( 154 //! # ('0'..='9'), 155 //! # )).parse_next(input) 156 //! # } 157 //! # 158 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 159 //! # take_while(1.., ( 160 //! # ('0'..='9'), 161 //! # ('A'..='F'), 162 //! # ('a'..='f'), 163 //! # )).parse_next(input) 164 //! # } 165 //! fn main() { 166 //! let input = "0xZZ"; 167 //! let error = "\ 168 //! 0xZZ 169 //! ^ 170 //! invalid digit 171 //! expected hexadecimal"; 172 //! assert_eq!(input.parse::<Hex>().unwrap_err(), error); 173 //! } 174 //! ``` 175 //! 176 //! If you remember back to [`chapter_3`], [`alt`] will only report the last error. 177 //! So if the parsers fail for any reason, like a bad radix, it will be reported as an invalid 178 //! hexadecimal value: 179 //! ```rust 180 //! # use winnow::prelude::*; 181 //! # use winnow::token::take_while; 182 //! # use winnow::combinator::alt; 183 //! # use winnow::token::take; 184 //! # use winnow::combinator::fail; 185 //! # use winnow::Parser; 186 //! # use winnow::error::StrContext; 187 //! # use winnow::error::StrContextValue; 188 //! # 189 //! # 190 //! # #[derive(Debug, PartialEq, Eq)] 191 //! # pub struct Hex(usize); 192 //! # 193 //! # impl std::str::FromStr for Hex { 194 //! # type Err = String; 195 //! # 196 //! # fn from_str(input: &str) -> Result<Self, Self::Err> { 197 //! # parse_digits 198 //! # .try_map(|(t, v)| match t { 199 //! # "0b" => usize::from_str_radix(v, 2), 200 //! # "0o" => usize::from_str_radix(v, 8), 201 //! # "0d" => usize::from_str_radix(v, 10), 202 //! # "0x" => usize::from_str_radix(v, 16), 203 //! # _ => unreachable!("`parse_digits` doesn't return `{t}`"), 204 //! # }) 205 //! # .map(Hex) 206 //! # .parse(input) 207 //! # .map_err(|e| e.to_string()) 208 //! # } 209 //! # } 210 //! # 211 //! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> { 212 //! # alt(( 213 //! # ("0b", parse_bin_digits) 214 //! # .context(StrContext::Label("digit")) 215 //! # .context(StrContext::Expected(StrContextValue::Description("binary"))), 216 //! # ("0o", parse_oct_digits) 217 //! # .context(StrContext::Label("digit")) 218 //! # .context(StrContext::Expected(StrContextValue::Description("octal"))), 219 //! # ("0d", parse_dec_digits) 220 //! # .context(StrContext::Label("digit")) 221 //! # .context(StrContext::Expected(StrContextValue::Description("decimal"))), 222 //! # ("0x", parse_hex_digits) 223 //! # .context(StrContext::Label("digit")) 224 //! # .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))), 225 //! # )).parse_next(input) 226 //! # } 227 //! # 228 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 229 //! # take_while(1.., ( 230 //! # ('0'..='1'), 231 //! # )).parse_next(input) 232 //! # } 233 //! # 234 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 235 //! # take_while(1.., ( 236 //! # ('0'..='7'), 237 //! # )).parse_next(input) 238 //! # } 239 //! # 240 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 241 //! # take_while(1.., ( 242 //! # ('0'..='9'), 243 //! # )).parse_next(input) 244 //! # } 245 //! # 246 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 247 //! # take_while(1.., ( 248 //! # ('0'..='9'), 249 //! # ('A'..='F'), 250 //! # ('a'..='f'), 251 //! # )).parse_next(input) 252 //! # } 253 //! fn main() { 254 //! let input = "100"; 255 //! let error = "\ 256 //! 100 257 //! ^ 258 //! invalid digit 259 //! expected hexadecimal"; 260 //! assert_eq!(input.parse::<Hex>().unwrap_err(), error); 261 //! } 262 //! ``` 263 //! We can improve this with [`fail`]: 264 //! ```rust 265 //! # use winnow::prelude::*; 266 //! # use winnow::token::take_while; 267 //! # use winnow::combinator::alt; 268 //! # use winnow::token::take; 269 //! # use winnow::combinator::fail; 270 //! # use winnow::Parser; 271 //! use winnow::error::StrContext; 272 //! use winnow::error::StrContextValue; 273 //! 274 //! # 275 //! # #[derive(Debug, PartialEq, Eq)] 276 //! # pub struct Hex(usize); 277 //! # 278 //! # impl std::str::FromStr for Hex { 279 //! # type Err = String; 280 //! # 281 //! # fn from_str(input: &str) -> Result<Self, Self::Err> { 282 //! # parse_digits 283 //! # .try_map(|(t, v)| match t { 284 //! # "0b" => usize::from_str_radix(v, 2), 285 //! # "0o" => usize::from_str_radix(v, 8), 286 //! # "0d" => usize::from_str_radix(v, 10), 287 //! # "0x" => usize::from_str_radix(v, 16), 288 //! # _ => unreachable!("`parse_digits` doesn't return `{t}`"), 289 //! # }) 290 //! # .map(Hex) 291 //! # .parse(input) 292 //! # .map_err(|e| e.to_string()) 293 //! # } 294 //! # } 295 //! # 296 //! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> { 297 //! alt(( 298 //! ("0b", parse_bin_digits) 299 //! .context(StrContext::Label("digit")) 300 //! .context(StrContext::Expected(StrContextValue::Description("binary"))), 301 //! ("0o", parse_oct_digits) 302 //! .context(StrContext::Label("digit")) 303 //! .context(StrContext::Expected(StrContextValue::Description("octal"))), 304 //! ("0d", parse_dec_digits) 305 //! .context(StrContext::Label("digit")) 306 //! .context(StrContext::Expected(StrContextValue::Description("decimal"))), 307 //! ("0x", parse_hex_digits) 308 //! .context(StrContext::Label("digit")) 309 //! .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))), 310 //! fail 311 //! .context(StrContext::Label("radix prefix")) 312 //! .context(StrContext::Expected(StrContextValue::StringLiteral("0b"))) 313 //! .context(StrContext::Expected(StrContextValue::StringLiteral("0o"))) 314 //! .context(StrContext::Expected(StrContextValue::StringLiteral("0d"))) 315 //! .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))), 316 //! )).parse_next(input) 317 //! } 318 //! 319 //! // ... 320 //! 321 //! # 322 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 323 //! # take_while(1.., ( 324 //! # ('0'..='1'), 325 //! # )).parse_next(input) 326 //! # } 327 //! # 328 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 329 //! # take_while(1.., ( 330 //! # ('0'..='7'), 331 //! # )).parse_next(input) 332 //! # } 333 //! # 334 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 335 //! # take_while(1.., ( 336 //! # ('0'..='9'), 337 //! # )).parse_next(input) 338 //! # } 339 //! # 340 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 341 //! # take_while(1.., ( 342 //! # ('0'..='9'), 343 //! # ('A'..='F'), 344 //! # ('a'..='f'), 345 //! # )).parse_next(input) 346 //! # } 347 //! fn main() { 348 //! let input = "100"; 349 //! let error = "\ 350 //! 100 351 //! ^ 352 //! invalid radix prefix 353 //! expected `0b`, `0o`, `0d`, `0x`"; 354 //! assert_eq!(input.parse::<Hex>().unwrap_err(), error); 355 //! } 356 //! ``` 357 //! 358 //! ## Error Cuts 359 //! 360 //! We still have the issue that we are falling-through when the radix is valid but the digits 361 //! don't match it: 362 //! ```rust 363 //! # use winnow::prelude::*; 364 //! # use winnow::token::take_while; 365 //! # use winnow::combinator::alt; 366 //! # use winnow::token::take; 367 //! # use winnow::combinator::fail; 368 //! # use winnow::Parser; 369 //! # use winnow::error::StrContext; 370 //! # use winnow::error::StrContextValue; 371 //! # 372 //! # 373 //! # #[derive(Debug, PartialEq, Eq)] 374 //! # pub struct Hex(usize); 375 //! # 376 //! # impl std::str::FromStr for Hex { 377 //! # type Err = String; 378 //! # 379 //! # fn from_str(input: &str) -> Result<Self, Self::Err> { 380 //! # parse_digits 381 //! # .try_map(|(t, v)| match t { 382 //! # "0b" => usize::from_str_radix(v, 2), 383 //! # "0o" => usize::from_str_radix(v, 8), 384 //! # "0d" => usize::from_str_radix(v, 10), 385 //! # "0x" => usize::from_str_radix(v, 16), 386 //! # _ => unreachable!("`parse_digits` doesn't return `{t}`"), 387 //! # }) 388 //! # .map(Hex) 389 //! # .parse(input) 390 //! # .map_err(|e| e.to_string()) 391 //! # } 392 //! # } 393 //! # 394 //! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> { 395 //! # alt(( 396 //! # ("0b", parse_bin_digits) 397 //! # .context(StrContext::Label("digit")) 398 //! # .context(StrContext::Expected(StrContextValue::Description("binary"))), 399 //! # ("0o", parse_oct_digits) 400 //! # .context(StrContext::Label("digit")) 401 //! # .context(StrContext::Expected(StrContextValue::Description("octal"))), 402 //! # ("0d", parse_dec_digits) 403 //! # .context(StrContext::Label("digit")) 404 //! # .context(StrContext::Expected(StrContextValue::Description("decimal"))), 405 //! # ("0x", parse_hex_digits) 406 //! # .context(StrContext::Label("digit")) 407 //! # .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))), 408 //! # fail 409 //! # .context(StrContext::Label("radix prefix")) 410 //! # .context(StrContext::Expected(StrContextValue::StringLiteral("0b"))) 411 //! # .context(StrContext::Expected(StrContextValue::StringLiteral("0o"))) 412 //! # .context(StrContext::Expected(StrContextValue::StringLiteral("0d"))) 413 //! # .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))), 414 //! # )).parse_next(input) 415 //! # } 416 //! # 417 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 418 //! # take_while(1.., ( 419 //! # ('0'..='1'), 420 //! # )).parse_next(input) 421 //! # } 422 //! # 423 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 424 //! # take_while(1.., ( 425 //! # ('0'..='7'), 426 //! # )).parse_next(input) 427 //! # } 428 //! # 429 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 430 //! # take_while(1.., ( 431 //! # ('0'..='9'), 432 //! # )).parse_next(input) 433 //! # } 434 //! # 435 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 436 //! # take_while(1.., ( 437 //! # ('0'..='9'), 438 //! # ('A'..='F'), 439 //! # ('a'..='f'), 440 //! # )).parse_next(input) 441 //! # } 442 //! fn main() { 443 //! let input = "0b5"; 444 //! let error = "\ 445 //! 0b5 446 //! ^ 447 //! invalid radix prefix 448 //! expected `0b`, `0o`, `0d`, `0x`"; 449 //! assert_eq!(input.parse::<Hex>().unwrap_err(), error); 450 //! } 451 //! ``` 452 //! 453 //! Let's break down `PResult<O, E>` one step further: 454 //! ```rust 455 //! # use winnow::error::ErrorKind; 456 //! # use winnow::error::ErrMode; 457 //! pub type PResult<O, E = ErrorKind> = Result<O, ErrMode<E>>; 458 //! ``` 459 //! [`PResult`] is just a fancy wrapper around `Result` that wraps our error in an [`ErrMode`] 460 //! type. 461 //! 462 //! [`ErrMode`] is an enum with [`Backtrack`] and [`Cut`] variants (ignore [`Incomplete`] as its only 463 //! relevant for [streaming][_topic::stream]). By default, errors are [`Backtrack`], meaning that 464 //! other parsing branches will be attempted on failure, like the next case of an [`alt`]. [`Cut`] 465 //! shortcircuits all other branches, immediately reporting the error. 466 //! 467 //! So we can get the correct `context` by modifying the above example with [`cut_err`]: 468 //! ```rust 469 //! # use winnow::prelude::*; 470 //! # use winnow::token::take_while; 471 //! # use winnow::combinator::alt; 472 //! # use winnow::token::take; 473 //! # use winnow::combinator::fail; 474 //! # use winnow::Parser; 475 //! # use winnow::error::StrContext; 476 //! # use winnow::error::StrContextValue; 477 //! use winnow::combinator::cut_err; 478 //! 479 //! # 480 //! # #[derive(Debug, PartialEq, Eq)] 481 //! # pub struct Hex(usize); 482 //! # 483 //! # impl std::str::FromStr for Hex { 484 //! # type Err = String; 485 //! # 486 //! # fn from_str(input: &str) -> Result<Self, Self::Err> { 487 //! # parse_digits 488 //! # .try_map(|(t, v)| match t { 489 //! # "0b" => usize::from_str_radix(v, 2), 490 //! # "0o" => usize::from_str_radix(v, 8), 491 //! # "0d" => usize::from_str_radix(v, 10), 492 //! # "0x" => usize::from_str_radix(v, 16), 493 //! # _ => unreachable!("`parse_digits` doesn't return `{t}`"), 494 //! # }) 495 //! # .map(Hex) 496 //! # .parse(input) 497 //! # .map_err(|e| e.to_string()) 498 //! # } 499 //! # } 500 //! # 501 //! fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> { 502 //! alt(( 503 //! ("0b", cut_err(parse_bin_digits)) 504 //! .context(StrContext::Label("digit")) 505 //! .context(StrContext::Expected(StrContextValue::Description("binary"))), 506 //! ("0o", cut_err(parse_oct_digits)) 507 //! .context(StrContext::Label("digit")) 508 //! .context(StrContext::Expected(StrContextValue::Description("octal"))), 509 //! ("0d", cut_err(parse_dec_digits)) 510 //! .context(StrContext::Label("digit")) 511 //! .context(StrContext::Expected(StrContextValue::Description("decimal"))), 512 //! ("0x", cut_err(parse_hex_digits)) 513 //! .context(StrContext::Label("digit")) 514 //! .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))), 515 //! fail 516 //! .context(StrContext::Label("radix prefix")) 517 //! .context(StrContext::Expected(StrContextValue::StringLiteral("0b"))) 518 //! .context(StrContext::Expected(StrContextValue::StringLiteral("0o"))) 519 //! .context(StrContext::Expected(StrContextValue::StringLiteral("0d"))) 520 //! .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))), 521 //! )).parse_next(input) 522 //! } 523 //! 524 //! // ... 525 //! 526 //! # 527 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 528 //! # take_while(1.., ( 529 //! # ('0'..='1'), 530 //! # )).parse_next(input) 531 //! # } 532 //! # 533 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 534 //! # take_while(1.., ( 535 //! # ('0'..='7'), 536 //! # )).parse_next(input) 537 //! # } 538 //! # 539 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 540 //! # take_while(1.., ( 541 //! # ('0'..='9'), 542 //! # )).parse_next(input) 543 //! # } 544 //! # 545 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 546 //! # take_while(1.., ( 547 //! # ('0'..='9'), 548 //! # ('A'..='F'), 549 //! # ('a'..='f'), 550 //! # )).parse_next(input) 551 //! # } 552 //! fn main() { 553 //! let input = "0b5"; 554 //! let error = "\ 555 //! 0b5 556 //! ^ 557 //! invalid digit 558 //! expected binary"; 559 //! assert_eq!(input.parse::<Hex>().unwrap_err(), error); 560 //! } 561 //! ``` 562 //! 563 //! ## Error Adaptation and Rendering 564 //! 565 //! While Winnow can provide basic rendering of errors, your application can have various demands 566 //! beyond the basics provided like 567 //! - Correctly reporting columns with unicode 568 //! - Conforming to a specific layout 569 //! 570 //! For example, to get rustc-like errors with [`annotate-snippets`](https://crates.io/crates/annotate-snippets): 571 //! ```rust 572 //! # use winnow::prelude::*; 573 //! # use winnow::token::take_while; 574 //! # use winnow::combinator::alt; 575 //! # use winnow::token::take; 576 //! # use winnow::combinator::fail; 577 //! # use winnow::Parser; 578 //! # use winnow::error::ParseError; 579 //! # use winnow::error::ContextError; 580 //! # use winnow::error::StrContext; 581 //! # use winnow::error::StrContextValue; 582 //! # use winnow::combinator::cut_err; 583 //! # 584 //! # 585 //! #[derive(Debug, PartialEq, Eq)] 586 //! pub struct Hex(usize); 587 //! 588 //! impl std::str::FromStr for Hex { 589 //! type Err = HexError; 590 //! 591 //! fn from_str(input: &str) -> Result<Self, Self::Err> { 592 //! // ... 593 //! # parse_digits 594 //! # .try_map(|(t, v)| match t { 595 //! # "0b" => usize::from_str_radix(v, 2), 596 //! # "0o" => usize::from_str_radix(v, 8), 597 //! # "0d" => usize::from_str_radix(v, 10), 598 //! # "0x" => usize::from_str_radix(v, 16), 599 //! # _ => unreachable!("`parse_digits` doesn't return `{t}`"), 600 //! # }) 601 //! # .map(Hex) 602 //! .parse(input) 603 //! .map_err(|e| HexError::from_parse(e, input)) 604 //! } 605 //! } 606 //! 607 //! #[derive(Debug)] 608 //! pub struct HexError { 609 //! message: String, 610 //! // Byte spans are tracked, rather than line and column. 611 //! // This makes it easier to operate on programmatically 612 //! // and doesn't limit us to one definition for column count 613 //! // which can depend on the output medium and application. 614 //! span: std::ops::Range<usize>, 615 //! input: String, 616 //! } 617 //! 618 //! impl HexError { 619 //! fn from_parse(error: ParseError<&str, ContextError>, input: &str) -> Self { 620 //! // The default renderer for `ContextError` is still used but that can be 621 //! // customized as well to better fit your needs. 622 //! let message = error.inner().to_string(); 623 //! let input = input.to_owned(); 624 //! let start = error.offset(); 625 //! // Assume the error span is only for the first `char`. 626 //! // Semantic errors are free to choose the entire span returned by `Parser::with_span`. 627 //! let end = (start + 1..).find(|e| input.is_char_boundary(*e)).unwrap_or(start); 628 //! Self { 629 //! message, 630 //! span: start..end, 631 //! input, 632 //! } 633 //! } 634 //! } 635 //! 636 //! impl std::fmt::Display for HexError { 637 //! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 638 //! let message = annotate_snippets::Level::Error.title(&self.message) 639 //! .snippet(annotate_snippets::Snippet::source(&self.input) 640 //! .fold(true) 641 //! .annotation(annotate_snippets::Level::Error.span(self.span.clone())) 642 //! ); 643 //! let renderer = annotate_snippets::Renderer::plain(); 644 //! let rendered = renderer.render(message); 645 //! rendered.fmt(f) 646 //! } 647 //! } 648 //! 649 //! impl std::error::Error for HexError {} 650 //! 651 //! # fn parse_digits<'s>(input: &mut &'s str) -> PResult<(&'s str, &'s str)> { 652 //! # alt(( 653 //! # ("0b", cut_err(parse_bin_digits)) 654 //! # .context(StrContext::Label("digit")) 655 //! # .context(StrContext::Expected(StrContextValue::Description("binary"))), 656 //! # ("0o", cut_err(parse_oct_digits)) 657 //! # .context(StrContext::Label("digit")) 658 //! # .context(StrContext::Expected(StrContextValue::Description("octal"))), 659 //! # ("0d", cut_err(parse_dec_digits)) 660 //! # .context(StrContext::Label("digit")) 661 //! # .context(StrContext::Expected(StrContextValue::Description("decimal"))), 662 //! # ("0x", cut_err(parse_hex_digits)) 663 //! # .context(StrContext::Label("digit")) 664 //! # .context(StrContext::Expected(StrContextValue::Description("hexadecimal"))), 665 //! # fail 666 //! # .context(StrContext::Label("radix prefix")) 667 //! # .context(StrContext::Expected(StrContextValue::StringLiteral("0b"))) 668 //! # .context(StrContext::Expected(StrContextValue::StringLiteral("0o"))) 669 //! # .context(StrContext::Expected(StrContextValue::StringLiteral("0d"))) 670 //! # .context(StrContext::Expected(StrContextValue::StringLiteral("0x"))), 671 //! # )).parse_next(input) 672 //! # } 673 //! # 674 //! # fn parse_bin_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 675 //! # take_while(1.., ( 676 //! # ('0'..='1'), 677 //! # )).parse_next(input) 678 //! # } 679 //! # 680 //! # fn parse_oct_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 681 //! # take_while(1.., ( 682 //! # ('0'..='7'), 683 //! # )).parse_next(input) 684 //! # } 685 //! # 686 //! # fn parse_dec_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 687 //! # take_while(1.., ( 688 //! # ('0'..='9'), 689 //! # )).parse_next(input) 690 //! # } 691 //! # 692 //! # fn parse_hex_digits<'s>(input: &mut &'s str) -> PResult<&'s str> { 693 //! # take_while(1.., ( 694 //! # ('0'..='9'), 695 //! # ('A'..='F'), 696 //! # ('a'..='f'), 697 //! # )).parse_next(input) 698 //! # } 699 //! fn main() { 700 //! let input = "0b5"; 701 //! let error = "\ 702 //! error: invalid digit 703 //! expected binary 704 //! | 705 //! 1 | 0b5 706 //! | ^ 707 //! |"; 708 //! assert_eq!(input.parse::<Hex>().unwrap_err().to_string(), error); 709 //! } 710 //! ``` 711 712 #![allow(unused_imports)] 713 use super::chapter_1; 714 use super::chapter_3; 715 use crate::combinator::alt; 716 use crate::combinator::cut_err; 717 use crate::combinator::fail; 718 use crate::error::ContextError; 719 use crate::error::ErrMode; 720 use crate::error::ErrMode::*; 721 use crate::error::ErrorKind; 722 use crate::PResult; 723 use crate::Parser; 724 use crate::_topic; 725 726 pub use super::chapter_6 as previous; 727 pub use super::chapter_8 as next; 728 pub use crate::_tutorial as table_of_contents; 729