/
core.cljc
199 lines (172 loc) · 7.24 KB
/
core.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
(ns sci.core
(:refer-clojure :exclude [with-bindings with-in-str with-out-str
with-redefs binding future pmap alter-var-root
ns])
(:require
[sci.impl.interpreter :as i]
[sci.impl.io :as sio]
[sci.impl.macros :as macros]
[sci.impl.opts :as opts]
[sci.impl.vars :as vars])
#?(:cljs (:require-macros [sci.core :refer [with-bindings with-out-str]])))
#?(:clj (set! *warn-on-reflection* true))
(defn new-var
"Alpha! Returns a new sci var. API subject to change."
([name] (doto (new-var name nil nil)
(vars/unbind)))
([name val] (new-var name val (meta name)))
([name init-val meta] (sci.impl.vars.SciVar. init-val name meta)))
(defn new-dynamic-var
"Alpha! Same as new-var but adds :dynamic true to meta. API subject to
change."
([name] (doto (new-dynamic-var name nil nil)
(vars/unbind)))
([name init-val] (new-dynamic-var name init-val (meta name)))
([name init-val meta] (sci.impl.vars.SciVar. init-val name (assoc meta :dynamic true))))
;; (defn set-var-root!
;; "Alpha! Sets root of sci var. API subject to change."
;; [sci-var root-val]
;; (vars/bindRoot sci-var root-val))
(macros/deftime
(defmacro with-bindings
"Macro for binding sci vars. Must be called with map of sci dynamic
vars to values. Used in babashka."
[bindings-map & body]
`(let [bm# ~bindings-map]
(assert (map? bm#))
(vars/push-thread-bindings bm#) ;; important: outside try
(try
(do ~@body)
(finally (vars/pop-thread-bindings)))))
(defmacro binding
"Macro for binding sci vars. Must be called with a vector of sci
dynamic vars to values."
[bindings & body]
(assert (vector? bindings))
`(with-bindings ~(apply hash-map bindings)
(do ~@body)))
#_(defmacro with-redefs
"Temporarily redefines sci vars while executing the body. The
temp-value-exprs will be evaluated and each resulting value will
replace in parallel the root value of its Var. After the body is
executed, the root values of all the sci vars will be set back to
their old values. These temporary changes will be visible in all
threads. Useful for mocking out functions during testing."
[bindings & body]
(assert (vector? bindings))
(let [binding-map (apply hash-map bindings)]
`(let [root-bind# (fn [m#]
(doseq [[a-var# a-val#] m#]
(vars/bindRoot a-var# a-val#)))
bm# ~binding-map
ks# (keys bm#)
_# (doseq [k# ks#]
(assert (sci.impl.vars/var? k#) (str k# " is not a var.")))
old-vals# (zipmap ks#
(map #(vars/getRawRoot %) ks#))]
(try
(root-bind# bm#)
(do ~@body)
(finally
(root-bind# old-vals#)))))))
(def in "Sci var that represents sci's `clojure.core/*in*`" sio/in)
(def out "Sci var that represents sci's `clojure.core/*out*`" sio/out)
(def err "Sci var that represents sci's `clojure.core/*err*`" sio/err)
(def ns "Sci var that represents sci's `clojure.core/*ns*`" vars/current-ns)
(def file "Sci var that represents sci's `clojure.core/*file*`" vars/current-file)
(def print-length "Sci var that represents sci's `clojure.core/*print-length*`" sio/print-length)
(macros/deftime
(defmacro with-in-str
"Evaluates body in a context in which sci's *in* is bound to a fresh
StringReader initialized with the string s."
[s & body]
`(let [in# (-> (java.io.StringReader. ~s)
(clojure.lang.LineNumberingPushbackReader.))]
(with-bindings {in in#}
(do ~@body)))))
(macros/deftime
(defmacro with-out-str
"Evaluates exprs in a context in which sci's *out* is bound to a fresh
StringWriter. Returns the string created by any nested printing
calls."
[& body]
`(let [out# (macros/? :clj (java.io.StringWriter.)
:cljs (goog.string/StringBuffer.))]
(with-bindings {out out#}
(do ~@body)
(str out#)))))
(macros/deftime
(defmacro future
"Like clojure.core/future but also conveys sci bindings to the thread."
[& body]
`(let [f# (-> (fn [] ~@body)
(vars/binding-conveyor-fn))]
(future-call f#))))
#?(:clj (defn pmap
"Like clojure.core/pmap but also conveys sci bindings to the threads."
([f coll]
(let [n (+ 2 (.. Runtime getRuntime availableProcessors))
rets (map #(future (f %)) coll)
step (fn step [[x & xs :as vs] fs]
(lazy-seq
(if-let [s (seq fs)]
(cons (deref x) (step xs (rest s)))
(map deref vs))))]
(step rets (drop n rets))))
([f coll & colls]
(let [step (fn step [cs]
(lazy-seq
(let [ss (map seq cs)]
(when (every? identity ss)
(cons (map first ss) (step (map rest ss)))))))]
(pmap #(apply f %) (step (cons coll colls)))))))
(defn alter-var-root
"Atomically alters the root binding of sci var v by applying f to its
current value plus any args."
[v f & args]
(apply vars/alter-var-root v f args))
(defn eval-string
"Evaluates string `s` as one or multiple Clojure expressions using the Small Clojure Interpreter.
The map `opts` may contain the following:
- `:bindings`: a map of symbols to values, e.g.: `{'x 1}`. The
symbols will acts as names bound to the corresponding values in the
expressions.
- `:namespaces`: a map of symbols to namespaces, where a namespace
is a map with symbols to values, e.g.: `{'foo.bar {'x 1}}`. These
namespaces can be used with `require`.
- `:allow`: a seqable of allowed symbols. All symbols, even those
brought in via `:bindings` or `:namespaces` have to be explicitly
enumerated.
- `:deny`: a seqable of disallowed symbols, e.g.: `[loop quote
recur]`.
- `:realize-max`: integer; when provided, program may realize a
maximum number of elements from sequences, e.g. `(vec (range))` will
throw for any number. This also applies to sequences returned from
the expression to the caller.
- `:preset`: a pretermined set of options. Currently only
`:termination-safe` is supported, which will set `:realize-max` to
`100` and disallows the symbols `loop`, `recur` and `trampoline`.
- `:features`: when provided a non-empty set of keywords, sci will process reader conditionals using these features (e.g. #{:bb}).
- `:env`: an atom with a map in which state from the
evaluation (defined namespaced and vars) will be persisted for
re-use over multiple calls.
"
([s] (eval-string s nil))
([s opts]
(i/eval-string s opts)))
(defn init
"Creates an initial sci context from given options `opts`. The context
can be used with `eval-string*`. See `eval-string` for available
options. The internal organization of the context is implementation
detail and may change in the future."
[opts]
(opts/init opts))
(defn eval-string*
"Evaluates string `s` in the context of `ctx` (as produced with
`init`)."
[ctx s]
(sci.impl.interpreter/eval-string* ctx s))
;;;; Scratch
(comment
(eval-string "(inc x)" {:bindings {'x 2}})
)