Как проверить тексты, не соответствующие грамматике Instaparse (Clojure)?

Я написал проект для разбора строк с использованием контекстно-свободной грамматики в Instaparse (Clojure). Теперь я хотел бы проверить несколько входных строк на предмет их результатов синтаксического анализа. Некоторые входные строки могут не соответствовать грамматике. До сих пор я тестировал только «разобранные строки, не соответствующие ожиданиям». Но я думаю, что было бы точнее проверять исключения с помощью (is (thrown? ...)). Выбрасываются ли исключения? Мне кажется, что некоторый вывод (содержащий Parse error...) генерируется, но исключение не выдается.

Мой проект.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)

Как мне проверить ошибки (здесь: когда предложение заканчивается не на ., а на !)?


person Edward    schedule 13.10.2014    source источник


Ответы (1)


Instaparse не генерирует исключение при ошибке синтаксического анализа; вместо этого он возвращает «объект сбоя» (ссылка: ошибки синтаксического анализа). Вы можете проверить объект отказа с помощью (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, как описано в этом ответе.

person lnmx    schedule 13.10.2014
comment
Как мне получить информацию от объекта отказа? Для начала я хотел бы сделать две вещи (если это возможно). Во-первых: добавьте номер строки в мой метод исключения. Второе: добавить красиво отформатированное сообщение об ошибке в мое исключение. Более того, для создания нового класса Exception кажется, что это самый простой способ реализовать его на Java - это правильно? - person Edward; 14.10.2014
comment
... и что именно вы подразумеваете под failure object. Я думал, что в Clojure нет объектов (с методами и переменными). Итак, как я могу (в общем) получить доступ к методам и переменным этих объектов? - person Edward; 14.10.2014
comment
@Edward, приведенный выше код теперь включает текстовое описание ошибки синтаксического анализа (строка, столбец и т. д.) в Exception. Объект сбоя — это карта (технически record, созданная defrecord), которая имеет некоторые хорошо известные ключи; например, номер строки можно получить с помощью (:line result). - person lnmx; 14.10.2014