├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── monch.apng ├── rust-toolchain.toml └── src └── lib.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | push: 7 | branches: [main] 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | rust: 13 | name: monch 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 30 16 | 17 | env: 18 | CARGO_INCREMENTAL: 0 19 | GH_ACTIONS: 1 20 | RUST_BACKTRACE: full 21 | RUSTFLAGS: -D warnings 22 | 23 | steps: 24 | - name: Clone repository 25 | uses: actions/checkout@v3 26 | 27 | - uses: denoland/setup-deno@v1 28 | - uses: dtolnay/rust-toolchain@stable 29 | 30 | - uses: Swatinem/rust-cache@v2 31 | with: 32 | save-if: ${{ github.ref == 'refs/heads/main' }} 33 | 34 | - name: Format 35 | run: cargo fmt --all -- --check 36 | 37 | - name: Lint 38 | run: cargo clippy --all-targets --all-features 39 | 40 | - name: Build 41 | run: cargo build --all-targets --all-features 42 | - name: Test 43 | run: cargo test --all-targets --all-features 44 | 45 | - name: Publish 46 | if: | 47 | github.repository == 'denoland/monch' && 48 | startsWith(github.ref, 'refs/tags/') 49 | env: 50 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 51 | run: | 52 | cargo publish 53 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | releaseKind: 7 | description: 'Kind of release' 8 | default: 'minor' 9 | type: choice 10 | options: 11 | - patch 12 | - minor 13 | required: true 14 | 15 | jobs: 16 | rust: 17 | name: release 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 30 20 | 21 | steps: 22 | - name: Clone repository 23 | uses: actions/checkout@v3 24 | with: 25 | token: ${{ secrets.DENOBOT_PAT }} 26 | 27 | - uses: denoland/setup-deno@v1 28 | - uses: dtolnay/rust-toolchain@stable 29 | 30 | - name: Tag and release 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.DENOBOT_PAT }} 33 | GH_WORKFLOW_ACTOR: ${{ github.actor }} 34 | run: | 35 | git config user.email "denobot@users.noreply.github.com" 36 | git config user.name "denobot" 37 | deno run -A https://raw.githubusercontent.com/denoland/automation/0.14.0/tasks/publish_release.ts --${{github.event.inputs.releaseKind}} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | edition = "2021" 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "monch" 7 | version = "0.5.0" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "monch" 3 | version = "0.5.0" 4 | authors = ["the Deno authors"] 5 | documentation = "https://docs.rs/monch" 6 | edition = "2021" 7 | homepage = "https://deno.land/" 8 | license = "MIT" 9 | repository = "https://github.com/denoland/monch" 10 | description = "Inspired by nom, but specifically for strings." 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2018-2024 the Deno authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # monch 2 | 3 | [![](https://img.shields.io/crates/v/monch.svg)](https://crates.io/crates/monch) 4 | 5 | ![Deno dinosaur eating leaves.](monch.apng) 6 | 7 | Inspired by [nom](https://crates.io/crates/nom), but specifically for strings and with some additional combinators we use in Deno. 8 | 9 | See an example of use in [deno_task_shell](https://github.com/denoland/deno_task_shell). 10 | -------------------------------------------------------------------------------- /monch.apng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denoland/monch/688e97c2a11727c2c8ed66a0f5df0110f905d339/monch.apng -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.75.0" 3 | components = ["clippy", "rustfmt"] 4 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. 2 | 3 | /// An error that occurred while parsing. 4 | #[derive(Debug)] 5 | pub enum ParseError<'a> { 6 | Backtrace, 7 | /// Parsing should completely fail. 8 | Failure(ParseErrorFailure<'a>), 9 | } 10 | 11 | /// A complete parsing failure along with the location 12 | /// the error occurred and the error message. 13 | #[derive(Debug, Clone)] 14 | pub struct ParseErrorFailure<'a> { 15 | pub input: &'a str, 16 | pub message: String, 17 | } 18 | 19 | impl<'a> ParseErrorFailure<'a> { 20 | pub fn new(input: &'a str, message: impl AsRef) -> Self { 21 | ParseErrorFailure { 22 | input, 23 | message: message.as_ref().to_owned(), 24 | } 25 | } 26 | 27 | /// Opinionated helper used to fail for trailing input. 28 | pub fn new_for_trailing_input(input: &'a str) -> Self { 29 | ParseErrorFailure::new(input, "Unexpected character.") 30 | } 31 | 32 | /// Opinionated helper to turn this failure into a result. 33 | pub fn into_result(self) -> Result { 34 | Err(self.into_error()) 35 | } 36 | 37 | /// Opinionated helper to turn this failure into a `ParseErrorFailureError`. 38 | pub fn into_error(self) -> ParseErrorFailureError { 39 | ParseErrorFailureError { 40 | message: self.message, 41 | // truncate the output to prevent wrapping in the console 42 | code_snippet: Some(self.input.chars().take(60).collect::()), 43 | } 44 | } 45 | } 46 | 47 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 48 | pub struct ParseErrorFailureError { 49 | pub message: String, 50 | pub code_snippet: Option, 51 | } 52 | 53 | impl ParseErrorFailureError { 54 | pub fn new(message: String) -> Self { 55 | ParseErrorFailureError { 56 | message, 57 | code_snippet: None, 58 | } 59 | } 60 | } 61 | 62 | impl std::fmt::Display for ParseErrorFailureError { 63 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 64 | if let Some(code_snippet) = &self.code_snippet { 65 | write!(f, "{}\n {}\n ~", self.message, code_snippet) 66 | } else { 67 | write!(f, "{}", self.message) 68 | } 69 | } 70 | } 71 | 72 | impl std::error::Error for ParseErrorFailureError {} 73 | 74 | impl<'a> ParseError<'a> { 75 | pub fn fail( 76 | input: &'a str, 77 | message: impl AsRef, 78 | ) -> ParseResult<'a, O> { 79 | Err(ParseError::Failure(ParseErrorFailure::new(input, message))) 80 | } 81 | 82 | pub fn backtrace() -> ParseResult<'a, O> { 83 | Err(ParseError::Backtrace) 84 | } 85 | } 86 | 87 | pub type ParseResult<'a, O> = Result<(&'a str, O), ParseError<'a>>; 88 | 89 | /// Opinionated helper that converts a combinator into a Result 90 | pub fn with_failure_handling<'a, T>( 91 | combinator: impl Fn(&'a str) -> ParseResult, 92 | ) -> impl Fn(&'a str) -> Result { 93 | move |input| match combinator(input) { 94 | Ok((input, result)) => { 95 | if !input.is_empty() { 96 | ParseErrorFailure::new_for_trailing_input(input).into_result() 97 | } else { 98 | Ok(result) 99 | } 100 | } 101 | Err(ParseError::Backtrace) => { 102 | ParseErrorFailure::new_for_trailing_input(input).into_result() 103 | } 104 | Err(ParseError::Failure(e)) => e.into_result(), 105 | } 106 | } 107 | 108 | /// Recognizes a character. 109 | pub fn ch<'a>(c: char) -> impl Fn(&'a str) -> ParseResult<'a, char> { 110 | if_true(next_char, move |found_char| *found_char == c) 111 | } 112 | 113 | /// Gets the next character. 114 | #[allow(clippy::needless_lifetimes)] 115 | pub fn next_char<'a>(input: &'a str) -> ParseResult<'a, char> { 116 | match input.chars().next() { 117 | Some(next_char) => Ok((&input[next_char.len_utf8()..], next_char)), 118 | _ => ParseError::backtrace(), 119 | } 120 | } 121 | 122 | /// Recognizes any character in the provided string. 123 | pub fn one_of<'a>( 124 | value: &'static str, 125 | ) -> impl Fn(&'a str) -> ParseResult<'a, char> { 126 | move |input| { 127 | let (input, c) = next_char(input)?; 128 | if value.contains(c) { 129 | Ok((input, c)) 130 | } else { 131 | ParseError::backtrace() 132 | } 133 | } 134 | } 135 | 136 | /// Recognizes a string. 137 | pub fn tag<'a>( 138 | value: impl AsRef, 139 | ) -> impl Fn(&'a str) -> ParseResult<'a, &'a str> { 140 | let value = value.as_ref().to_string(); 141 | move |input| { 142 | if input.starts_with(&value) { 143 | Ok((&input[value.len()..], &input[..value.len()])) 144 | } else { 145 | Err(ParseError::Backtrace) 146 | } 147 | } 148 | } 149 | 150 | /// Gets the substring found for the duration of the combinator. 151 | pub fn substring<'a, O>( 152 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 153 | ) -> impl Fn(&'a str) -> ParseResult<'a, &'a str> { 154 | move |input| { 155 | let original_input = input; 156 | let (input, _) = combinator(input)?; 157 | let length = original_input.len() - input.len(); 158 | Ok((input, &original_input[..length])) 159 | } 160 | } 161 | 162 | /// Skip the input while the condition is true. 163 | pub fn skip_while<'a>( 164 | cond: impl Fn(char) -> bool, 165 | ) -> impl Fn(&'a str) -> ParseResult<'a, ()> { 166 | move |input| { 167 | for (pos, c) in input.char_indices() { 168 | if !cond(c) { 169 | return Ok((&input[pos..], ())); 170 | } 171 | } 172 | // reached the end 173 | Ok(("", ())) 174 | } 175 | } 176 | 177 | /// Takes a substring while the condition is true. 178 | pub fn take_while<'a>( 179 | cond: impl Fn(char) -> bool, 180 | ) -> impl Fn(&'a str) -> ParseResult<'a, &'a str> { 181 | substring(skip_while(cond)) 182 | } 183 | 184 | /// Maps a success to `Some(T)` and a backtrace to `None`. 185 | pub fn maybe<'a, O>( 186 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 187 | ) -> impl Fn(&'a str) -> ParseResult<'a, Option> { 188 | move |input| match combinator(input) { 189 | Ok((input, value)) => Ok((input, Some(value))), 190 | Err(ParseError::Backtrace) => Ok((input, None)), 191 | Err(err) => Err(err), 192 | } 193 | } 194 | 195 | /// Maps the success of a combinator by a function. 196 | pub fn map<'a, O, R>( 197 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 198 | func: impl Fn(O) -> R, 199 | ) -> impl Fn(&'a str) -> ParseResult<'a, R> { 200 | move |input| { 201 | let (input, result) = combinator(input)?; 202 | Ok((input, func(result))) 203 | } 204 | } 205 | 206 | /// Maps the result of a combinator by a function. 207 | pub fn map_res<'a, O, R>( 208 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 209 | func: impl Fn(ParseResult<'a, O>) -> R, 210 | ) -> impl Fn(&'a str) -> R { 211 | move |input| func(combinator(input)) 212 | } 213 | 214 | /// Checks for either to match. 215 | pub fn or<'a, O>( 216 | a: impl Fn(&'a str) -> ParseResult<'a, O>, 217 | b: impl Fn(&'a str) -> ParseResult<'a, O>, 218 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 219 | move |input| match a(input) { 220 | Ok(result) => Ok(result), 221 | Err(ParseError::Backtrace) => b(input), 222 | Err(err) => Err(err), 223 | } 224 | } 225 | 226 | /// Checks for any to match. 227 | pub fn or3<'a, O>( 228 | a: impl Fn(&'a str) -> ParseResult<'a, O>, 229 | b: impl Fn(&'a str) -> ParseResult<'a, O>, 230 | c: impl Fn(&'a str) -> ParseResult<'a, O>, 231 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 232 | or(a, or(b, c)) 233 | } 234 | 235 | /// Checks for any to match. 236 | pub fn or4<'a, O>( 237 | a: impl Fn(&'a str) -> ParseResult<'a, O>, 238 | b: impl Fn(&'a str) -> ParseResult<'a, O>, 239 | c: impl Fn(&'a str) -> ParseResult<'a, O>, 240 | d: impl Fn(&'a str) -> ParseResult<'a, O>, 241 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 242 | or3(a, b, or(c, d)) 243 | } 244 | 245 | /// Checks for any to match. 246 | pub fn or5<'a, O>( 247 | a: impl Fn(&'a str) -> ParseResult<'a, O>, 248 | b: impl Fn(&'a str) -> ParseResult<'a, O>, 249 | c: impl Fn(&'a str) -> ParseResult<'a, O>, 250 | d: impl Fn(&'a str) -> ParseResult<'a, O>, 251 | e: impl Fn(&'a str) -> ParseResult<'a, O>, 252 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 253 | or4(a, b, c, or(d, e)) 254 | } 255 | 256 | /// Checks for any to match. 257 | pub fn or6<'a, O>( 258 | a: impl Fn(&'a str) -> ParseResult<'a, O>, 259 | b: impl Fn(&'a str) -> ParseResult<'a, O>, 260 | c: impl Fn(&'a str) -> ParseResult<'a, O>, 261 | d: impl Fn(&'a str) -> ParseResult<'a, O>, 262 | e: impl Fn(&'a str) -> ParseResult<'a, O>, 263 | f: impl Fn(&'a str) -> ParseResult<'a, O>, 264 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 265 | or5(a, b, c, d, or(e, f)) 266 | } 267 | 268 | /// Checks for any to match. 269 | pub fn or7<'a, O>( 270 | a: impl Fn(&'a str) -> ParseResult<'a, O>, 271 | b: impl Fn(&'a str) -> ParseResult<'a, O>, 272 | c: impl Fn(&'a str) -> ParseResult<'a, O>, 273 | d: impl Fn(&'a str) -> ParseResult<'a, O>, 274 | e: impl Fn(&'a str) -> ParseResult<'a, O>, 275 | f: impl Fn(&'a str) -> ParseResult<'a, O>, 276 | g: impl Fn(&'a str) -> ParseResult<'a, O>, 277 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 278 | or6(a, b, c, d, e, or(f, g)) 279 | } 280 | 281 | /// Returns the second value and discards the first. 282 | pub fn preceded<'a, First, Second>( 283 | first: impl Fn(&'a str) -> ParseResult<'a, First>, 284 | second: impl Fn(&'a str) -> ParseResult<'a, Second>, 285 | ) -> impl Fn(&'a str) -> ParseResult<'a, Second> { 286 | map(pair(first, second), |(_, second)| second) 287 | } 288 | 289 | /// Returns the first value and discards the second. 290 | pub fn terminated<'a, First, Second>( 291 | first: impl Fn(&'a str) -> ParseResult<'a, First>, 292 | second: impl Fn(&'a str) -> ParseResult<'a, Second>, 293 | ) -> impl Fn(&'a str) -> ParseResult<'a, First> { 294 | map(pair(first, second), |(first, _)| first) 295 | } 296 | 297 | /// Gets a second value that is delimited by a first and third. 298 | pub fn delimited<'a, First, Second, Third>( 299 | first: impl Fn(&'a str) -> ParseResult<'a, First>, 300 | second: impl Fn(&'a str) -> ParseResult<'a, Second>, 301 | third: impl Fn(&'a str) -> ParseResult<'a, Third>, 302 | ) -> impl Fn(&'a str) -> ParseResult<'a, Second> { 303 | move |input| { 304 | let (input, _) = first(input)?; 305 | let (input, return_value) = second(input)?; 306 | let (input, _) = third(input)?; 307 | Ok((input, return_value)) 308 | } 309 | } 310 | 311 | /// Returns both results of the two combinators. 312 | pub fn pair<'a, First, Second>( 313 | first: impl Fn(&'a str) -> ParseResult<'a, First>, 314 | second: impl Fn(&'a str) -> ParseResult<'a, Second>, 315 | ) -> impl Fn(&'a str) -> ParseResult<'a, (First, Second)> { 316 | move |input| { 317 | let (input, first_value) = first(input)?; 318 | let (input, second_value) = second(input)?; 319 | Ok((input, (first_value, second_value))) 320 | } 321 | } 322 | 323 | /// Asserts that a combinator resolves. If backtracing occurs, returns a failure. 324 | pub fn assert_exists<'a, O>( 325 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 326 | message: &'static str, 327 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 328 | assert(combinator, |result| result.is_ok(), message) 329 | } 330 | 331 | /// Asserts that a given condition is true about the combinator. 332 | /// Otherwise returns an error with the message. 333 | pub fn assert<'a, O>( 334 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 335 | condition: impl Fn(Result<&(&'a str, O), &ParseError<'a>>) -> bool, 336 | message: &'static str, 337 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 338 | move |input| { 339 | let result = combinator(input); 340 | if condition(result.as_ref()) { 341 | result 342 | } else { 343 | match combinator(input) { 344 | Err(ParseError::Failure(err)) => { 345 | let mut message = message.to_string(); 346 | message.push_str("\n\n"); 347 | message.push_str(&err.message); 348 | ParseError::fail(err.input, message) 349 | } 350 | _ => ParseError::fail(input, message), 351 | } 352 | } 353 | } 354 | } 355 | 356 | /// Changes the input on a failure in order to provide 357 | /// a better error message. 358 | pub fn with_failure_input<'a, O>( 359 | new_input: &'a str, 360 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 361 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 362 | move |input| { 363 | let result = combinator(input); 364 | match result { 365 | Err(ParseError::Failure(mut err)) => { 366 | err.input = new_input; 367 | Err(ParseError::Failure(err)) 368 | } 369 | _ => result, 370 | } 371 | } 372 | } 373 | 374 | /// Provides some context to a failure. 375 | pub fn with_error_context<'a, O>( 376 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 377 | message: &'static str, 378 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 379 | move |input| match combinator(input) { 380 | Ok(result) => Ok(result), 381 | Err(ParseError::Backtrace) => Err(ParseError::Backtrace), 382 | Err(ParseError::Failure(err)) => { 383 | let mut message = message.to_string(); 384 | message.push_str("\n\n"); 385 | message.push_str(&err.message); 386 | ParseError::fail(err.input, message) 387 | } 388 | } 389 | } 390 | 391 | /// Keeps consuming a combinator into an array until a condition 392 | /// is met or backtracing occurs. 393 | pub fn many_till<'a, O, OCondition>( 394 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 395 | condition: impl Fn(&'a str) -> ParseResult<'a, OCondition>, 396 | ) -> impl Fn(&'a str) -> ParseResult<'a, Vec> { 397 | move |mut input| { 398 | let mut results = Vec::new(); 399 | while !input.is_empty() && is_backtrace(condition(input))? { 400 | match combinator(input) { 401 | Ok((result_input, value)) => { 402 | results.push(value); 403 | input = result_input; 404 | } 405 | Err(ParseError::Backtrace) => { 406 | return Ok((input, results)); 407 | } 408 | Err(err) => return Err(err), 409 | } 410 | } 411 | Ok((input, results)) 412 | } 413 | } 414 | 415 | /// Keeps consuming a combinator into an array until a condition 416 | /// is met or backtracing occurs. 417 | pub fn separated_list<'a, O, OSeparator>( 418 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 419 | separator: impl Fn(&'a str) -> ParseResult<'a, OSeparator>, 420 | ) -> impl Fn(&'a str) -> ParseResult<'a, Vec> { 421 | move |mut input| { 422 | let mut results = Vec::new(); 423 | while !input.is_empty() { 424 | match combinator(input) { 425 | Ok((result_input, value)) => { 426 | results.push(value); 427 | input = result_input; 428 | } 429 | Err(ParseError::Backtrace) => { 430 | return Ok((input, results)); 431 | } 432 | Err(err) => return Err(err), 433 | } 434 | input = match separator(input) { 435 | Ok((input, _)) => input, 436 | Err(ParseError::Backtrace) => break, 437 | Err(err) => return Err(err), 438 | }; 439 | } 440 | Ok((input, results)) 441 | } 442 | } 443 | 444 | /// Applies the combinator 0 or more times and returns a vector 445 | /// of all the parsed results. 446 | pub fn many0<'a, O>( 447 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 448 | ) -> impl Fn(&'a str) -> ParseResult<'a, Vec> { 449 | many_till(combinator, |_| ParseError::backtrace::<()>()) 450 | } 451 | 452 | /// Applies the combinator at least 1 time, but maybe more 453 | /// and returns a vector of all the parsed results. 454 | pub fn many1<'a, O>( 455 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 456 | ) -> impl Fn(&'a str) -> ParseResult<'a, Vec> { 457 | if_not_empty(many0(combinator)) 458 | } 459 | 460 | /// Skips the whitespace. 461 | pub fn skip_whitespace(input: &str) -> ParseResult<()> { 462 | match whitespace(input) { 463 | Ok((input, _)) => Ok((input, ())), 464 | // the next char was not a backtrace... continue. 465 | Err(ParseError::Backtrace) => Ok((input, ())), 466 | Err(err) => Err(err), 467 | } 468 | } 469 | 470 | /// Parses and expects whitespace. 471 | pub fn whitespace(input: &str) -> ParseResult<&str> { 472 | if input.is_empty() { 473 | return ParseError::backtrace(); 474 | } 475 | 476 | for (pos, c) in input.char_indices() { 477 | if !c.is_whitespace() { 478 | if pos == 0 { 479 | return ParseError::backtrace(); 480 | } 481 | return Ok((&input[pos..], &input[..pos])); 482 | } 483 | } 484 | 485 | Ok(("", input)) 486 | } 487 | 488 | /// Checks if a condition is true for a combinator. 489 | pub fn if_true<'a, O>( 490 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 491 | condition: impl Fn(&O) -> bool, 492 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 493 | move |input| { 494 | let (input, value) = combinator(input)?; 495 | if condition(&value) { 496 | Ok((input, value)) 497 | } else { 498 | ParseError::backtrace() 499 | } 500 | } 501 | } 502 | 503 | pub trait IsEmptyable { 504 | fn is_empty(&self) -> bool; 505 | } 506 | 507 | impl IsEmptyable for String { 508 | fn is_empty(&self) -> bool { 509 | self.is_empty() 510 | } 511 | } 512 | 513 | impl<'a> IsEmptyable for &'a str { 514 | fn is_empty(&self) -> bool { 515 | (*self).is_empty() 516 | } 517 | } 518 | 519 | impl IsEmptyable for Vec { 520 | fn is_empty(&self) -> bool { 521 | self.is_empty() 522 | } 523 | } 524 | 525 | /// Checks if the combinator result is not empty. 526 | pub fn if_not_empty<'a, R: IsEmptyable>( 527 | combinator: impl Fn(&'a str) -> ParseResult<'a, R>, 528 | ) -> impl Fn(&'a str) -> ParseResult<'a, R> { 529 | if_true(combinator, |items| !items.is_empty()) 530 | } 531 | 532 | /// Checks if a combinator is false without consuming the input. 533 | pub fn check_not<'a, O>( 534 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 535 | ) -> impl Fn(&'a str) -> ParseResult<'a, ()> { 536 | move |input| match combinator(input) { 537 | Ok(_) => ParseError::backtrace(), 538 | Err(_) => Ok((input, ())), 539 | } 540 | } 541 | 542 | /// Logs the result for quick debugging purposes. 543 | #[cfg(debug_assertions)] 544 | #[allow(dead_code)] 545 | pub fn log_result<'a, O: std::fmt::Debug>( 546 | prefix: &'static str, 547 | combinator: impl Fn(&'a str) -> ParseResult<'a, O>, 548 | ) -> impl Fn(&'a str) -> ParseResult<'a, O> { 549 | move |input| { 550 | let result = combinator(input); 551 | println!("{} (input): {:?}", prefix, input); 552 | println!("{} (result): {:#?}", prefix, result); 553 | result 554 | } 555 | } 556 | 557 | fn is_backtrace(result: ParseResult) -> Result { 558 | match result { 559 | Ok(_) => Ok(false), 560 | Err(ParseError::Backtrace) => Ok(true), 561 | Err(err) => Err(err), 562 | } 563 | } 564 | 565 | #[cfg(test)] 566 | mod test { 567 | use super::*; 568 | 569 | #[test] 570 | fn error_with_code_formatting() { 571 | let error = ParseErrorFailureError { 572 | message: "Message.".to_string(), 573 | code_snippet: Some("code".to_string()), 574 | }; 575 | assert_eq!(error.to_string(), "Message.\n code\n ~"); 576 | } 577 | } 578 | --------------------------------------------------------------------------------