Има ли удобен начин за използване на комбинаторите за анализатор на Scala за анализиране на езици, където отстъпът е значителен? (напр. Python)
Разбор на език, базиран на отстъп, с помощта на комбинатори за анализатор на scala
Отговори (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
....
От това, което знам, не, комбинаторите за синтактични анализатори на 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
}
}
И след това можете да използвате библиотеката на комбинатора, за да направите анализирането по по-прост начин.
Надявам се това да помогне
override val skipWhitespace = false
- person senia   schedule 20.11.2012