2010-07-30 6 views
5

Potrzebowałem parsować symbole zastępcze z tekstu takiego jak abc $$FOO$$ cba. Dołączyłem coś do kombinatorów parsera Scali, ale nie jestem zadowolony z rozwiązania.Jak analizować symbole zastępcze z tekstu bez wyrzucania miecza, aby można było odeprzeć maruderów za pomocą abażuru

W szczególności użyłem wycinaka o zerowej szerokości w wyrażeniu regularnym (?=(\$\$|\z)), aby przerwać analizowanie tekstu i rozpocząć analizowanie symboli zastępczych. Brzmi to niebezpiecznie blisko omawianych shenaniganów i barwnie odrzuconych na scala mailing list (co zainspirowało tytuł tego pytania).

Wyzwanie: popraw mój parser, aby działał bez tego ataku hakerskiego. Chciałbym widzieć wyraźny postęp od problemu do rozwiązania, więc mogę zastąpić moją strategię losowego łączenia kombinatorów, aż testy przejdą.

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

Najpierw może być prostsze uruchamianie lexera opartego na regexp, dzieląc dane wejściowe na tokeny, które są "$$" lub "ciąg nie zawierający $$". Jeśli chodzi o to, że ograniczniki nie pasują do par gniazd, czy to nie jest zwykły język? To, co robisz, wygląda bardziej jak "zasłanianie lampy" niż "walczenie z maruderami". –

+1

Brzmi jak krok we właściwym kierunku. Jak powinienem wybrać sposób dzielenia pracy między lexerem a skanerem? – retronym

+0

erm, lexer i parser. – retronym

Odpowiedz

2

Znalazłem inne podejście. Nie ma już regexu, ale kod jest trochę dłuższy. Parsuje cały ciąg do listy pojedynczych znaków lub obiektów Placeholder. Funkcja compact następnie zagęszcza listy (to znaczy przekształca kolejne ciągi do Text przedmiotów i nie dotykaj Placeholder obiektów):

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("$$") 
} 

Nie jest to idealne rozwiązanie, ale może coś można zacząć.

Powiązane problemy