Skip to content

Commit

Permalink
Imitate clojure.main Error Reporting
Browse files Browse the repository at this point in the history
Fixes #1004
  • Loading branch information
mfikes committed Aug 18, 2019
1 parent 60e8114 commit fd6e1cf
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 56 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file. This change
### Added
- Ability to specify prefix and suffix for temp files ([#1005](https://github.com/planck-repl/planck/issues/1005))

### Changed
- Imitate `clojure.main` Error Reporting ([#1004](https://github.com/planck-repl/planck/issues/1004))

### Fixed
- Crash if writing large byte vector to output stream ([#1001](https://github.com/planck-repl/planck/issues/1001))

Expand Down
12 changes: 12 additions & 0 deletions doc/running.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,15 @@ Options that may be configured via `-co` / `--compile-opts` comprise:
- [:verbose](https://clojurescript.org/reference/compiler-options#verbose)
- [:warnings](https://clojurescript.org/reference/compiler-options#warnings)
- [:warn-on-undeclared](https://clojurescript.org/reference/repl-options#warn-on-undeclared)

### Error Reporting

Planck will catch exceptions and use the same error triage and printing functionality present in the REPL. The full stack trace, ex-info, and other information will be printed to a target specified by the configuration.

The three available error targets are:

* file - write to a temp file (default, falls back to stderr)
* stderr - write to stderr stream
* none - don't write

These error targets can be specified as an option to `planck` or `plk`: use `--report <target>`.
2 changes: 1 addition & 1 deletion int-test/script/gen-actual
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
COLORFGBG="0;15"
unset PLANCK_CLASSPATH

export PLANCK="$PLANCK_BINARY --quiet --theme=plain"
export PLANCK="$PLANCK_BINARY --quiet --theme=plain --report none"

echo "Repeated ordered -e and -i, supressing nil"
$PLANCK -i $SRC/no-ns/foo.cljs -e ':a' -e 'nil' -i $SRC/no-ns/bar.cljs -e 2 -i $SRC/no-ns/foo.cljs
Expand Down
10 changes: 8 additions & 2 deletions planck-c/engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ void *do_engine_init(void *data) {
set_print_sender(&discarding_sender);

{
JSValueRef arguments[9];
JSValueRef arguments[10];
arguments[0] = JSValueMakeBoolean(ctx, config.repl);
arguments[1] = JSValueMakeBoolean(ctx, config.verbose);
JSValueRef cache_path_ref = NULL;
Expand Down Expand Up @@ -649,9 +649,15 @@ void *do_engine_init(void *data) {
compile_opts[i] = JSValueMakeString(ctx, compile_opts_str);
}
arguments[8] = JSObjectMakeArray(ctx, config.num_compile_opts, compile_opts, NULL);
JSValueRef report_ref = NULL;
if (config.report != NULL) {
JSStringRef report_str = JSStringCreateWithUTF8CString(config.report);
report_ref = JSValueMakeString(ctx, report_str);
}
arguments[9] = report_ref;

JSValueRef ex = NULL;
JSObjectCallAsFunction(ctx, get_function("planck.repl", "init"), JSContextGetGlobalObject(ctx), 9,
JSObjectCallAsFunction(ctx, get_function("planck.repl", "init"), JSContextGetGlobalObject(ctx), 10,
arguments, &ex);
debug_print_value("planck.repl/init", ctx, ex);

Expand Down
1 change: 1 addition & 0 deletions planck-c/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct config {
char* optimizations;
const char *theme;
bool dumb_terminal;
char* report;

char *main_ns_name;
size_t num_rest_args;
Expand Down
22 changes: 19 additions & 3 deletions planck-c/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ void usage(char *program_name) {
" -A x, --checked-arrays x Enables checked arrays where x is either warn\n"
" or error.\n"
" -a, --elide-asserts Set *assert* to false to remove asserts\n"
" --report target Report uncaught exception to \"file\" (default),\n"
" \"stderr\", or \"none\"\n"
"\n"
" main options:\n"
" -m ns-name, --main ns-name Call the -main function from a namespace with\n"
Expand Down Expand Up @@ -292,7 +294,7 @@ bool should_ignore_arg(const char *opt) {
}

// opt is a short opt or clump of short opts. If the clump
// ends with i, e, m, c, n, k, t, S, A, O, D, L, or \1
// ends with i, e, m, c, n, k, t, S, A, O, D, L, \1, or \2
// then this opt takes an argument.
int idx = 0;
char c = 0;
Expand All @@ -314,7 +316,8 @@ bool should_ignore_arg(const char *opt) {
last_c == 'O' ||
last_c == 'D' ||
last_c == 'L' ||
last_c == '\1');
last_c == '\1' ||
last_c == '\2');
}

void control_FTL_JIT() {
Expand Down Expand Up @@ -439,6 +442,7 @@ int main(int argc, char **argv) {
{"init", required_argument, NULL, 'i'},
{"main", required_argument, NULL, 'm'},
{"compile-opts", required_argument, NULL, '\1'},
{"report", required_argument, NULL, '\2'},

// development options
{"javascript", no_argument, NULL, 'j'},
Expand All @@ -452,11 +456,23 @@ int main(int argc, char **argv) {
// pass index_of_script_path_or_hyphen instead of argc to guarantee that everything
// after a bare dash "-" or a script path gets passed as *command-line-args*
while (!did_encounter_main_opt &&
(opt = getopt_long(index_of_script_path_or_hyphen, argv, "O:Xh?VS:D:L:\1:lvrA:sfak:je:t:n:dc:o:Ki:qm:", long_options, &option_index)) != -1) {
(opt = getopt_long(index_of_script_path_or_hyphen, argv, "O:Xh?VS:D:L:\1:\2:lvrA:sfak:je:t:n:dc:o:Ki:qm:", long_options, &option_index)) != -1) {
switch (opt) {
case '\1':
process_compile_opts(optarg);
break;
case '\2':
if (!strcmp(optarg, "file")) {
config.report = "file";
} else if (!strcmp(optarg, "stderr")) {
config.report = "stderr";
} else if (!strcmp(optarg, "none")) {
config.report = "none";
} else {
print_usage_error("report value must be file, stderr, or none", argv[0]);
return EXIT_FAILURE;
}
break;
case 'X':
init_launch_timing();
break;
Expand Down
6 changes: 1 addition & 5 deletions planck-cljs/src/planck/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -394,11 +394,7 @@
"Resolves namespace-qualified sym per [[resolve]]. If initial resolve
fails, attempts to require `sym`'s namespace and retries."
[sym]
(if (qualified-symbol? sym)
(or (resolve sym)
(do (eval `(require '~(-> sym namespace symbol)))
(resolve sym)))
(throw (js/Error. (str "Not a qualified symbol: " sym)))))
(#'repl/requiring-resolve sym))

(s/fdef requiring-resolve
:args (s/cat :sym qualified-symbol?)
Expand Down
46 changes: 46 additions & 0 deletions planck-cljs/src/planck/from/cljs/repl.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
(ns planck.from.cljs.repl)

(def ^:dynamic *extract-canonical-stacktrace* (fn [_] []))

;; Copied and made to use *extract-canonical-stacktrace*
(defn Error->map
"Constructs a data representation for a Error with keys:
:cause - root cause message
:phase - error phase
:via - cause chain, with cause keys:
:type - exception class symbol
:message - exception message
:data - ex-data
:at - top stack element
:trace - root cause stack elements"
[o]
(let [base (fn [t]
(merge {:type (cond
(instance? ExceptionInfo t) 'ExceptionInfo
(instance? js/EvalError t) 'js/EvalError
(instance? js/RangeError t) 'js/RangeError
(instance? js/ReferenceError t) 'js/ReferenceError
(instance? js/SyntaxError t) 'js/SyntaxError
(instance? js/URIError t) 'js/URIError
(instance? js/Error t) 'js/Error
:else nil)}
(when-let [msg (ex-message t)]
{:message msg})
(when-let [ed (ex-data t)]
{:data ed})
(let [st (*extract-canonical-stacktrace* t)]
(when (pos? (count st))
{:at st}))))
via (loop [via [], t o]
(if t
(recur (conj via t) (ex-cause t))
via))
root (peek via)]
(merge {:via (vec (map base via))
:trace (*extract-canonical-stacktrace* (or root o))}
(when-let [root-msg (ex-message root)]
{:cause root-msg})
(when-let [data (ex-data root)]
{:data data})
(when-let [phase (-> o ex-data :clojure.error/phase)]
{:phase phase}))))
35 changes: 35 additions & 0 deletions planck-cljs/src/planck/from/clojure/main.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
(ns ^:no-doc planck.from.clojure.main
(:require
[planck.core :refer [requiring-resolve with-open spit]]
[planck.io :as io :refer [temp-file]]
[cljs.repl]))

;; Copied and revised for use in Planck
(defn report-error
"Create and output an exception report for a Throwable to target.
Options:
:target - \"file\" (default), \"stderr\", \"none\"
If file is specified but cannot be written, falls back to stderr."
[t & {:keys [target]
:or {target "file"} :as opts}]
(when-not (= target "none")
(let [trace (cljs.repl/Error->map t)
triage (cljs.repl/ex-triage trace)
message (cljs.repl/ex-str triage)
report (array-map
:planck.repl/message message
:planck.repl/triage triage
:planck.repl/trace trace)
report-str (with-out-str
(binding [*print-namespace-maps* false]
((requiring-resolve 'clojure.pprint/pprint) report)))
err-path (when (= target "file")
(try
(let [f (temp-file "planck-" ".edn")]
(spit f report-str)
(:path f))
(catch :default _)))] ;; ignore, fallback to stderr
(binding [*print-fn* *print-err-fn*]
(if err-path
(println (str message \newline "Full report at:" \newline err-path))
(println (str report-str \newline message)))))))
Loading

0 comments on commit fd6e1cf

Please sign in to comment.