Reputation: 3914
I would like to compose a parser from another parser to have the consumed input as an argument to the ast construction.
Say I have
def ingredient = amount ~ nameOfIngredient ^^ {
case amount ~ name => Ingredient(name, amount)
}
What I'm looking for is a way to have another parser to construct an element of:
case class RecipeRow(orginalText: String, ingredient: Ingredient)
So I'm looking for a way to retrieve the original consumed input to the parser in a composition. Maybe something like:
def recipeRow = ingredient withConsumedInput ^^ {
case (amount ~ name, consumed) => RecipeRow(consumed, Ingredient(name, amount))
}
I guess the signature in this case would be:
def withConsumedInput [U](p: => Parser[U]): Parser[(U, String)]
Is there another simple way to get what I want or do I need to write that thing? It feels like it probably is a better way…
Upvotes: 2
Views: 317
Reputation: 297155
Not easy, actually.
Let's start with Parser
: what can it give us? Well, a Parser
extends Input => ParseResult
, so we have to extract the information from either one.
The type Input
is an alias, on RegexParsers
anyway, for scala.util.parsing.input.Reader[Char]
. There's very little there to help us, unless it happens to be a Reader
of CharSequence
, in which case we can use source
and offset
. Let's use that then.
Now, ParseResult
has many subclasses, but we are only interested in Success
, which has a next: Input
field. Using that, we may try this:
def withConsumedInput [U](p: => Parser[U]): Parser[(U, String)] = new Parser[(U, String)] {
def apply(in: Input) = p(in) match {
case Success(result, next) =>
val parsedString = in.source.subSequence(in.offset, next.offset).toString
Success(result -> parsedString, next)
case other: NoSuccess => other
}
}
It will capture any skipped whitespaces, though. You can adapt it to avoid that automatically:
def withConsumedInput [U](p: => Parser[U]): Parser[(U, String)] = new Parser[(U, String)] {
def apply(in: Input) = p(in) match {
case Success(result, next) =>
val parsedString = in.source.subSequence(handleWhiteSpace(in.source, in.offset), next.offset).toString
Success(result -> parsedString, next)
case other: NoSuccess => other
}
}
Upvotes: 4