2009-11-29 4 views
11

Je me demande s'il est possible d'obtenir les données MatchData générées à partir de l'expression régulière correspondante dans la grammaire ci-dessous.Accéder aux données de correspondance d'expressions régulières de Scala Parser

object DateParser extends JavaTokenParsers { 

    .... 

    val dateLiteral = """(\d{4}[-/])?(\d\d[-/])?(\d\d)""".r ^^ { 
     ... get MatchData 
    } 
} 

Une option est bien sûr d'effectuer le match à nouveau à l'intérieur du bloc, mais puisque le RegexParser a déjà effectué le match, je suis en espérant que cela passe le MatchData au bloc, ou stocke?

Répondre

20

Voici la définition implicite qui convertit votre Regex en Parser.

/** A parser that matches a regex string */ 
    implicit def regex(r: Regex): Parser[String] = new Parser[String] { 
    def apply(in: Input) = { 
     val source = in.source 
     val offset = in.offset 
     val start = handleWhiteSpace(source, offset) 
     (r findPrefixMatchOf (source.subSequence(start, source.length))) match { 
     case Some(matched) => 
      Success(source.subSequence(start, start + matched.end).toString, 
        in.drop(start + matched.end - offset)) 
     case None => 
      Failure("string matching regex `"+r+"' expected but `"+in.first+"' found", in.drop(start - offset)) 
     } 
    } 
    } 

adapter Il suffit:

object X extends RegexParsers { 
    /** A parser that matches a regex string and returns the Match */ 
    def regexMatch(r: Regex): Parser[Regex.Match] = new Parser[Regex.Match] { 
    def apply(in: Input) = { 
     val source = in.source 
     val offset = in.offset 
     val start = handleWhiteSpace(source, offset) 
     (r findPrefixMatchOf (source.subSequence(start, source.length))) match { 
     case Some(matched) => 
      Success(matched, 
        in.drop(start + matched.end - offset)) 
     case None => 
      Failure("string matching regex `"+r+"' expected but `"+in.first+"' found", in.drop(start - offset)) 
     } 
    } 
    } 
    val t = regexMatch("""(\d\d)/(\d\d)/(\d\d\d\d)""".r) ^^ { case m => (m.group(1), m.group(2), m.group(3)) } 
} 

Exemple:

scala> X.parseAll(X.t, "23/03/1971") 
res8: X.ParseResult[(String, String, String)] = [1.11] parsed: (23,03,1971) 
+0

Merci Daniel, fonctionne comme un charme –

+0

Great post! Excellent! – fotNelton

+0

C'est curieux, pourquoi ce genre de fonctionnalité ne fait pas partie de la mise en œuvre de la classe standard (bibliothèque)? Cela semble très utile, mais chaque utilisateur devrait l'implémenter lui-même ... –

1

Lorsqu'un Regex est utilisé dans un exemple de RegexParsers, l'expression rationnelle def implicite (Regex): Parser [chaîne] dans RegexParsers est utilisé pour appoly que Regex à l'entrée. L'occurrence Match obtenue lors de l'application réussie de RE à l'entrée courante est utilisée pour construire une méthode Success dans la méthode regex(), mais seule sa valeur «end» est utilisée, de sorte que les sous-correspondances capturées sont supprimées au moment où cette méthode résultats.

En l'état (dans la source 2.7 que j'ai regardé), vous n'avez pas de chance, je crois.

3

Non, vous ne pouvez pas faire cela. Si vous regardez la définition du Parser utilisé lors de la conversion d'une expression rationnelle à un Parser, il jette tout contexte et juste retourne la chaîne complète adaptée:

http://lampsvn.epfl.ch/trac/scala/browser/scala/tags/R_2_7_7_final/src/library/scala/util/parsing/combinator/RegexParsers.scala?view=markup#L55

Vous avez deux autres options, bien que :

  • briser votre analyseur en plusieurs parseurs plus petits (pour les jetons que vous voulez réellement extraire)
  • définir un analyseur personnalisé qui extrait les valeurs que vous voulez et retourne un objet de domaine au lieu d'une chaîne

La première ressemblerait

val separator = "-" | "/" 
    val year = ("""\d{4}"""r) <~ separator 
    val month = ("""\d\d"""r) <~ separator 
    val day = """\d\d"""r 

    val date = ((year?) ~ (month?) ~ day) map { 
    case year ~ month ~ day => 
     (year.getOrElse("2009"), month.getOrElse("11"), day) 
    } 

Les <~ signifie « exiger que ces deux jetons ensemble, mais seulement me donner le résultat de la première.

Les ~ moyens « exigent ces deux jetons ensemble et de les attacher ensemble dans un motif matchable ~ objet.

Le ? signifie que l'analyseur est en option et retourne une option.

Le bit .getOrElse fournit une valeur par défaut lorsque l'analyseur n'a pas défini une valeur

+0

Merci David, belle Solution. Je vais aller avec la solution de l'analyseur personnalisé car elle garde la définition de la grammaire plus lisible. –

+1

Maintenant que j'y pense, un analyseur personnalisé est également plus correct. Chaque analyseur d'expression rationnelle individuel autorise les espaces de début, de sorte que le code que j'ai posté correspondrait également aux chaînes comme "1999 - 02 - 28". –

0

je suis tombé sur un problème similaire en utilisant scala 2.8.1 et d'essayer d'analyser l'entrée de la forme « nom: valeur » en utilisant la RegexParsers classe:

package scalucene.query 

import scala.util.matching.Regex 
import scala.util.parsing.combinator._ 

object QueryParser extends RegexParsers { 
    override def skipWhitespace = false 

    private def quoted = regex(new Regex("\"[^\"]+")) 
    private def colon = regex(new Regex(":")) 
    private def word = regex(new Regex("\\w+")) 
    private def fielded = (regex(new Regex("[^:]+")) <~ colon) ~ word 
    private def term = (fielded | word | quoted) 

    def parseItem(str: String) = parse(term, str) 
} 

Il semble que vous pouvez saisir les groupes appariés après l'analyse syntaxique comme ceci:

QueryParser.parseItem("nameExample:valueExample") match { 
    case QueryParser.Success(result:scala.util.parsing.combinator.Parsers$$tilde, _) => { 
     println("Name: " + result.productElement(0) + " value: " + result.productElement(1)) 
    } 
} 
Questions connexes