001  (ns ^{:doc "Display parse errors in a format which makes it easy for the user
002        to see where the error occurred."
003        :author "Simon Brooke"}
004    mw-parser.errors)
005  
006  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
007  ;;
008  ;; This program is free software; you can redistribute it and/or
009  ;; modify it under the terms of the GNU General Public License
010  ;; as published by the Free Software Foundation; either version 2
011  ;; of the License, or (at your option) any later version.
012  ;;
013  ;; This program is distributed in the hope that it will be useful,
014  ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
015  ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016  ;; GNU General Public License for more details.
017  ;;
018  ;; You should have received a copy of the GNU General Public License
019  ;; along with this program; if not, write to the Free Software
020  ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
021  ;; USA.
022  ;;
023  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
024  
025  
026  ;; error thrown when an attempt is made to set a reserved property
027  (def reserved-properties-error
028    "The properties 'x' and 'y' of a cell are reserved and should not be set in rule actions")
029  ;; error thrown when a rule cannot be parsed. Slots are for
030  ;; (1) rule text
031  ;; (2) cursor showing where in the rule text the error occurred
032  ;; (3) the reason for the error
033  (def bad-parse-error "I did not understand:\n  '%s'\n  %s\n  %s")
034  
035  
036  (defn- explain-parse-error-reason
037    "Attempt to explain the reason for the parse error."
038    [reason]
039    (str "Expecting one of (" (apply str (map #(str (:expecting %) " ") reason)) ")"))
040  
041  
042  (defn- parser-error-to-map
043    [parser-error]
044    (let [m (reduce (fn [map item](merge map {(first item)(second item)})) {} parser-error)
045          reason (map
046                   #(reduce (fn [map item] (merge {(first item) (second item)} map)) {} %)
047                   (:reason m))]
048      (merge m {:reason reason})))
049  
050  
051  (defn throw-parse-exception
052    "Construct a helpful error message from this `parser-error`, and throw an exception with that message."
053    [parser-error]
054    (assert (coll? parser-error) "Expected a paser error structure?")
055    (let
056      [
057        ;; the error structure is a list, such that each element is a list of two items, and
058        ;; the first element in each sublist is a keyword. Easier to work with it as a map
059       error-map (parser-error-to-map parser-error)
060       text (:text error-map)
061       reason (explain-parse-error-reason (:reason error-map))
062        ;; rules have only one line, by definition; we're interested in the column
063       column (if (:column error-map)(:column error-map) 0)
064        ;; create a cursor to point to that column
065       cursor (apply str (reverse (conj (repeat column " ") "^")))
066       message (format bad-parse-error text cursor reason)
067       ]
068    (throw (Exception. message))))