2012-11-20 25 views

Odpowiedz

5

Załóżmy, że mamy bardzo prostym językiem, gdzie jest to poprawny program

block 
    inside 
    the 
    block 

i chcemy analizować to do List[String] z każdej linii wewnątrz bloku jako jednego String.

Najpierw definiujemy metodę, która ma minimalny poziom wcięcia i zwraca analizator składni dla linii o tym poziomie wcięcia.

def line(minIndent:Int):Parser[String] = 
    repN(minIndent + 1,"\\s".r) ~ ".*".r ^^ {case s ~ r => s.mkString + r} 

Następnie określają blok z minimalnym poziomie wcięcia powtarzając parser linii z odpowiednim separatorze między liniami.

def lines(minIndent:Int):Parser[List[String]] = 
    rep1sep(line(minIndent), "[\n\r]|(\n\r)".r) 

Teraz możemy zdefiniować parser dla naszego małego języka tak:

val block:Parser[List[String]] = 
    (("\\s*".r <~ "block\\n".r) ^^ { _.size }) >> lines 

Najpierw określa aktualny poziom wcięcia, a następnie przekazuje, że jako minimum do parsera linii. Przetestujmy go:

val s = 
"""block 
    inside 
    the 
    block 
outside 
the 
block""" 

println(block(new CharSequenceReader(s))) 

i otrzymujemy

[4.10] parsed: List( inside,  the,  block) 

Za to wszystko skompilować, trzeba przywóz

import scala.util.parsing.combinator.RegexParsers 
import scala.util.parsing.input.CharSequenceReader 

i trzeba postawić wszystko na obiekt, który rozciąga RegexParsers tak jak

object MyParsers extends RegexParsers { 
    override def skipWhitespace = false 
    .... 
1

Z tego, co wiem, nie, kombinatory parsera Scala nie mają wsparcia dla tego typu rzeczy po wyjęciu z pudełka. Z pewnością można to zrobić, parsując białą przestrzeń w znaczący sposób, ale napotkasz pewne problemy, ponieważ potrzebujesz jakiejś formy automatu stanów, aby śledzić stos wcięć.

Polecam zrobić krok preprocessing. Tutaj jest trochę preprocesor, który dodaje znaczniki do oddzielenia wcięte bloki:

object Preprocessor { 

    val BlockStartToken = "{" 
    val BlockEndToken = "}" 

    val TabSize = 4 //how many spaces does a tab take 

    def preProcess(text: String): String = { 
     val lines = text.split('\n').toList.filterNot(_.forall(isWhiteChar)) 
     val processedLines = BlockStartToken :: insertTokens(lines, List(0)) 
     processedLines.mkString("\n") 
    } 

    def insertTokens(lines: List[String], stack: List[Int]): List[String] = lines match { 
     case List() => List.fill(stack.length) { BlockEndToken } //closing all opened blocks 
     case line :: rest => { 
      (computeIndentation(line), stack) match { 
       case (indentation, top :: stackRest) if indentation > top => { 
        BlockStartToken :: line :: insertTokens(rest, indentation :: stack) 
       } 
       case (indentation, top :: stackRest) if indentation == top => 
        line :: insertTokens(rest, stack) 
       case (indentation, top :: stackRest) if indentation < top => { 
        BlockEndToken :: insertTokens(lines, stackRest) 
       } 
       case _ => throw new IllegalStateException("Invalid algorithm") 
      } 
     } 
    } 


    private def computeIndentation(line: String): Int = { 
     val whiteSpace = line takeWhile isWhiteChar 
     (whiteSpace map { 
      case ' ' => 1 
      case '\t' => TabSize 
     }).sum 
    } 

    private def isWhiteChar(ch: Char) = ch == ' ' || ch == '\t' 
} 

Wykonanie tego tekstu otrzymujemy:

val text = 
    """ 
     |line1 
     |line2 
     | line3 
     | line4 
     | line5 
     |  line6 
     |  line7 
     | line8 
     | line9 
     |line10 
     | line11 
     | line12 
     | line13 
    """.stripMargin 
println(Preprocessor.preProcess(text)) 

... następujący wynik

{ 
line1 
line2 
{ 
    line3 
    line4 
    line5 
{ 
     line6 
     line7 
} 
} 
{ 
    line8 
    line9 
} 
line10 
{ 
    line11 
    line12 
    line13 
} 
} 

I Afterwords można użyj biblioteki kombinatorów, aby wykonać parsowanie w prostszy sposób.

Mam nadzieję, że to pomoże