【问题标题】:How to ignore single line comments in a parser-combinator如何忽略解析器组合器中的单行注释
【发布时间】:2014-01-04 19:07:16
【问题描述】:

我有一个可以工作的解析器,但我刚刚意识到我不适合 cmets。在我正在解析的 DSL 中,cmets 以 ; 字符开头。如果遇到;,则忽略该行的rest(但不是全部,除非第一个字符是;)。

我正在为我的解析器扩展 RegexParsers 并忽略空格(默认方式),所以无论如何我都会丢失换行符。我也不希望修改每个解析器来满足 cmets 的可能性,因为语句可以跨越多行(因此每个语句的每个部分都可能以注释结尾)。有什么干净的方法可以实现这个吗?

【问题讨论】:

    标签: scala parser-combinators


    【解决方案1】:

    可能会影响您选择的一件事是是否可以在您的有效解析器中找到 cmets。例如,假设您有类似的东西:

    val p = "(" ~> "[a-z]*".r <~ ")"
    

    它会解析 ( abc ) 之类的东西,但由于 cmets 你实际上可能会遇到类似的东西:

    ( ; comment goes here
      abc
    )
    

    那么我会推荐使用TokenParser 或其子类之一。这是更多的工作,因为您必须提供一个词法解析器,该解析器将首先通过丢弃 cmets。但是,如果您有嵌套的 cmets,或者如果 ; 可以转义,或者如果 ; 可以在字符串文字内,它也会更加灵活,例如:

    abc = "; don't ignore this" ; ignore this
    

    另一方面,您也可以尝试覆盖 whitespace 的值,使其类似于

    override protected val whiteSpace = """(\s|;.*)+""".r
    

    或者类似的东西。 例如使用 RegexParsers scaladoc 中的示例:

    import scala.util.parsing.combinator.RegexParsers
    
    object so1 {
      Calculator("""(1 + ; foo
      (1 + 2))
      ; bar""")
    }
    
    object Calculator extends RegexParsers {
      override protected val whiteSpace = """(\s|;.*)+""".r
      def number: Parser[Double] = """\d+(\.\d*)?""".r ^^ { _.toDouble }
      def factor: Parser[Double] = number | "(" ~> expr <~ ")"
      def term: Parser[Double] = factor ~ rep("*" ~ factor | "/" ~ factor) ^^ {
        case number ~ list => (number /: list) {
          case (x, "*" ~ y) => x * y
          case (x, "/" ~ y) => x / y
        }
      }
      def expr: Parser[Double] = term ~ rep("+" ~ log(term)("Plus term") | "-" ~ log(term)("Minus term")) ^^ {
        case number ~ list => list.foldLeft(number) { // same as before, using alternate name for /:
          case (x, "+" ~ y) => x + y
          case (x, "-" ~ y) => x - y
        }
      }
      def apply(input: String): Double = parseAll(expr, input) match {
        case Success(result, _) => result
        case failure: NoSuccess => scala.sys.error(failure.msg)
      }
    }
    

    打印出来:

    Plus term --> [2.9] parsed: 2.0
    Plus term --> [2.10] parsed: 3.0
    res0: Double = 4.0
    

    【讨论】:

    • 我已经在 Scala 中使用过很多解析器组合器库,我建议将TokenParser 用于除了最琐碎的解析器之外的任何东西。首先,首先标记化使解析器更快更简单。其次,如果没有第二个标记阶段,很难让解析器正确。我在尝试区分关键字和标识符时遇到了各种各样的问题。例如,我发现我的解析器会以相同的方式处理 if x then y else zifxthenyelsez,除非我在正则表达式中添加了一堆否定的前瞻内容。
    • 感谢您的 cmets。空白覆盖似乎很有趣。现在我使用RegexParsers 完成了我的解析器,我不想再次更改所有内容以使用ToeknParser,尽管我还不知道其中的区别。
    • @DaoWen 你得到ifxthenyelsez 的原因可能是因为你的正则表达式的结构方式。我有类似的问题,你可能想检查一下:stackoverflow.com/questions/20793058/…
    • @huynhjl 我尝试了空格覆盖方法,但它似乎不起作用。我也以这种方式尝试过:override protected val whiteSpace = """(;.*\n)|(\s+)""".r,希望它在空格删除结尾 \n 之前先贪婪地捕获 cmets。虽然不知道发生了什么,但它仍然会在以 ; 开头的行上抱怨,尽管文件中的第一行是注释被跳过了。
    • @jbx - """(\s|;.*)+""".r 也应该可以工作,而且它可能更容易阅读。原始正则表达式的问题在于它只匹配 one 注释。你的和我刚刚提供的都允许多个 cmets。需要注意的一点是,正则表达式中的. 默认情况下 匹配换行符(尽管我很确定您可以启用一个选项来更改它),但是 \s 确实匹配换行符。
    【解决方案2】:

    在将代码传递到解析器之前,只需使用正则表达式过滤掉所有 cmets。

    def removeComments(input: String): String = {
      """(?ms)\".*?\"|;.*?$|.+?""".r.findAllIn(input).map(str => if(str.startsWith(";")) "" else str).mkString
    }
    
    val code =
    """abc "def; ghij"
    abc ;this is a comment
    def"""
    
    println(removeComments(code))
    

    【讨论】:

    • 是的,但是如果我使用Reader,这将不起作用,并且如果我使用Positional 输出来了解解析失败的位置,也会干扰行号定位。
    • 我不认为在这里使用惰性量词是一个好主意。这可能会使正则表达式匹配少于该行的其余部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-21
    • 1970-01-01
    • 2022-06-29
    • 2015-04-30
    相关资源
    最近更新 更多