2010-07-30 5 views
5

Necesitaba analizar los marcadores de posición de texto como abc $$FOO$$ cba. Intenté juntar algo con los combinadores de analizadores de Scala, pero no estoy muy contento con la solución.Cómo analizar los marcadores de posición del texto sin tirar la espada para poder luchar contra los intrusos con una pantalla de lámpara

En particular, recurrí a un matcher de ancho cero en la expresión regular (?=(\$\$|\z)) para detener el análisis del texto y comenzar a analizar los marcadores de posición. Esto suena peligrosamente cerca de los chanchullos discutidos y colorido despedidos en el scala mailing list

Por lo tanto, el reto (que inspiró el título de esta pregunta.): Arreglar mi analizador de trabajar sin este truco. Me gustaría ver una clara progresión del problema a su solución, de modo que pueda reemplazar mi estrategia de ensamblar ensambladores aleatoriamente hasta que pasen las pruebas.

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

Puede ser más simple ejecutar un lexer basado en expresiones regulares primero, dividiendo la entrada en tokens que son "$$" o "una cadena que no contiene $$". En realidad, dado que los delimitadores no coinciden en pares encajables, ¿no es esto un lenguaje común? Lo que estás haciendo se parece más a "cubrir una lámpara" que a "luchar contra los merodeadores". –

+1

Suena como un paso en la dirección correcta. ¿Cómo debería elegir cómo dividir el trabajo entre el Lexer y el escáner? – retronym

+0

erm, lexer y analizador. – retronym

Respuesta

2

Encontré otro enfoque. Ya no hay hack regex, pero el código es un poco más largo. Analiza toda la cadena a una lista de caracteres individuales o objetos Placeholder. La función compact continuación, compacta la lista (es decir, que convierte las cadenas consecutivas a Text objetos y no toca los objetos 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("$$") 
} 

No es una solución perfecta, pero tal vez algo que puede empezar.

Cuestiones relacionadas