|
25 | 25 | [clojure.pprint :as pprint :refer [pprint pp]]
|
26 | 26 | [clojure.set :as set]
|
27 | 27 | [clojure.spec.alpha]
|
| 28 | + [clojure.stacktrace :as stacktrace] |
28 | 29 | [clojure.string :as s]
|
29 | 30 | [clojure.test :refer [deftest testing is are]]
|
30 | 31 | [clojure.test.check :as check]
|
|
177 | 178 | (meta exp)))
|
178 | 179 |
|
179 | 180 | ;; else
|
180 |
| - (map (partial iter &env) expanded)) |
| 181 | + (with-meta (map (partial iter &env) expanded) (meta exp))) |
181 | 182 |
|
182 | 183 | (or (vector? expanded) (set? expanded) (map? expanded))
|
183 | 184 | (map-coll #(iter &env %) expanded)
|
|
199 | 200 | java.util.regex.Pattern (evalme [t _] t)
|
200 | 201 | clojure.lang.Keyword (evalme [t _] t))
|
201 | 202 |
|
| 203 | +(def ^java.util.Map exception-stack (java.util.Collections/synchronizedMap (new java.util.WeakHashMap))) |
| 204 | + |
| 205 | +(defmacro with-err-report [loc expr] |
| 206 | + `(try ~expr |
| 207 | + (catch Throwable t# |
| 208 | + (when-let [loc# ~loc] |
| 209 | + (.compute exception-stack t# (reify java.util.function.BiFunction (apply [_# _# v#] (cons loc# v#))))) |
| 210 | + (throw t#)))) |
| 211 | + |
202 | 212 | ;; TODO: test with interfaces instead of protocols!
|
203 | 213 | (defmacro gen-eval-node
|
204 | 214 | ([m body] `(with-meta (gen-eval-node ~body) ~m))
|
|
232 | 242 |
|
233 | 243 | (custom-var! #'clojure.core/load-file
|
234 | 244 | (fn [fname]
|
235 |
| - (binding [*file* (io/file fname)] |
236 |
| - ((@custom-var-impls #'clojure.core/load-reader) (io/reader *file*))))) |
| 245 | + (binding [*file* fname] |
| 246 | + ((@custom-var-impls #'clojure.core/load-reader) (io/reader (io/file fname)))))) |
237 | 247 |
|
238 | 248 | (custom-var! #'clojure.core/load
|
239 | 249 | (fn [& bodies] (throw (new RuntimeException "UCLJ does not yet support clojure.core/load!")))
|
|
265 | 275 | (alter-meta! (var *ns*) assoc :dynamic true)
|
266 | 276 | (var-set-reset! (var *ns*) (create-ns new-ns))))
|
267 | 277 |
|
| 278 | +;; Emits a clojure (let[]) expression in which values in let-vals are bound to new local vars |
| 279 | +;; and body is result of calling body-fn with the generated var names. |
| 280 | +(defn- gen-let-form [let-vals body-fn] |
| 281 | + (assert (seq? let-vals)) |
| 282 | + (let [let-vars (repeatedly (count let-vals) gensym)] |
| 283 | + (list 'let* |
| 284 | + (vec (interleave let-vars let-vals)) |
| 285 | + (apply body-fn let-vars)))) |
| 286 | + |
| 287 | +;; invocation of core functions is inlined for all arities |
268 | 288 | (def clojure-core-inlined-fns
|
269 | 289 | (template
|
270 | 290 | (hash-map
|
|
276 | 296 | #'clojure.core/alength #'clojure.core/aset} v))
|
277 | 297 | :when (not (@custom-var-impls v))
|
278 | 298 | :when (not (:macro (meta v)))
|
279 |
| - :let [arglists (var->arglists v)] |
| 299 | + :let [arglists (var->arglists v) |
| 300 | + +meta (gensym)] ;; symbol will reference metadata of original form |
280 | 301 | :when arglists]
|
281 | 302 | [v (list* 'fn*
|
282 | 303 | (symbol (str (name (symbol v)) "-inlined"))
|
283 | 304 | (for [args arglists]
|
284 | 305 | (if (= 'variadic (last args))
|
285 |
| - (list (vec args) (list 'gen-eval-node (concat ['clojure.core/apply (symbol v)] |
| 306 | + ;; TODO: also add to variadic calls! |
| 307 | + (list (vec (cons +meta args)) (list 'gen-eval-node (concat ['clojure.core/apply (symbol v)] |
286 | 308 | (for [a (butlast (butlast args))] (list 'evalme a '&b))
|
287 | 309 | [(list 'clojure.core/for [(last args) (last args)] (list 'evalme (last args) '&b))])))
|
288 |
| - (list (vec args) (list 'gen-eval-node (list* (symbol v) (for [a args] (list 'evalme a '&b))))))))]))))) |
| 310 | + (list (vec (cons +meta args)) (list 'gen-eval-node |
| 311 | + (gen-let-form (for [a args] (list 'evalme a '&b)) |
| 312 | + (fn [& arg-forms] (list 'with-err-report +meta (list* (symbol v) arg-forms)))))))))]))))) |
289 | 313 |
|
290 | 314 | (defmethod seq->eval-node ::default seq-eval-call [&a _ s]
|
291 | 315 | (if (empty? s)
|
292 | 316 | (gen-eval-node ())
|
293 |
| - (let [[f & args] (map (partial ->eval-node &a nil) s) |
294 |
| - [a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15] args] ;; TODO: unroll with template! |
| 317 | + (let [[f & args :as f+arglists] (map (partial ->eval-node &a nil) s) |
| 318 | + [a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14 a15] args ;; TODO: unroll with template! |
| 319 | + form-meta (::source-meta (meta s)) |
| 320 | + form-meta (when form-meta (assoc form-meta :file *file*))] |
295 | 321 | (dorun args)
|
296 | 322 | (if-let [call-factory (clojure-core-inlined-fns (::var (meta f)))]
|
297 |
| - (apply call-factory args) |
| 323 | + (apply call-factory form-meta args) |
298 | 324 | (template [a-symbol #(symbol (str 'a %))]
|
299 | 325 | (case (count args)
|
300 | 326 | ~@(mapcat seq (for [i (range 16)]
|
301 |
| - [i (list 'gen-eval-node (list* '.invoke (quote ^clojure.lang.IFn (evalme f &b)) |
302 |
| - (for [j (range 1 (inc i))] (list 'evalme (a-symbol j) '&b))))])) |
| 327 | + [i (list 'gen-eval-node |
| 328 | + (gen-let-form (cons (with-meta '(evalme f &b) {:tag 'clojure.lang.IFn}) (for [j (range 1 (inc i))] (list 'evalme (a-symbol j) '&b))) |
| 329 | + (fn [f & as] (list 'with-err-report 'form-meta (list* '.invoke f as)))))])) |
303 | 330 | ;; else
|
304 | 331 | (gen-eval-node (apply (evalme f &b) (for [e args] (evalme e &b))))))))))
|
305 | 332 |
|
|
407 | 434 | `(let [~'enclosed-array-size (int (if ~fname (inc (count ~symbol-used)) (count ~symbol-used)))
|
408 | 435 | body-vararg# (:variadic ~arity->body-node)
|
409 | 436 | body-vararg-symbols# (:variadic ~arity->symbols-introduced)
|
| 437 | + ~'err-meta {:fn (or ~fname "fn$anonymous") :ns *ns*} |
410 | 438 | [~@(for [i (range (inc max-arity))] (symbol (str 'body i)))] (map ~arity->body-node (range))
|
411 | 439 | [~@(for [i (range (inc max-arity))] (symbol (str 'body i '-symbols)))] (map ~arity->symbols-introduced (range))]
|
412 | 440 | (gen-eval-node
|
|
421 | 449 | ~'enclosed-array (+ (count ~(symbol (str 'body i '-symbols))) ~'enclosed-array-size))]
|
422 | 450 | ~@(for [j (range i)]
|
423 | 451 | (list 'aset 'invocation-array (list '+ j 'enclosed-array-size) (nth arg-symbols j)))
|
424 |
| - (loop [] |
425 |
| - (let [result# (evalme ~(symbol (str 'body i)) ~'invocation-array)] |
426 |
| - (if (identical? ::recur result#) |
427 |
| - (recur) |
428 |
| - result#)))))) |
| 452 | + (with-err-report ~'err-meta |
| 453 | + (loop [] |
| 454 | + (let [result# (evalme ~(symbol (str 'body i)) ~'invocation-array)] |
| 455 | + (if (identical? ::recur result#) |
| 456 | + (recur) |
| 457 | + result#))))))) |
429 | 458 | ([~@(for [i (range max-arity)] (symbol (str 'arg- i))) ~'& arg-rest#]
|
430 | 459 | (assert body-vararg-symbols# "Called with too many parameters!")
|
431 | 460 | (let [~'invocation-array (java.util.Arrays/copyOf ~'enclosed-array (+ (count body-vararg-symbols#) ~'enclosed-array-size))]
|
432 | 461 | ~@(for [j (range (+ max-arity))]
|
433 | 462 | (list 'aset 'invocation-array (list '+ j 'enclosed-array-size) (symbol (str 'arg- j))))
|
434 | 463 | (aset ~'invocation-array (+ ~max-arity ~'enclosed-array-size) arg-rest#)
|
435 |
| - (loop [] |
436 |
| - (let [result# (evalme body-vararg# ~'invocation-array)] |
437 |
| - (if (identical? ::recur result#) |
438 |
| - (recur) |
439 |
| - result#)))))) |
| 464 | + (with-err-report ~'err-meta |
| 465 | + (loop [] |
| 466 | + (let [result# (evalme body-vararg# ~'invocation-array)] |
| 467 | + (if (identical? ::recur result#) |
| 468 | + (recur) |
| 469 | + result#))))))) |
440 | 470 | (cond->> ~fname (aset #^objects ~'enclosed-array (dec ~'enclosed-array-size))))))))
|
441 | 471 |
|
442 | 472 |
|
|
562 | 592 | (assert (= 2 (count form)))
|
563 | 593 | (assert (or (symbol? e) (seq? e)))
|
564 | 594 | (let [e (->eval-node &a nil e)]
|
| 595 | + ;; TODO: mark exception as rethrown! |
565 | 596 | (gen-eval-node (throw (evalme e &b)))))
|
566 | 597 |
|
567 | 598 | (defmethod seq->eval-node 'var [&a _ [_ v]]
|
|
683 | 714 | ;; method calls
|
684 | 715 | (let [bodies (doall (for [b v] (enhance-code sym->iden b)))]
|
685 | 716 | (with-meta bodies {::symbol-used (set (mapcat (comp ::symbol-used meta) bodies))
|
| 717 | + ::source-meta (meta v) |
686 | 718 | ::symbol-introduced (set (mapcat (comp ::symbol-introduced meta) bodies))}))
|
687 | 719 | ;; scalar values: string, numbers, etc.
|
688 | 720 | v))
|
|
831 | 863 | (some (comp :test meta) (vals (ns-interns ns))))]
|
832 | 864 | ns))
|
833 | 865 |
|
| 866 | +(alter-var-root #'stacktrace/print-trace-element |
| 867 | + (fn [print-trace-element] |
| 868 | + (fn stack-trace-element-2 [e] |
| 869 | + (if (map? e) |
| 870 | + (print (str (:ns e) "/" (:fn e) "(" (:file e) ":" (:line e) ":" (:column e) ")")) |
| 871 | + (print-trace-element e))))) |
| 872 | + |
| 873 | +(alter-var-root #'stacktrace/print-stack-trace |
| 874 | + (fn [old-print-stack-trace] |
| 875 | + (fn print-stack-trace-2 [^Throwable tr & n] |
| 876 | + (println :print-stack-trace) |
| 877 | + (if-let [st (first (reduce (fn [[xs ns fn] entry] |
| 878 | + (if (:ns entry) |
| 879 | + [xs (:ns entry) (:fn entry)] |
| 880 | + [(cons (assoc entry :ns ns :fn fn) xs) ns fn])) |
| 881 | + [nil nil nil] (.get exception-stack tr)))] |
| 882 | + (do (stacktrace/print-throwable tr) |
| 883 | + (newline) |
| 884 | + (print " at ") |
| 885 | + (if-let [e (first st)] |
| 886 | + (stacktrace/print-trace-element e) |
| 887 | + (print "[empty stack trace]")) |
| 888 | + (newline) |
| 889 | + (doseq [e (rest st)] |
| 890 | + (print " ") |
| 891 | + (stacktrace/print-trace-element e) |
| 892 | + (newline))) |
| 893 | + (old-print-stack-trace tr n))))) |
| 894 | + |
| 895 | +(defmacro ^:private try-catch-error [exit-code body] |
| 896 | + `(try ~body |
| 897 | + (catch Throwable ~'t |
| 898 | + (binding [*out* *err*] |
| 899 | + (stacktrace/print-stack-trace ~'t) |
| 900 | + (flush)) |
| 901 | + ~(if (int? exit-code) `(System/exit ~exit-code) exit-code)))) |
| 902 | + |
| 903 | +(Thread/setDefaultUncaughtExceptionHandler |
| 904 | + (reify Thread$UncaughtExceptionHandler |
| 905 | + (uncaughtException [_ thread ex] (try-catch-error nil (throw ex))))) |
| 906 | + |
834 | 907 | (defn -main [& args]
|
835 | 908 | (evaluator '(in-ns 'user))
|
836 | 909 | (evaluator '(run! require uclj.core/namespaces-to-require))
|
837 | 910 | (cond
|
838 | 911 | (and (first args) (.startsWith (str (first args)) "("))
|
839 |
| - (binding [*command-line-args* (second args)] |
840 |
| - (println (evaluator (read-string (first args))))) |
| 912 | + (binding [*command-line-args* (second args)] |
| 913 | + (try-catch-error 1 (println (evaluator (read-string (first args)))))) |
841 | 914 |
|
842 | 915 | (and (first args) (.exists (io/file (first args))))
|
843 | 916 | (let [test? (= "--test" (second args))]
|
844 |
| - (binding [*command-line-args* (if test? (nnext args) (next args))] |
845 |
| - (evaluator `(load-file ~(first args)))) |
| 917 | + (try-catch-error 1 (binding [*command-line-args* (if test? (nnext args) (next args))] |
| 918 | + (evaluator `(load-file ~(first args)))) ) |
846 | 919 | (when test?
|
847 |
| - (let [test-result (apply clojure.test/run-tests (all-test-namespaces))] |
848 |
| - (when-not (zero? (:fail test-result)) |
849 |
| - (System/exit 1))))) |
| 920 | + (try-catch-error 2 |
| 921 | + (let [test-result (apply clojure.test/run-tests (all-test-namespaces))] |
| 922 | + (when-not (zero? (:fail test-result)) |
| 923 | + (System/exit 1)))))) |
850 | 924 |
|
851 | 925 | :else ;; interactive mode
|
852 | 926 | (do (println "Welcome to the small interpreter!")
|
|
856 | 930 | (print (str (ns-name *ns*) "=> ")) (flush)
|
857 | 931 | (let [read (read {:eof ::eof} *in*)]
|
858 | 932 | (when-not (= ::eof read)
|
859 |
| - (try (let [e (evaluator read)] |
860 |
| - (var-set-reset! #'*3 *2) |
861 |
| - (var-set-reset! #'*2 *1) |
862 |
| - (var-set-reset! #'*1 e) |
863 |
| - (println e)) |
864 |
| - (catch Throwable t |
865 |
| - (.printStackTrace t) |
866 |
| - (var-set-reset! #'*e t))) |
| 933 | + (try-catch-error (var-set-reset! #'*e t) |
| 934 | + (let [e (evaluator read)] |
| 935 | + (var-set-reset! #'*3 *2) |
| 936 | + (var-set-reset! #'*2 *1) |
| 937 | + (var-set-reset! #'*1 e) |
| 938 | + (println e))) |
867 | 939 | (recur))))
|
868 | 940 | (println "EOF, bye!"))))
|
869 | 941 |
|
|
0 commit comments