【问题标题】:Parsing an indentation based language using scala parser combinators使用 scala 解析器组合器解析基于缩进的语言
【发布时间】:2023-03-12 10:49:01
【问题描述】:

有没有一种方便的方法来使用 Scala 的解析器组合器来解析缩进很重要的语言? (例如 Python)

【问题讨论】:

  • 使用override val skipWhitespace = false

标签: parsing scala indentation parser-combinators


【解决方案1】:

假设我们有一个非常简单的语言,这是一个有效的程序

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
  ....

【讨论】:

    【解决方案2】:

    据我所知,不,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
    }
    }
    

    然后,您可以使用组合子库以更简单的方式进行解析。

    希望对你有帮助

    【讨论】:

      猜你喜欢
      • 2017-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-01
      • 1970-01-01
      • 2020-01-17
      • 2012-05-03
      • 1970-01-01
      相关资源
      最近更新 更多