2010-07-30 17 views
5

Je devais analyser les espaces réservés en texte comme abc $$FOO$$ cba. J'ai piraté ensemble quelque chose avec les combinateurs d'analyseurs de Scala, mais je ne suis pas vraiment content de la solution.Comment analyser les espaces réservés à partir du texte sans jeter votre épée afin de pouvoir combattre les maraudeurs avec un abat-jour

En particulier, j'ai utilisé un matcher de largeur nulle dans l'expression régulière (?=(\$\$|\z)) pour arrêter l'analyse du texte et commencer à analyser les espaces réservés. Cela semble dangereusement proche des manigances discutées et colorées rejeté sur le scala mailing list (qui a inspiré le titre de cette question.)

Donc, le défi: réparer mon analyseur pour travailler sans ce hack. J'aimerais voir une progression claire du problème à votre solution, afin de pouvoir remplacer ma stratégie d'assemblage aléatoire de combinateurs jusqu'à la réussite des tests.

import scala.util.parsing.combinator.RegexParsers 

object PlaceholderParser extends RegexParsers { 
    sealed abstract class Element 
    case class Text(text: String) extends Element 
    case class Placeholder(key: String) extends Element 

    override def skipWhitespace = false 

    def parseElements(text: String): List[Element] = parseAll(elements, text) match { 
    case Success(es, _) => es 
    case NoSuccess(msg, _) => error("Could not parse: [%s]. Error: %s".format(text, msg)) 
    } 

    def parseElementsOpt(text: String): ParseResult[List[Element]] = parseAll(elements, text) 

    lazy val elements: Parser[List[Element]] = rep(element) 
    lazy val element: Parser[Element] = placeholder ||| text 
    lazy val text: Parser[Text] = """(?ims).+?(?=(\$\$|\z))""".r ^^ Text.apply 
    lazy val placeholder: Parser[Placeholder] = delimiter ~> """[\w. ]+""".r <~ delimiter ^^ Placeholder.apply 
    lazy val delimiter: Parser[String] = literal("$$") 
} 


import org.junit.{Assert, Test} 

class PlaceholderParserTest { 
    @Test 
    def parse1 = check("a quick brown $$FOX$$ jumped over the lazy $$DOG$$")(Text("a quick brown "), Placeholder("FOX"), Text(" jumped over the lazy "), Placeholder("DOG")) 

    @Test 
    def parse2 = check("a quick brown $$FOX$$!")(Text("a quick brown "), Placeholder("FOX"), Text("!")) 

    @Test 
    def parse3 = check("a quick brown $$FOX$$!\n!")(Text("a quick brown "), Placeholder("FOX"), Text("!\n!")) 

    @Test 
    def parse4 = check("a quick brown $$F.O X$$")(Text("a quick brown "), Placeholder("F.O X")) 

    def check(text: String)(expected: Element*) = Assert.assertEquals(expected.toList, parseElements(text)) 
} 
+0

Il peut être plus simple d'exécuter d'abord une lexer basée sur une expression rationnelle, en divisant l'entrée en jetons qui sont "$$" ou "une chaîne ne contenant pas $$". D'ailleurs, puisque vos délimiteurs ne correspondent pas à des paires imbriquées, n'est-ce pas simplement une langue régulière? Ce que vous faites ressemble plus à "couvrir une lampe" qu'à "combattre des maraudeurs". –

+1

Sonne comme un pas dans la bonne direction. Comment devrais-je choisir comment diviser le travail entre le lexer et le scanner? – retronym

+0

erm, lexer et parser. – retronym

Répondre

2

J'ai trouvé une autre approche. Il n'y a plus de hack regex, mais le code est un peu plus long. Il analyse la chaîne entière en une liste de caractères simples ou Placeholder objets. La fonction compact compacte alors la liste (par exemple, il convertit les chaînes consécutives à Text objets et ne touche pas les objets Placeholder):

object PlaceholderParser extends RegexParsers { 
    sealed abstract class Element 
    case class Text(text: String) extends Element 
    case class Placeholder(key: String) extends Element 

    override def skipWhitespace = false 

    def parseElements(text: String): List[Element] = parseAll(elements, text) match { 
    case Success(es, _) => es 
    case NoSuccess(msg, _) => error("Could not parse: [%s]. Error: %s".format(text, msg)) 
    } 

    def parseElementsOpt(text: String): ParseResult[List[Element]] = parseAll(elements, text) 

    def compact(l: List[Any]): List[Element] = { 
    val builder = new StringBuilder() 
    val r = l.foldLeft(List.empty[Element])((l, e) => e match { 
     case s: String => 
     builder.append(s) 
     l 
     case p: Placeholder => 
     val t = if (builder.size > 0) { 
      val k = l ++ List(Text(builder.toString)) 
      builder.clear 
      k 
     } else { 
      l 
     } 
     t ++ List(p) 
    }) 
    if (builder.size > 0) r ++ List(Text(builder.toString)) else r 
    } 

    lazy val elements: Parser[List[Element]] = (placeholder ||| text).+ ^^ compact 
    lazy val text: Parser[String] = """(?ims).""".r 
    lazy val placeholder: Parser[Placeholder] = delimiter ~> """[\w. ]+""".r <~ delimiter ^^ Placeholder.apply 
    lazy val delimiter: Parser[String] = literal("$$") 
} 

Ce n'est pas une solution parfaite, mais peut-être quelque chose que vous pouvez commencer.