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))
}
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". –
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
erm, lexer y analizador. – retronym