【问题标题】:How to test for texts not fitting an Instaparse-grammar (Clojure)?如何测试不符合 Instaparse 语法(Clojure)的文本?
【发布时间】:2014-10-13 11:28:31
【问题描述】:

我在 Instaparse (Clojure) 中编写了一个使用上下文无关语法解析字符串的项目。现在我想测试几个输入字符串的解析结果。某些输入字符串可能不适合语法。到目前为止,我只测试了“不符合预期的解析字符串”。但我认为使用(is (thrown? ...)) 测试异常会更准确。是否抛出异常?在我看来,生成了一些输出(包含Parse error...),但没有抛出异常。

我的 project.clj 是:

(defproject com.stackoverflow.clojure/tests "0.1.0-SNAPSHOT"
  :description "Tests of Clojure test-framework."
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [instaparse "1.3.4"]])

我的核心来源是:

(ns com.stackoverflow.clojure.testInstaparseWrongGrammar
  (:require [instaparse.core :as insta]))

(def parser (insta/parser "
    <sentence> = words <DOT>
    DOT        = '.'
    <words>    = word (<SPACE> word)*
    SPACE      = ' '
    word     = #'(?U)\\w+'
"))

(defn formatter [expr] 
  (->> (parser expr)
       (insta/transform {:word identity})
       (apply str)))

我的测试来源是:

(ns com.stackoverflow.clojure.testInstaparseWrongGrammar-test
  (:require [clojure.test :refer :all]
            [com.stackoverflow.clojure.testInstaparseWrongGrammar :refer :all]))

(deftest parser-tests
  (is (= [[:word "Hello"] [:word "World"]] (parser "Hello World.")))
  (is (not (= [[:word "Hello"] [:word "World"]] (parser "Hello World?"))))
  ;(parser "Hello World?")     gives:
  ;
  ;Parse error at line 1, column 12:
  ;Hello World?
  ;           ^
  ;Expected one of:
  ;"." (followed by end-of-string)
  ;" "
)

(deftest formatter-tests
  (is (= "HelloWorld" (formatter "Hello World.")))
  (is (not (= "HelloWorld" (formatter "Hello World?"))))
  ;(formatter "Hello World?")     gives:
  ;"[:index 11][:reason [{:tag :string, :expecting \".\", :full true} {:tag :string, :expecting \" \"}]][:text \"Hello World?\"][:column 12][:line 1]"
)

; run the tests
(run-tests)

我应该如何测试错误(这里:当句子不以. 结尾但以! 结尾时)?

【问题讨论】:

    标签: parsing exception clojure context-free-grammar instaparse


    【解决方案1】:

    Instaparse 在解析错误时不会抛出异常;相反,它返回一个“失败对象”(参考:parse errors)。您可以使用(insta/failure? result) 测试失败对象。

    如果您希望解析器/格式化程序在意外输入时引发异常,请将其添加到您的核心:

    (ns com.stackoverflow.clojure.testInstaparseWrongGrammar
      (:require [instaparse.core :as insta])
      (:require [instaparse.failure :as fail]))
    
    (def raw-parser (insta/parser "
        <sentence> = words <DOT>
        DOT        = '.'
        <words>    = word (<SPACE> word)*
        SPACE      = ' '
        word     = #'(?U)\\w+'
    "))
    
    ; pretty-print a failure as a string
    (defn- failure->string [result]
      (with-out-str (fail/pprint-failure result)))
    
    ; create an Exception with the pretty-printed failure message
    (defn- failure->exn [result]
      (Exception. (failure->string result)))  
    
    (defn parser [expr]
      (let [result (raw-parser expr)]
        (if (insta/failure? result)
          (throw (failure->exn result))
          result)))
    
    (defn formatter [expr]
      (->> (parser expr)
           (insta/transform {:word identity})
           (apply str)))
    

    ...现在您可以在测试中使用(is (thrown? ...))

    (deftest parser-tests
      (is (= [[:word "Hello"] [:word "World"]] (parser "Hello World.")))
      (is (thrown? Exception (= [[:word "Hello"] [:word "World"]] (parser "Hello World?"))))
    

    这种方法使用 instaparse 来漂亮地打印失败并将其包装在异常中。另一种方法是使用ex-info,如answer 中所述。

    【讨论】:

    • 如何从失败对象中获取信息?一开始我想做两件事(如果可能的话)。 首先:将行号添加到我的异常方法中。 第二:将格式正确的错误消息添加到我的异常中。 此外,对于创建一个新的 Exception 类,这似乎是在 Java 中实现它的最简单方法 - 对吗?
    • ...您所说的failure object 到底是什么意思。我认为 Clojure 中没有对象(带有方法和变量)。那么我如何(通常)访问这些对象的方法和变量?
    • @Edward 上面的代码现在在异常中包含解析错误(行、列等)的文本描述。 “失败对象”是一个映射(技术上是由defrecord 创建的record),它有一些众所周知的键;例如,可以使用(:line result) 访问行号。
    猜你喜欢
    • 2013-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-03
    • 1970-01-01
    • 2014-05-30
    • 1970-01-01
    相关资源
    最近更新 更多