Skip to content

Commit b427c32

Browse files
committed
better errors, handle infinite sequences
1 parent c5fbadd commit b427c32

File tree

11 files changed

+2679
-2559
lines changed

11 files changed

+2679
-2559
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ Alpha. Certainly contains bugs. There is a [live demo](https://bobbicodes.github
3939
- ✅ Test published package
4040
- ✅ Handle errors
4141
- ✅ Pretty-print eval result
42-
- [ ] Truncate very long eval result
43-
- [ ] Handle infinite loops
42+
- Truncate very long eval result
43+
- Handle infinite loops
4444

4545
## Run demo locally
4646

dist/assets/index-1337c271.js

Lines changed: 0 additions & 1050 deletions
This file was deleted.

dist/assets/index-6879cb8c.js

Lines changed: 1054 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<link rel="icon" type="image/svg+xml" href="/lang-clojure-eval/assets/vite-4a748afd.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Vite App</title>
8-
<script type="module" crossorigin src="/lang-clojure-eval/assets/index-1337c271.js"></script>
8+
<script type="module" crossorigin src="/lang-clojure-eval/assets/index-6879cb8c.js"></script>
99
<link rel="stylesheet" href="/lang-clojure-eval/assets/index-b488241e.css">
1010
</head>
1111
<body>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lang-clojure-eval",
3-
"version": "0.0.14",
3+
"version": "0.0.15",
44
"author": "Bobbi Towers <[email protected]>",
55
"description": "Lezer-based Clojure Codemirror 6 extension with live evaluation",
66
"main": "dist/index.cjs",

shadow-cljs.edn

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
{:target :esm
77
:output-dir "public/js"
88
:modules {:sci {:exports {evalString lang-clojure-eval.main/eval-string
9-
sciInit lang-clojure-eval.main/sci-init}}}}}}
9+
context lang-clojure-eval.main/context}}}}}}

src/eval-region.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { Prec } from '@codemirror/state'
2-
import { keymap } from '@codemirror/view'
3-
import { syntaxTree } from "@codemirror/language"
4-
import { props } from "@nextjournal/lezer-clojure"
5-
import { evalString, sciInit } from "./sci"
6-
import { NodeProp } from "@lezer/common"
1+
import {Prec} from '@codemirror/state'
2+
import {keymap} from '@codemirror/view'
3+
import {syntaxTree} from "@codemirror/language"
4+
import {props} from "@nextjournal/lezer-clojure"
5+
import {evalString} from "./sci"
6+
import {NodeProp} from "@lezer/common"
77

88
// Node props are marked in the grammar and distinguish categories of nodes
99

@@ -111,9 +111,7 @@ function topLevelString(state) {
111111
return rangeStr(state, topLevelNode(state))
112112
}
113113

114-
let ctx = sciInit()
115114
let evalResult = ""
116-
let codeTail = ""
117115
let codeBeforeEval = ""
118116
let posBeforeEval = 0
119117

@@ -127,9 +125,9 @@ function updateEditor(view, text, pos) {
127125
})
128126
}
129127

130-
export function tryEval(ctx, s) {
128+
export function tryEval(s) {
131129
try {
132-
return evalString(ctx, s).trim()
130+
return evalString(s)
133131
} catch (err) {
134132
console.log(err)
135133
return "\nError: " + err.message
@@ -142,7 +140,7 @@ function evalAtCursor(view) {
142140
posBeforeEval = view.state.selection.main.head
143141
const codeBeforeCursor = codeBeforeEval.slice(0, posBeforeEval)
144142
const codeAfterCursor = codeBeforeEval.slice(posBeforeEval, codeBeforeEval.length)
145-
evalResult = tryEval(ctx, cursorNodeString(view.state))
143+
evalResult = tryEval(cursorNodeString(view.state))
146144
const codeWithResult = codeBeforeCursor + " => " + evalResult + " " + codeAfterCursor
147145
updateEditor(view, codeWithResult, posBeforeEval)
148146
view.dispatch({selection: {anchor: posBeforeEval, head: posBeforeEval}})
@@ -163,7 +161,7 @@ function evalTopLevel(view) {
163161
codeBeforeEval = doc
164162
const codeBeforeFormEnd = codeBeforeEval.slice(0, posAtFormEnd)
165163
const codeAfterFormEnd = codeBeforeEval.slice(posAtFormEnd, codeBeforeEval.length)
166-
evalResult = tryEval(ctx, topLevelString(view.state))
164+
evalResult = tryEval(topLevelString(view.state))
167165
const codeWithResult = codeBeforeFormEnd + " => " + evalResult + " " + codeAfterFormEnd
168166
updateEditor(view, codeWithResult, posBeforeEval)
169167
return true

src/lang_clojure_eval/error.cljs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
(ns lang-clojure-eval.error
2+
(:require [clojure.string :as str]
3+
[sci.core :as sci]))
4+
5+
(defn ruler [title]
6+
(println (apply str "----- " title " " (repeat (- 50 7 (count title)) \-))))
7+
8+
(defn split-stacktrace [stacktrace verbose?]
9+
(if verbose? [stacktrace]
10+
(let [stack-count (count stacktrace)]
11+
(if (<= stack-count 10)
12+
[stacktrace]
13+
[(take 5 stacktrace)
14+
(drop (- stack-count 5) stacktrace)]))))
15+
16+
(defn print-stacktrace
17+
[stacktrace {:keys [:verbose?]}]
18+
(let [stacktrace (sci/format-stacktrace stacktrace)
19+
segments (split-stacktrace stacktrace verbose?)
20+
[fst snd] segments]
21+
(run! #(print % "\n") fst)
22+
(when snd
23+
(print "...\n")
24+
(run! #(print % "\n") snd))))
25+
26+
(defn error-context [source ex]
27+
(let [{:keys [:line :column]} (ex-data ex)]
28+
(when line
29+
(when-let [content source]
30+
(let [matching-line (dec line)
31+
start-line (max (- matching-line 4) 0)
32+
end-line (+ matching-line 6)
33+
[before after] (->>
34+
(str/split-lines content)
35+
(map-indexed list)
36+
(drop start-line)
37+
(take (- end-line start-line))
38+
(split-at (inc (- matching-line start-line))))
39+
snippet-lines (concat before
40+
[[nil (str (str/join "" (repeat (dec column) " "))
41+
(str "^--- " (ex-message ex)))]]
42+
after)
43+
indices (map first snippet-lines)
44+
max-size (reduce max 0 (map (comp count str) indices))
45+
snippet-lines (map (fn [[idx line]]
46+
(if idx
47+
(let [line-number (inc idx)]
48+
(str (.padStart (str line-number ":") max-size "0") " " line))
49+
(str (str/join (repeat (+ 2 max-size) " ")) line)))
50+
snippet-lines)]
51+
(str/join "\n" snippet-lines))))))
52+
53+
(defn right-pad [s n]
54+
(let [n (- n (count s))]
55+
(str s (str/join (repeat n " ")))))
56+
57+
(defn print-locals [locals]
58+
(let [max-name-length (reduce max 0 (map (comp count str)
59+
(keys locals)))
60+
max-name-length (+ max-name-length 2)]
61+
(println
62+
(with-out-str (binding [*print-length* 10
63+
*print-level* 2]
64+
(doseq [[k v] locals]
65+
(print (str (right-pad (str k ": ") max-name-length)))
66+
;; print nil as nil
67+
(prn v)))))))
68+
69+
(defn error-handler [source ex]
70+
(let [stacktrace (sci/stacktrace ex)
71+
d (ex-data ex)
72+
sci-error? (isa? (:type d) :sci/error)]
73+
(ruler "Error")
74+
(when-let [name (.-name ex)]
75+
(when-not (= "Error" name)
76+
(println "Type: " name)))
77+
(when-let [m (.-message ex)]
78+
(println (str "Message: " m)))
79+
(let [{:keys [:file :line :column]} d]
80+
(when line
81+
(println (str "Location: "
82+
(when file (str file ":"))
83+
line ":" column ""))))
84+
(when-let [phase (:phase d)]
85+
(println "Phase: " phase))
86+
(when-let [ec (when sci-error?
87+
(error-context source ex))]
88+
(println)
89+
(ruler "Context")
90+
(println ec))
91+
(when sci-error?
92+
(when-let
93+
[st (let [st (with-out-str
94+
(when stacktrace
95+
(print-stacktrace stacktrace nil)))]
96+
(when-not (str/blank? st) st))]
97+
(println)
98+
(ruler "Stack trace")
99+
(println st)))))

src/lang_clojure_eval/main.cljs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
(ns lang-clojure-eval.main
22
(:require [sci.core :as sci]
3-
[clojure.pprint :as pprint]))
4-
5-
(defn sci-init []
6-
(sci/init {}))
7-
8-
(defn eval-string [ctx s]
9-
(with-out-str (pprint/pprint (sci/eval-string* ctx s))))
3+
[lang-clojure-eval.error :refer [error-handler]]
4+
[goog.string]
5+
[goog.string.format]
6+
[clojure.pprint :as pprint]
7+
[sci.impl.evaluator]))
108

9+
(defonce context
10+
(sci/init {:classes {'js goog/global
11+
:allow :all}
12+
:namespaces {'clojure.core {'format goog.string/format}}}))
1113

14+
(defn eval-string [source]
15+
(try (binding [*print-length* 100]
16+
(with-out-str (pprint/pprint (sci/eval-string* context source))))
17+
(catch :default e
18+
(with-out-str (error-handler source e)))))

0 commit comments

Comments
 (0)