Skip to content

Commit 31acbf0

Browse files
authored
[#399] Support nested libspecs
1 parent e661cee commit 31acbf0

File tree

3 files changed

+108
-75
lines changed

3 files changed

+108
-75
lines changed

src/sci/impl/analyzer.cljc

+12-8
Original file line numberDiff line numberDiff line change
@@ -278,11 +278,11 @@
278278

279279
(defn expand-as->
280280
"The ->> macro from clojure.core."
281-
[ctx [_as expr name & forms]]
282-
(let [[let-bindings & body] `([~name ~expr
283-
~@(interleave (repeat name) (butlast forms))]
281+
[ctx [_as expr nm & forms]]
282+
(let [[let-bindings & body] `([~nm ~expr
283+
~@(interleave (repeat nm) (butlast forms))]
284284
~(if (empty? forms)
285-
name
285+
nm
286286
(last forms)))]
287287
(expand-let* ctx let-bindings body)))
288288

@@ -600,18 +600,22 @@
600600
(loop [exprs exprs
601601
ret []]
602602
(if exprs
603-
(let [[k & args] (first exprs)]
603+
(let [[k & args :as expr] (first exprs)]
604604
(case k
605605
(:require :use)
606606
(recur (next exprs)
607607
(conj ret
608608
(mark-eval-call
609-
(list* (symbol (name k)) args))))
610-
:import (recur (next exprs) (conj ret (mark-eval-call (list* 'import args))))
609+
(with-meta (list* (symbol (name k)) args)
610+
(meta expr)))))
611+
:import (recur (next exprs) (conj ret (mark-eval-call
612+
(with-meta (list* 'import args)
613+
(meta expr)))))
611614
:refer-clojure (recur (next exprs)
612615
(conj ret
613616
(mark-eval-call
614-
(list* 'refer 'clojure.core args))))
617+
(with-meta (list* 'refer 'clojure.core args)
618+
(meta expr)))))
615619
:gen-class ;; ignore
616620
(recur (next exprs) ret)))
617621
(mark-eval-call (list* 'do ret))))))

src/sci/impl/interpreter.cljc

+80-66
Original file line numberDiff line numberDiff line change
@@ -139,25 +139,6 @@
139139
(str "Could not resolve symbol: " sym "\nks:" (keys (:bindings ctx)))
140140
sym))))
141141

142-
(defn parse-libspec [libspec]
143-
(cond
144-
(sequential? libspec)
145-
(let [[lib-name & opts] libspec]
146-
(loop [ret {:lib-name lib-name}
147-
[opt-name fst-opt & rst-opts] opts]
148-
(if-not opt-name ret
149-
(case opt-name
150-
:as (recur (assoc ret :as fst-opt)
151-
rst-opts)
152-
(:reload :reload-all :verbose) (recur
153-
(assoc ret :reload true)
154-
(cons fst-opt rst-opts))
155-
(:refer :rename :exclude :only) (recur (assoc ret opt-name fst-opt)
156-
rst-opts)))))
157-
(symbol? libspec) {:lib-name libspec}
158-
:else (throw (new #?(:clj Exception :cljs js/Error)
159-
(str "Invalid libspec: " libspec)))))
160-
161142
(declare eval-string*)
162143

163144
(defn handle-refer-all [the-current-ns the-loaded-ns include-sym? rename-sym only]
@@ -172,8 +153,8 @@
172153
the-loaded-ns)))
173154

174155
(defn handle-require-libspec-env
175-
[ctx env use? current-ns the-loaded-ns lib-name
176-
{:keys [:as :refer :rename :exclude :only] :as _parsed-libspec}]
156+
[ctx env current-ns the-loaded-ns lib-name
157+
{:keys [:as :refer :rename :exclude :only :use] :as _parsed-libspec}]
177158
(let [the-current-ns (get-in env [:namespaces current-ns]) ;; = ns-data?
178159
the-current-ns (if as (assoc-in the-current-ns [:aliases as] lib-name)
179160
the-current-ns)
@@ -187,7 +168,7 @@
187168
the-current-ns
188169
(cond refer
189170
(cond (or (kw-identical? :all refer)
190-
use?)
171+
use)
191172
(handle-refer-all the-current-ns the-loaded-ns include-sym? rename-sym nil)
192173
(sequential? refer)
193174
(reduce (fn [ns sym]
@@ -203,84 +184,115 @@
203184
refer)
204185
:else (throw (new #?(:clj Exception :cljs js/Error)
205186
(str ":refer value must be a sequential collection of symbols"))))
206-
use? (handle-refer-all the-current-ns the-loaded-ns include-sym? rename-sym only)
187+
use (handle-refer-all the-current-ns the-loaded-ns include-sym? rename-sym only)
207188
:else the-current-ns)
208189
env (assoc-in env [:namespaces current-ns] the-current-ns)]
209190
(when-let [on-loaded (some-> the-loaded-ns :obj meta :sci.impl/required-fn)]
210191
(on-loaded {}))
211192
env))
212193

213194
(defn handle-require-libspec
214-
[ctx libspec use?]
215-
(let [{:keys [:lib-name :reload] :as parsed-libspec} (parse-libspec libspec)
195+
[ctx lib opts]
196+
(let [{:keys [:reload]} opts
216197
env* (:env ctx)
217198
env @env* ;; NOTE: loading namespaces is not (yet) thread-safe
218199
cnn (vars/current-ns-name)
219200
namespaces (get env :namespaces)
220201
uberscript (:uberscript ctx)
221202
reload* (or reload uberscript)]
222-
(if-let [the-loaded-ns (when-not reload* (get namespaces lib-name))]
223-
(reset! env* (handle-require-libspec-env ctx env use? cnn the-loaded-ns lib-name parsed-libspec))
203+
(if-let [the-loaded-ns (when-not reload* (get namespaces lib))]
204+
(reset! env* (handle-require-libspec-env ctx env cnn the-loaded-ns lib opts))
224205
(if-let [load-fn (:load-fn env)]
225-
(if-let [{:keys [:file :source]} (load-fn {:namespace lib-name
206+
(if-let [{:keys [:file :source]} (load-fn {:namespace lib
226207
:reload reload})]
227208
(do
228209
(try (vars/with-bindings
229210
{vars/current-ns @vars/current-ns
230211
vars/current-file file}
231212
(eval-string* (assoc ctx :bindings {}) source))
232213
(catch #?(:clj Exception :cljs js/Error) e
233-
(swap! env* update :namespaces dissoc lib-name)
214+
(swap! env* update :namespaces dissoc lib)
234215
(throw e)))
235216
(swap! env* (fn [env]
236217
(let [namespaces (get env :namespaces)
237-
the-loaded-ns (get namespaces lib-name)]
238-
(handle-require-libspec-env ctx env use? cnn
218+
the-loaded-ns (get namespaces lib)]
219+
(handle-require-libspec-env ctx env cnn
239220
the-loaded-ns
240-
lib-name parsed-libspec)))))
221+
lib opts)))))
241222
(or (when reload*
242-
(when-let [the-loaded-ns (get namespaces lib-name)]
243-
(reset! env* (handle-require-libspec-env ctx env use? cnn the-loaded-ns lib-name parsed-libspec))))
223+
(when-let [the-loaded-ns (get namespaces lib)]
224+
(reset! env* (handle-require-libspec-env ctx env cnn the-loaded-ns lib opts))))
244225
(throw (new #?(:clj Exception :cljs js/Error)
245-
(str "Could not find namespace: " lib-name ".")))))
226+
(str "Could not find namespace: " lib ".")))))
246227
(throw (new #?(:clj Exception :cljs js/Error)
247-
(str "Could not find namespace " lib-name ".")))))))
248-
249-
(defn eval-require*
250-
[ctx args use?]
251-
(loop [libspecs []
252-
current-libspec nil
253-
args args]
254-
(if args
255-
(let [ret (interpret ctx (first args))]
256-
(cond
257-
(symbol? ret)
258-
(recur (cond-> libspecs
259-
current-libspec (conj current-libspec))
260-
[ret]
261-
(next args))
262-
(keyword? ret)
263-
(recur (conj libspecs (conj current-libspec ret))
264-
nil
265-
(next args))
266-
:else
267-
(recur (cond-> libspecs
268-
current-libspec (conj current-libspec))
269-
ret
270-
(next args))))
271-
(let [libspecs (cond-> libspecs
272-
current-libspec (conj current-libspec))]
273-
(run! #(handle-require-libspec ctx % use?) libspecs)))))
228+
229+
(str "Could not find namespace " lib ".")))))))
230+
231+
(defn load-lib [ctx prefix lib & options]
232+
(when (and prefix (pos? (.indexOf (name lib) #?(:clj (int \.)
233+
:cljs \.))))
234+
(throw-error-with-location (str "Found lib name '" (name lib) "' containing period with prefix '"
235+
prefix "'. lib names inside prefix lists must not contain periods")
236+
lib))
237+
(let [lib (if prefix (symbol (str prefix \. lib)) lib)
238+
opts (apply hash-map options)]
239+
(handle-require-libspec ctx lib opts)))
240+
241+
(defn- prependss
242+
"Prepends a symbol or a seq to coll"
243+
[x coll]
244+
(if (symbol? x)
245+
(cons x coll)
246+
(concat x coll)))
247+
248+
(defn- libspec?
249+
"Returns true if x is a libspec"
250+
[x]
251+
(or (symbol? x)
252+
(and (vector? x)
253+
(or
254+
(nil? (second x))
255+
(keyword? (second x))))))
256+
257+
(defn- load-libs
258+
"Loads libs, interpreting libspecs, prefix lists, and flags for
259+
forwarding to load-lib"
260+
[ctx kw args]
261+
(let [args* (cons kw args)
262+
flags (filter keyword? args*)
263+
opts (interleave flags (repeat true))
264+
args* (filter (complement keyword?) args*)]
265+
;; check for unsupported options
266+
(let [supported #{:as :reload :reload-all :require :use :verbose :refer}
267+
unsupported (seq (remove supported flags))]
268+
(when unsupported
269+
(throw-error-with-location (apply str "Unsupported option(s) supplied: "
270+
(interpose \, unsupported))
271+
;; best effort location
272+
args)))
273+
;; check a load target was specified
274+
(when-not (seq args*)
275+
(throw-error-with-location "Nothing specified to load"
276+
args))
277+
(doseq [arg args*]
278+
(if (libspec? arg)
279+
(apply load-lib ctx nil (prependss arg opts))
280+
(let [[prefix & args*] arg]
281+
(when (nil? prefix)
282+
(throw-error-with-location "prefix cannot be nil"
283+
args))
284+
(doseq [arg args*]
285+
(apply load-lib ctx prefix (prependss arg opts))))))))
274286

275287
(defn eval-require
276288
[ctx & args]
277-
(eval-require* ctx args false))
289+
(load-libs ctx :require args))
278290

279291
(vreset! utils/eval-require-state eval-require)
280292

281293
(defn eval-use
282294
[ctx & args]
283-
(eval-require* ctx args true))
295+
(load-libs ctx :use args))
284296

285297
(vreset! utils/eval-use-state eval-use)
286298

@@ -567,8 +579,10 @@
567579
in-ns (eval-in-ns ctx expr)
568580
set! (eval-set! ctx expr)
569581
refer (apply eval-refer ctx (rest expr))
570-
require (apply eval-require ctx (rest expr))
571-
use (apply eval-use ctx (rest expr))
582+
require (apply eval-require ctx (with-meta (rest expr)
583+
(meta expr)))
584+
use (apply eval-use ctx (with-meta (rest expr)
585+
(meta expr)))
572586
resolve (eval-resolve ctx (second expr))
573587
macroexpand-1 (macroexpand-1 ctx (interpret ctx (second expr)))
574588
macroexpand (macroexpand ctx (interpret ctx (second expr)))

test/sci/namespaces_test.cljc

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(ns sci.namespaces-test
22
(:require
33
[clojure.set :as set]
4-
[clojure.test :as test :refer [deftest is]]
4+
[clojure.test :as test :refer [deftest is testing]]
55
[sci.test-utils :as tu]))
66

77
(defn eval*
@@ -92,3 +92,18 @@
9292
#?(:clj Exception :cljs js/Error)
9393
#"symbol"
9494
(eval* "(ns)"))))
95+
96+
(deftest nested-libspecs-test
97+
(is (= #{1 2 3 4} (eval* "(require '[clojure [set :refer [union]]]) (union #{1 2 3} #{2 3 4})")))
98+
(is (thrown-with-msg?
99+
#?(:clj Exception :cljs js/Error)
100+
#"lib names inside prefix lists must not contain periods"
101+
(eval* "(ns clojure.core.protocols) (ns foo) (require '[clojure [core.protocols]])")))
102+
(is (thrown-with-msg?
103+
#?(:clj Exception :cljs js/Error)
104+
#"Unsupported option\(s\) supplied: :foo"
105+
(eval* "(ns foo (:require [clojure.core] [dude] :foo))")))
106+
(testing "error message contains location"
107+
(is (thrown-with-data?
108+
{:line 1 :column 9}
109+
(eval* "(ns foo (:require [clojure.core] [dude] :foo))")))))

0 commit comments

Comments
 (0)