Skip to content

Get range of parsed input #68

Closed
Closed

Description

@dehlen

Hi,

I am struggling to find a way to get the range of my parsed input and I am not sure whether this is something which would be useful to this library.

For example consider the following use case:
I am writing an ingredient parser and I want to make sense of a string by parsing the ingredient amount and unit. But I also want to highlight the parse amount and unit when displaying the ingredient string in a NSAttributedString. From my understand I would need to know which range the parser combinator parsed.

Here is a simple example:

final class IngredientParser {
    func parse(_ ingredients: [String]) -> [Ingredient] {
        return ingredients.compactMap {
            parseLine($0.trimmingCharacters(in: .whitespacesAndNewlines))
        }
    }

    func parse(_ ingredientsInput: String) -> [Ingredient] {
        return parse(ingredientsInput.split(separator: "\n").map { String($0) })
    }
    
    private func parseLine(_ ingredientsInput: String) -> Ingredient? {        
        let amount: AnyParser<Substring.UTF8View, Int> = Int.parser()
            .eraseToAnyParser()

        let unit: AnyParser<Substring.UTF8View, Unit> = "gram".utf8.map { Unit.gram }.eraseToAnyParser()
        
        let line = Optional.parser(of: amount)
            .skip(Whitespace())
            .take(Optional.parser(of: unit))
            .skip(Whitespace())
            .take(Rest())
            .map { result -> Ingredient in
                return Ingredient(amount: result.0, unit: result.1, name: result.2)
            }
        
        var input = ingredientsInput[...].utf8
        return line.parse(&input)
    }
}

Would it be possible to get the parsed range of f.e the Int.parser() from parsing the amount?

Activity

  1. stephencelis commented on Nov 29, 2021

    @stephencelis
    Member

    Hi @dehlen,

    One way to capture the range of a parsed result is by introducing a new operator that captures it. Here's something @mbrandonw and I sketched out:

    extension Parser where Input: Collection {
      func withRange() -> AnyParser<Input, (output: Output, range: Range<Input.Index>)> {
        .init { input in
          let startIndex = input.startIndex
          guard let output = self.parse(&input)
          else { return nil }
          let endIndex = input.startIndex
          return (output, startIndex ..< endIndex)
        }
      }
    }

    With that defined, you can get the range from Int.parser() by tacking on withRange():

    let parser = Int.parser().withRange()
    let input = "123 Hello"[...].utf8
    let (output, range) = parser.parse(&input)!
    XCTAssertEqual(123, output)
    XCTAssertEqual(3, input.distance(from: range.lowerBound, to: range.upperBound))

    Hope this helps!

    I'm going to convert this to a discussion, since it's not a bug with the library.

  2. locked and limited conversation to collaborators on Nov 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions