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))
}
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". –
Brzmi jak krok we właściwym kierunku. Jak powinienem wybrać sposób dzielenia pracy między lexerem a skanerem? – retronym
erm, lexer i parser. – retronym