Разбор на език, базиран на отстъп, с помощта на комбинатори за анализатор на scala

Има ли удобен начин за използване на комбинаторите за анализатор на Scala за анализиране на езици, където отстъпът е значителен? (напр. Python)


person Rob Lachlan    schedule 20.11.2012    source източник
comment
Използвайте override val skipWhitespace = false   -  person senia    schedule 20.11.2012


Отговори (2)


Да приемем, че имаме много прост език, където това е валидна програма

block
  inside
  the
  block

и искаме да анализираме това в List[String] с всеки ред вътре в блока като едно String.

Първо дефинираме метод, който взема минимално ниво на отстъп и връща анализатор за ред с това ниво на отстъп.

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

След това дефинираме блок с минимално ниво на отстъп, като повтаряме анализатора на редовете с подходящ разделител между редовете.

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

Сега можем да дефинираме анализатор за нашия малък език по следния начин:

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

Първо определя текущото ниво на отстъп и след това го предава като минимум на анализатора на редовете. Нека го тестваме:

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

println(block(new CharSequenceReader(s)))

И получаваме

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

За да компилирате всичко това, имате нужда от тези импортирания

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

И трябва да поставите всичко в обект, който разширява RegexParsers ето така

object MyParsers extends RegexParsers {
  override def skipWhitespace = false
  ....
person Kim Stebel    schedule 20.11.2012

От това, което знам, не, комбинаторите за синтактични анализатори на Scala нямат поддръжка за този вид неща извън кутията. Със сигурност можете да го направите, като анализирате празното пространство по смислен начин, но ще срещнете някои проблеми, тъй като имате нужда от някаква форма на държавна машина, за да следите стека с отстъпи.

Бих препоръчал да направите стъпка на предварителна обработка. Ето малък препроцесор, който добавя маркери към отделни блокове с отстъп:

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'
}

Изпълнението на този текст дава:

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

... следния резултат

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

И след това можете да използвате библиотеката на комбинатора, за да направите анализирането по по-прост начин.

Надявам се това да помогне

person Marius Danila    schedule 20.11.2012