Skip to content

Commit cc07b80

Browse files
authored
Untangle parser from analyzer (#745)
Make the parser step independent of the analyzer. This allows us to skip analysis when caching is disabled for the whole namespace.
1 parent 52d0ccc commit cc07b80

File tree

11 files changed

+439
-272
lines changed

11 files changed

+439
-272
lines changed

notebooks/hello.clj

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
;; Clerk enables a _rich_, local-first notebook experience using standard Clojure namespaces.
44
(ns hello
5+
{:nextjournal.clerk/no-cache true}
56
(:require [nextjournal.clerk :as clerk]))
67

78
;; Here's a visualization of unemployment in the US.

notebooks/hello.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Hello Markdown 👋
2+
3+
Clerk enables a _rich_, local-first notebook experience using standard
4+
Clojure namespaces and markdown.
5+
6+
```clojure
7+
(ns hello-markdown
8+
{:nextjournal.clerk/no-cache true
9+
:nextjournal.clerk/visibility {:code :fold}}
10+
(:require [nextjournal.clerk :as clerk]))
11+
```
12+
13+
Here's a visualization of unemployment in the US.
14+
15+
```clojure
16+
^{::clerk/viewer clerk/vl}
17+
{:width 700 :height 400 :data {:url "https://vega.github.io/vega-datasets/data/us-10m.json"
18+
:format {:type "topojson" :feature "counties"}}
19+
:transform [{:lookup "id" :from {:data {:url "https://vega.github.io/vega-datasets/data/unemployment.tsv"}
20+
:key "id" :fields ["rate"]}}]
21+
:projection {:type "albersUsa"} :mark "geoshape" :encoding {:color {:field "rate" :type "quantitative"}}}
22+
```

src/nextjournal/clerk.clj

+2-2
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
(when-let [path (paths/path-in-cwd file-or-ns)]
5858
{:file-path path})
5959
{:nav-path (webserver/->nav-path file-or-ns)}
60-
(parser/parse-file {:doc? true} file))
60+
(parser/parse-file file))
6161
(catch java.io.FileNotFoundException e
6262
(throw (ex-info (str "`nextjournal.clerk/show!` could not find the file: `" (pr-str file-or-ns) "`")
6363
{:file-or-ns file-or-ns}
@@ -648,7 +648,7 @@
648648
(show! "notebooks/onwards.md")
649649
(show! "notebooks/pagination.clj")
650650
(show! "notebooks/how_clerk_works.clj")
651-
(show! "notebooks/hello_clojurescript.cljs")
651+
(show! "notebooks/hello.cljs")
652652
(show! "notebooks/conditional_read.cljc")
653653
(show! "src/nextjournal/clerk/analyzer.clj")
654654
(show! "src/nextjournal/clerk.clj")

src/nextjournal/clerk/analyzer.clj

+16-77
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
(ns nextjournal.clerk.analyzer
22
{:nextjournal.clerk/no-cache true}
3-
(:refer-clojure :exclude [hash read-string])
3+
(:refer-clojure :exclude [hash])
44
(:require [babashka.fs :as fs]
5-
[edamame.core :as edamame]
65
[clojure.core :as core]
76
[clojure.java.io :as io]
87
[clojure.set :as set]
98
[clojure.string :as str]
10-
[clojure.tools.reader :as tools.reader]
119
[clojure.tools.analyzer :as ana]
1210
[clojure.tools.analyzer.ast :as ana-ast]
1311
[clojure.tools.analyzer.jvm :as ana-jvm]
@@ -85,29 +83,6 @@
8583

8684
#_(rewrite-defcached '(nextjournal.clerk/defcached foo :bar))
8785

88-
(defn auto-resolves [ns]
89-
(as-> (ns-aliases ns) $
90-
(assoc $ :current (ns-name *ns*))
91-
(zipmap (keys $)
92-
(map ns-name (vals $)))))
93-
94-
#_(auto-resolves (find-ns 'nextjournal.clerk.parser))
95-
#_(auto-resolves (find-ns 'cards))
96-
97-
(defn read-string [s]
98-
(edamame/parse-string s {:all true
99-
:syntax-quote {:resolve-symbol tools.reader/resolve-symbol}
100-
:readers *data-readers*
101-
:read-cond :allow
102-
:regex #(list `re-pattern %)
103-
:features #{:clj}
104-
:end-location false
105-
:row-key :line
106-
:col-key :column
107-
:auto-resolve (auto-resolves (or *ns* (find-ns 'user)))}))
108-
109-
#_(read-string "(ns rule-30 (:require [nextjournal.clerk.viewer :as v]))")
110-
11186
(defn unresolvable-symbol-handler [ns sym ast-node]
11287
ast-node)
11388

@@ -297,20 +272,6 @@
297272
(filter (comp #{:code} :type)
298273
blocks)))))
299274

300-
(defn get-block-id [!id->count {:as block :keys [var form type doc]}]
301-
(let [id->count @!id->count
302-
id (if var
303-
var
304-
(let [hash-fn #(-> % nippy/fast-freeze sha1-base58)]
305-
(symbol (str *ns*)
306-
(case type
307-
:code (str "anon-expr-" (hash-fn (cond-> form (instance? clojure.lang.IObj form) (with-meta {}))))
308-
:markdown (str "markdown-" (hash-fn doc))))))]
309-
(swap! !id->count update id (fnil inc 0))
310-
(if (id->count id)
311-
(symbol (str *ns*) (str (name id) "#" (inc (id->count id))))
312-
id)))
313-
314275
(defn ^:private internal-proxy-name?
315276
"Returns true if `sym` represents a var name interned by `clojure.core/proxy`."
316277
[sym]
@@ -377,41 +338,21 @@
377338
(analyze-doc {:doc? true} doc))
378339
([{:as state :keys [doc?]} doc]
379340
(binding [*ns* *ns*]
380-
(let [!id->count (atom {})]
341+
(let [add-block-id (partial parser/add-block-id (atom {}))]
381342
(cond-> (reduce (fn [{:as state notebook-ns :ns} {:as block :keys [type text loc]}]
382343
(if (not= type :code)
383-
(update state :blocks conj (assoc block :id (get-block-id !id->count block)))
384-
(let [form (try (read-string text)
385-
(catch Exception e
386-
(throw (ex-info (str "Clerk analysis failed reading block: "
387-
(ex-message e))
388-
{:block block
389-
:file (:file doc)}
390-
e))))
391-
form+loc (cond-> form
392-
(instance? clojure.lang.IObj form)
393-
(vary-meta merge (cond-> loc
394-
(:file doc) (assoc :clojure.core/eval-file
395-
(str (cond-> (:file doc)
396-
(instance? java.net.URL (:file doc)) extract-file))))))
397-
{:as analyzed :keys [ns-effect?]} (cond-> (analyze form+loc)
398-
(:file doc) (assoc :file (:file doc)))
399-
_ (when ns-effect? ;; needs to run before setting doc `:ns` via `*ns*`
400-
(eval form))
401-
block-id (get-block-id !id->count (merge analyzed block))
402-
analyzed (assoc analyzed :id block-id)]
403-
344+
(update state :blocks conj (add-block-id block))
345+
(let [{:as form-analysis :keys [ns-effect? form]} (cond-> (analyze (:form block))
346+
(:file doc) (assoc :file (:file doc)))
347+
block+analysis (add-block-id (merge block form-analysis))]
348+
(when ns-effect? ;; needs to run before setting doc `:ns` via `*ns*`
349+
(eval form))
404350
(-> state
405-
(store-info analyzed)
406-
(track-var->block+redefs analyzed)
407-
(update :blocks conj (-> block
408-
(merge (dissoc analyzed :deps :no-cache? :ns-effect?))
409-
(cond->
410-
(parser/ns? form) (assoc :ns? true)
411-
doc? (assoc :text-without-meta (parser/text-with-clerk-metadata-removed text (ns-resolver notebook-ns))))))
412-
(cond->
413-
(and doc? (not (contains? state :ns)))
414-
(merge (parser/->doc-settings form) {:ns *ns*}))))))
351+
(store-info block+analysis)
352+
(track-var->block+redefs block+analysis)
353+
(update :blocks conj (cond-> (dissoc block+analysis :deps :no-cache? :ns-effect?)
354+
(parser/ns? form) (assoc :ns? true)
355+
doc? (assoc :text-without-meta (parser/text-with-clerk-metadata-removed text (ns-resolver notebook-ns)))))))))
415356

416357
(-> state
417358
(cond-> doc? (merge doc))
@@ -421,11 +362,9 @@
421362
(:blocks doc))
422363

423364
true (dissoc :doc?)
424-
doc? (-> parser/add-block-settings
425-
parser/add-open-graph-metadata
426-
parser/filter-code-blocks-without-form))))))
365+
doc? parser/filter-code-blocks-without-form)))))
427366

428-
#_(let [parsed (nextjournal.clerk.parser/parse-clojure-string "clojure.core/dec")]
367+
#_(let [parsed (parser/parse-clojure-string "clojure.core/dec")]
429368
(build-graph (analyze-doc parsed)))
430369

431370
(defonce !file->analysis-cache
@@ -603,7 +542,7 @@
603542
analyze-doc-deps
604543
set-no-cache-on-redefs
605544
make-deps-inherit-no-cache
606-
(dissoc :analyzed-file-set :counter))))))
545+
(dissoc :analyzed-file-set :counter))))))
607546

608547
(comment
609548
(reset! !file->analysis-cache {})

src/nextjournal/clerk/config.clj

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
".clerk/cache"))
99

1010
(def cache-disabled?
11-
(when-let [prop (System/getProperty "clerk.disable_cache")]
12-
(not= "false" prop)))
11+
(boolean (when-let [prop (System/getProperty "clerk.disable_cache")]
12+
(not= "false" prop))))
1313

1414
(def resource-manifest-from-props
1515
(when-let [prop (System/getProperty "clerk.resource_manifest")]

src/nextjournal/clerk/eval.clj

+31-16
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,7 @@
135135
var-from-def? (and var (var? result) (= var (symbol result)))
136136
no-cache? (or ns-effect?
137137
no-cache?
138-
(boolean (seq @!interned-vars))
139-
config/cache-disabled?)]
138+
(boolean (seq @!interned-vars)))]
140139
(when (and (not no-cache?)
141140
(not ns-effect?)
142141
freezable?
@@ -167,7 +166,9 @@
167166
(defn read+eval-cached [{:as doc :keys [blob->result ->analysis-info ->hash]} codeblock]
168167
(let [{:keys [id form _vars var]} codeblock
169168
_ (assert id (format "Missing id on codeblock: '%s'." (pr-str codeblock)))
170-
{:as form-info :keys [ns-effect? no-cache? freezable?]} (->analysis-info id)
169+
{:as form-info :keys [ns-effect? no-cache? freezable?]} (if (:no-cache doc)
170+
(assoc codeblock :no-cache? true)
171+
(->analysis-info id))
171172
no-cache? (or ns-effect? no-cache?)
172173
hash (when-not no-cache? (or (get ->hash id)
173174
(analyzer/hash-codeblock ->hash doc codeblock)))
@@ -262,30 +263,41 @@
262263

263264
(defn +eval-results
264265
"Evaluates the given `parsed-doc` using the `in-memory-cache` and augments it with the results."
265-
[in-memory-cache {:as parsed-doc :keys [set-status-fn]}]
266+
[in-memory-cache {:as parsed-doc :keys [set-status-fn no-cache]}]
266267
(if (cljs? parsed-doc)
267268
(process-cljs parsed-doc)
268-
(do
269-
(when set-status-fn
270-
(set-status-fn {:progress 0.10 :status "Analyzing…"}))
271-
(let [{:as analyzed-doc :keys [ns]} (analyzer/build-graph
272-
(assoc parsed-doc :blob->result in-memory-cache))]
273-
(binding [*ns* ns]
274-
(-> analyzed-doc
275-
analyzer/hash
276-
eval-analyzed-doc))))))
269+
(let [{:as analyzed-doc :keys [ns]}
270+
271+
(if no-cache
272+
parsed-doc
273+
(do
274+
(when set-status-fn
275+
(set-status-fn {:progress 0.10 :status "Analyzing…"}))
276+
(-> parsed-doc
277+
(assoc :blob->result in-memory-cache)
278+
analyzer/build-graph
279+
analyzer/hash)))]
280+
(when (and (not-empty (:var->block-id analyzed-doc))
281+
(not ns))
282+
(throw (ex-info "namespace must be set" (select-keys analyzed-doc [:file :ns]))))
283+
(binding [*ns* ns]
284+
(eval-analyzed-doc analyzed-doc)))))
277285

278286
(defn eval-doc
279287
"Evaluates the given `doc`."
280288
([doc] (eval-doc {} doc))
281-
([in-memory-cache doc] (+eval-results in-memory-cache doc)))
289+
([in-memory-cache doc]
290+
(+eval-results in-memory-cache
291+
(cond-> doc
292+
config/cache-disabled?
293+
(assoc :no-cache true)))))
282294

283295
(defn eval-file
284296
"Reads given `file` (using `slurp`) and evaluates it."
285297
([file] (eval-file {} file))
286298
([in-memory-cache file]
287299
(->> file
288-
(parser/parse-file {:doc? true})
300+
parser/parse-file
289301
(eval-doc in-memory-cache))))
290302

291303
#_(eval-file "notebooks/hello.clj")
@@ -296,6 +308,9 @@
296308
"Evaluated the given `code-string` using the optional `in-memory-cache` map."
297309
([code-string] (eval-string {} code-string))
298310
([in-memory-cache code-string]
299-
(eval-doc in-memory-cache (parser/parse-clojure-string {:doc? true} code-string))))
311+
(eval-doc in-memory-cache (parser/parse-clojure-string code-string))))
300312

301313
#_(eval-string "(+ 39 3)")
314+
315+
#_(nextjournal.clerk/show! "notebooks/hello.md")
316+

src/nextjournal/clerk/home.clj

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
(ns nextjournal.clerk.home
2-
{:nextjournal.clerk/visibility {:code :hide :result :hide}}
2+
{:nextjournal.clerk/visibility {:code :hide :result :hide}
3+
:nextjournal.clerk/no-cache true}
34
(:require [babashka.fs :as fs]
45
[clojure.string :as str]
56
[nextjournal.clerk :as clerk]

0 commit comments

Comments
 (0)