diff --git a/project.clj b/project.clj index 3c1a8bb..81e13d0 100755 --- a/project.clj +++ b/project.clj @@ -4,7 +4,6 @@ [org.clojure/clojure "1.5.0"] [clj-stacktrace "0.2.5"] [org.clojure/core.incubator "0.1.2"] - ;[trammel "0.7.0"] - ;[org.clojure/core.contracts "0.0.4"] [seesaw "1.4.3"]] - :main intro.core) + :main intro.core + ) diff --git a/src/errors/core.clj b/src/errors/core.clj index 8a53fe1..f8f19eb 100644 --- a/src/errors/core.clj +++ b/src/errors/core.clj @@ -6,29 +6,62 @@ [errors.messageobj] [seesaw.core])) -;;(def ignore-nses #"(clojure|java)\..*") +;; Ignore stack trace entries beginning with user, clojure, or java (def ignore-nses #"(user|clojure|java)\..*") +;; Gets the first match out of the error dictionary +;; based on the exception class and the message (defn- first-match [e message] - (println (str (class e) " " message)) ; debugging print + ;(println (str (class e) " " message)) ; debugging print (first (filter #(and (instance? (:class %) e) (re-matches (:match %) message)) error-dictionary))) ;; Putting together a message (perhaps should be moved to errors.dictionaries? ) (defn- get-pretty-message [e] (let [m (.getMessage e) - message (if m m "")] ; converting an empty message from nil to "" - (if-let [entry (first-match e message)] - ((:make-preobj entry) (re-matches (:match entry) message)) - (make-preobj-hashes message)))) + message (if m m "")] ; converting an empty message from nil to "" + (if-let [entry (first-match e message)] + ((:make-preobj entry) (re-matches (:match entry) message)) + (make-preobj-hashes message)))) +;; Returns true if a :trace-elems element is meaningful to the student +(defn- is-meaningful-elem? [elem] + (and + (:clojure elem) + (not (re-matches ignore-nses (:ns elem)))) + ) + +;; Takes in a single :trace-elems entry and produces a string for +;; our filtered stacktrace +(defn- create-error-str [err-elem] + (str + "\t" + (:ns err-elem) + "/" + (:fn err-elem) + " (" + (:file err-elem) + " line " + (:line err-elem) + ")" + ) + ) + +;; Creates the pre-object from a filtered stack trace +(defn- create-pre-obj [errstrs] + (make-preobj-hashes (str "\nSequence of function calls:\n" (join "\n" errstrs)) :causes) + ) + +;; Creates the full error object that is to be displayed +(defn- create-error-obj [e preobj] + (make-obj (concat (make-preobj-hashes "ERROR: " :err) + (get-pretty-message e) + preobj))) ;; All together: (defn prettify-exception [e] - (let [info (stacktrace/parse-exception e) - cljerrs (filter #(and (:clojure %) (not (re-matches ignore-nses (:ns %)))) - (:trace-elems info)) - errstrs (map #(str "\t" (:ns %) "/" (:fn %) " (" (:file %) " line " (:line %) ")") cljerrs)] - (show-error (make-obj (concat (make-preobj-hashes "ERROR: " :err) (get-pretty-message e) - (make-preobj-hashes (str "\nSequence of function calls:\n" (join "\n" errstrs)) :causes))) - e))) + (let [preobj (->> e stacktrace/parse-exception + (#(filter is-meaningful-elem? (:trace-elems %))) + (map create-error-str) + (create-pre-obj))] + (show-error (create-error-obj e preobj) e))) \ No newline at end of file diff --git a/src/errors/dictionaries.clj b/src/errors/dictionaries.clj index 7d3bf78..4b8a33d 100755 --- a/src/errors/dictionaries.clj +++ b/src/errors/dictionaries.clj @@ -167,6 +167,8 @@ :make-preobj (fn [matches] (make-preobj-hashes "There is an unmatched parameter in declaration of " (nth matches 1) :arg))} {:class IllegalArgumentException + ; TODO New message: + ; (nth matches 1) "'s must be pairs, you passed an odd number of parameters to " (nth matches 1) " on line " (nth matches 3) " in the file " (nth matches 2) :match #"(.*) requires an even number of forms in binding vector in (.*):(.*)" :make-preobj (fn [matches] (make-preobj-hashes "A parameter for a " (nth matches 1) " is missing a binding on line " @@ -221,7 +223,8 @@ :match #"Unsupported binding form: (.*)" :make-preobj (fn [matches] (make-preobj-hashes "You cannot use " (nth matches 1) :arg " as a variable."))} - ;; Compilation errors + ;; Compilation errors + ;; These next two exceptions about wrong # of args will only be thrown by macros NOT functions {:class clojure.lang.Compiler$CompilerException :match #"(.+): Too many arguments to (.+), compiling:(.+)" ;:replace "Compilation error: too many arguments to $2 while compiling $3" @@ -237,11 +240,11 @@ {:class clojure.lang.Compiler$CompilerException :match #"(.+): EOF while reading, starting at line (.+), compiling:(.+)" :replace "Compilation error: end of file, starting at line $2, while compiling $3.\nProbabbly a non-closing parentheses or bracket." - :make-preobj make-mock-preobj} + :make-preobj make-mock-preobj} ; TODO Make preobj function {:class clojure.lang.Compiler$CompilerException :match #"(.+): Unmatched delimiter: (.+), compiling:(.+)" ;:replace "Compilation error: a closing $2 without a matching opening one while compiling $3." - :make-preobj make-mock-preobj} + :make-preobj make-mock-preobj} ; TODO Make preobj function {:class clojure.lang.Compiler$CompilerException :match #"(.+): Unable to resolve symbol: (.+) in this context, compiling:\((.+)\)" ;:replace "Compilation error: name $2 is undefined in this context, while compiling $3." diff --git a/src/errors/messageobj.clj b/src/errors/messageobj.clj index 86b17b8..5ef7f0a 100644 --- a/src/errors/messageobj.clj +++ b/src/errors/messageobj.clj @@ -1,5 +1,5 @@ (ns errors.messageobj) - ;(:refer corefn/core :only [add-fisrt add-last])) + ;; Functions related to a message object. Message object ;; is a vector of parts of a message (in order). Each ;; part is a hash map that contains the message text :msg, @@ -8,40 +8,41 @@ ;; A message pre-object doesn't have :start (defn make-msg-preobj-hash - "creates a hash map for a msg pre-object out of a msg and style" - ([msg style] (let [m (str msg)] - {:msg m :stylekey style :length (count m)})) - ([msg] (let [m (str msg)] - {:msg m :stylekey :reg :length (count m)}))) + "creates a hash map for a msg pre-object out of a msg and style" + ([msg style] (let [m (str msg)] + {:msg m :stylekey style :length (count m)})) + ([msg] (let [m (str msg)] + {:msg m :stylekey :reg :length (count m)}))) (defn- make-msg-preobj-hashes-helper [messages result] - (if (empty? messages) result - (let [next (second messages)] - (if (keyword? next) (recur (rest (rest messages)) - (conj result (make-msg-preobj-hash (first messages) next))) - (recur (rest messages) - (conj result (make-msg-preobj-hash (first messages)))))))) + (if (empty? messages) result + (let [next (second messages)] + (if (keyword? next) (recur (rest (rest messages)) + (conj result (make-msg-preobj-hash (first messages) next))) + ;ELSE CASE + (recur (rest messages) + (conj result (make-msg-preobj-hash (first messages)))))))) (defn make-preobj-hashes [& args] - "creates a vector of hash maps out of a vector that are strings, possibly followed by optional keywords" - (make-msg-preobj-hashes-helper args [])) + "creates a vector of hash maps out of a vector that are strings, possibly followed by optional keywords" + (make-msg-preobj-hashes-helper args [])) -;(defn make-preobj-hashes [messages] -; "creates a vector of hash maps out of a vector of vectors of msg + optional style" -; ;; apply is needed since messages contains vectors of 1 or 2 elements -; (map #(apply make-msg-preobj-hash %) messages)) + ;(defn make-preobj-hashes [messages] + ; "creates a vector of hash maps out of a vector of vectors of msg + optional style" + ; ;; apply is needed since messages contains vectors of 1 or 2 elements + ; (map #(apply make-msg-preobj-hash %) messages)) (defn make-obj [pre-obj] ; pre-obj is a vector of hashmaps "fills in the starting points of objects in the hash maps" (loop [hashes pre-obj start 0 res []] (if (empty? hashes) res - (recur (rest hashes) - (+ start (:length (first hashes))) - (conj res (assoc (first hashes) :start start)))))) + (recur (rest hashes) + (+ start (:length (first hashes))) + (conj res (assoc (first hashes) :start start)))))) (defn get-all-text [msg-obj] - "concatenate all text from a message object into a string" - (reduce #(str %1 (:msg %2)) "" msg-obj)) + "concatenate all text from a message object into a string" + (reduce #(str %1 (:msg %2)) "" msg-obj)) (defn make-mock-preobj [matches] "creates a test message pre-obj. Used for testing so that things don't break" diff --git a/src/intro/student.clj b/src/intro/student.clj index 421eada..5ec738f 100644 --- a/src/intro/student.clj +++ b/src/intro/student.clj @@ -1,7 +1,4 @@ -(ns intro.student - (:use [corefns.core] - [seesaw.core] - [turtle.core])) +(ns intro.student) ;; Testing compilation errors diff --git a/test/errors/core_test.clj b/test/errors/core_test.clj index db338cc..fee6eb3 100644 --- a/test/errors/core_test.clj +++ b/test/errors/core_test.clj @@ -1,17 +1,29 @@ (ns errors.core_test (:use [clojure.test] [errors.core] - [errors.dictionaries])) + [errors.dictionaries] + [errors.messageobj])) (def simple-non-match-exception (java.lang.Exception. "Test Message")) (def get-pretty-message (ns-resolve 'errors.core 'get-pretty-message)) (def class-cast-exception (java.lang.ClassCastException. "oneType cannot be cast to anotherType")) +(def bigint-illegal-arg-ex (java.lang.IllegalArgumentException. "contains? not supported on type: clojure.lang.BigInt")) +(def best-approximation (ns-resolve 'errors.dictionaries 'best-approximation)) +(def incorrect-let-exception (try (eval (read-string "(let [l 1 s] (println l))")) (catch Exception e e))) + +(defn get-pretty-message-string [e] + (-> e get-pretty-message get-all-text) + ) (deftest test-best-approximation (is (= "unrecognized type oneType" (best-approximation "oneType"))) + (is (= "a number" (best-approximation "clojure.lang.BigInt"))) + ;(is (= "unrecognized type clojure.lang.LittleInt" (best-approximation "clojure.lang.LittleInt"))) This triggers a classNotFoundException since there are periods in the type name ) (deftest test-get-pretty-message - (is (= "Test Message" (get-pretty-message simple-non-match-exception))) - (is (= "Attempted to use unrecognized type oneType, but unrecognized type anotherType was expected." (get-pretty-message class-cast-exception))) + (is (= "Test Message" (get-pretty-message-string simple-non-match-exception))) + (is (= "Attempted to use unrecognized type oneType, but unrecognized type anotherType was expected." (get-pretty-message-string class-cast-exception))) + (is (= "Function contains? does not allow a number as an argument" (get-pretty-message-string bigint-illegal-arg-ex))) + (is (= "A parameter for a let is missing a binding on line in the file errors.core_test" (get-pretty-message-string incorrect-let-exception))) ) \ No newline at end of file