Skip to content

Commit ca0a915

Browse files
committed
Merge pull request #253 from truecolour/better-stdlib
Various additions to the standard libraries
2 parents 894c514 + 67d8641 commit ca0a915

File tree

10 files changed

+279
-108
lines changed

10 files changed

+279
-108
lines changed

pixie/stdlib.pxi

Lines changed: 147 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,32 @@
164164
(def reduce (fn [rf init col]
165165
(-reduce col rf init)))
166166

167+
(def instance? (fn ^{:doc "Checks if x is an instance of t.
168+
169+
When t is seqable, checks if x is an instance of
170+
any of the types contained therein."
171+
:signatures [[t x]]}
172+
instance? [t x]
173+
(if (-satisfies? ISeqable t)
174+
(let [ts (seq t)]
175+
(if (not ts) false
176+
(or (-instance? (first ts) x)
177+
(instance? (rest ts) x))))
178+
(-instance? t x))))
179+
180+
(def satisfies? (fn ^{:doc "Checks if x satisfies the protocol p.
181+
182+
When p is seqable, checks if x satisfies all of
183+
the protocols contained therein."
184+
:signatures [[t x]]}
185+
satisfies? [p x]
186+
(if (-satisfies? ISeqable p)
187+
(let [ps (seq p)]
188+
(if (not ps) true
189+
(and (-satisfies? (first ps) x)
190+
(satisfies? (rest ps) x))))
191+
(-satisfies? p x))))
192+
167193
(def into (fn ^{:doc "Add the elements of `from` to the collection `to`."
168194
:signatures [[to from]]
169195
:added "0.1"}
@@ -751,7 +777,6 @@ there's a value associated with the key. Use `some` for checking for values."
751777
~then
752778
(cond ~@clauses))))
753779

754-
755780
(defmacro try [& body]
756781
(loop [catch nil
757782
catch-sym nil
@@ -802,7 +827,7 @@ If further arguments are passed, invokes the method named by symbol, passing the
802827
(defn string? [v] (instance? String v))
803828
(defn keyword? [v] (instance? Keyword v))
804829

805-
(defn list? [v] (instance? PersistentList v))
830+
(defn list? [v] (instance? [PersistentList Cons] v))
806831
(defn set? [v] (instance? PersistentHashSet v))
807832
(defn map? [v] (satisfies? IMap v))
808833
(defn fn? [v] (satisfies? IFn v))
@@ -861,17 +886,21 @@ If further arguments are passed, invokes the method named by symbol, passing the
861886
([x y] (not (f x y)))
862887
([x y & more] (not (apply f x y more))))))
863888

864-
(defn some
865-
{:doc "Checks if the predicate is true for any element of the collection.
889+
(defn constantly [x]
890+
{:doc "Return a function that always returns x, no matter what it is called with."
891+
:examples [["(let [f (constantly :me)] [(f 1) (f \"foo\") (f :abc) (f nil)])"
892+
nil [:me :me :me :me]]]}
893+
(fn [& _] x))
866894

867-
Stops if it finds such an element."
895+
(defn some
896+
{:doc "Returns the first true value of the predicate for the elements of the collection."
868897
:signatures [[pred coll]]
869898
:added "0.1"}
870899
[pred coll]
871-
(cond
872-
(nil? (seq coll)) false
873-
(pred (first coll)) true
874-
:else (recur pred (next coll))))
900+
(if-let [coll (seq coll)]
901+
(or (pred (first coll))
902+
(recur pred (next coll)))
903+
false))
875904

876905
(extend -count MapEntry (fn [self] 2))
877906
(extend -nth MapEntry (fn map-entry-nth [self idx]
@@ -1053,7 +1082,7 @@ Creates new maps if the keys are not present."
10531082
([test msg]
10541083
`(if ~test
10551084
nil
1056-
(throw (str "Assert failed " ~msg)))))
1085+
(throw (str "Assert failed: " ~msg)))))
10571086

10581087
(defmacro resolve
10591088
{:doc "Resolve the var associated with the symbol in the current namespace."
@@ -1243,6 +1272,17 @@ and implements IAssociative, ILookup and IObject."
12431272
([n x]
12441273
(take n (repeat x))))
12451274

1275+
(defn repeatedly
1276+
{:doc "Returns a lazy seq that contains the return values of repeated calls to f.
1277+
1278+
Yields an infinite seq with one argument.
1279+
With two arguments n specifies the number of elements."
1280+
:examples [["(into '(:batman!) (repeatedly 8 (fn [] :na)))"
1281+
nil (:na :na :na :na :na :na :na :na :batman!)]]
1282+
:signatures [[f] [n f]]}
1283+
([f] (lazy-seq (cons (f) (repeatedly f))))
1284+
([n f] (take n (repeatedly f))))
1285+
12461286
(defmacro doseq
12471287
{:doc "Evaluates all elements of the seq, presumably for side effects. Returns nil."
12481288
:added "0.1"}
@@ -1476,6 +1516,13 @@ The new value is thus `(apply f current-value-of-atom args)`."
14761516
(recur (dec n) (next s))
14771517
s)))
14781518

1519+
(defn split-at
1520+
{:doc "Returns a vector of the first n elements of the collection, and the remaining elements."
1521+
:examples [["(split-at 2 [:a :b :c :d :e])" nil
1522+
[(:a :b) (:c :d :e)]]]}
1523+
[n coll]
1524+
[(take n coll) (drop n coll)])
1525+
14791526
(defmacro while
14801527
{:doc "Repeatedly executes body while test expression is true. Presumes
14811528
some side-effect will cause test to become false/nil. Returns nil"
@@ -1568,8 +1615,21 @@ not enough elements were present."
15681615
:added "0.1"}
15691616
([n coll] (partition n n coll))
15701617
([n step coll]
1571-
(when-let [s (seq coll)]
1572-
(cons (take n s) (partition n step (drop step s))))))
1618+
(when-let [s (seq coll)]
1619+
(lazy-seq
1620+
(cons (take n s) (partition n step (drop step s)))))))
1621+
1622+
(defn partitionf [f coll]
1623+
{:doc "A generalized version of partition. Instead of taking a constant number of elements,
1624+
this function calls f with the remaining collection to determine how many elements to
1625+
take."
1626+
:examples [["(partitionf first [1 :a, 2 :a b, 3 :a :b :c])"
1627+
nil ((1 :a) (2 :a :b) (3 :a :b :c))]]}
1628+
(when-let [s (seq coll)]
1629+
(lazy-seq
1630+
(let [n (f s)]
1631+
(cons (take n s)
1632+
(partitionf f (drop n coll)))))))
15731633

15741634
(defn destructure [binding expr]
15751635
(cond
@@ -1942,6 +2002,9 @@ user => (refer 'pixie.string :exclude '(substring))"
19422002
(pred (first coll)) (recur pred (next coll))
19432003
:else false))
19442004

2005+
; If you want a fn that uses destructuring in its parameter list, place
2006+
; it after this definition. If you don't, you will get compile failures
2007+
; in unrelated files.
19452008
(defmacro fn
19462009
{:doc "Creates a function.
19472010

@@ -2115,6 +2178,45 @@ Expands to calls to `extend-type`."
21152178
[~@body])))]
21162179
`(or (seq ~(gen-loop [] bindings)) '())))
21172180

2181+
(defn reverse
2182+
; TODO: We should probably have a protocol IReversible, so we can e.g.
2183+
; reverse vectors efficiently, etc..
2184+
[coll]
2185+
"Returns a collection that contains all the elements of the argument in reverse order."
2186+
(into () coll))
2187+
2188+
;; TODO: implement :>> like in Clojure?
2189+
(defmacro condp
2190+
"Takes a binary predicate, an expression and a number of two-form clauses.
2191+
Calls the predicate on the first value of each clause and the expression.
2192+
If the result is truthy returns the second value of the clause.
2193+
2194+
If the number of arguments is odd and no clause matches, the last argument is returned.
2195+
If the number of arguments is even and no clause matches, throws an exception."
2196+
[pred-form expr & clauses]
2197+
(let [x (gensym 'expr), pred (gensym 'pred)]
2198+
`(let [~x ~expr, ~pred ~pred-form]
2199+
(cond ~@(mapcat
2200+
(fn [[a b :as clause]]
2201+
(if (> (count clause) 1)
2202+
`((~pred ~a ~x) ~b)
2203+
`(:else ~a)))
2204+
(partition 2 clauses))
2205+
:else (throw "No matching clause!")))))
2206+
2207+
(defmacro case
2208+
"Takes an expression and a number of two-form clauses.
2209+
Checks for each clause if the first part is equal to the expression.
2210+
If yes, returns the value of the second part.
2211+
2212+
The first part of each clause can also be a set. If that is the case, the clause matches when the result of the expression is in the set.
2213+
2214+
If the number of arguments is odd and no clause matches, the last argument is returned.
2215+
If the number of arguments is even and no clause matches, throws an exception."
2216+
[expr & args]
2217+
`(condp #(if (set? %1) (%1 %2) (= %1 %2))
2218+
~expr ~@args))
2219+
21182220
(defmacro use
21192221
[ns]
21202222
`(do
@@ -2176,14 +2278,47 @@ Expands to calls to `extend-type`."
21762278
(mapcat walk (children node))))))]
21772279
(walk root)))
21782280

2281+
(defn flatten [x]
2282+
; TODO: laziness?
2283+
{:doc "Takes any nested combination of ISeqable things, and return their contents
2284+
as a single, flat sequence.
2285+
2286+
Calling this function on something that is not ISeqable returns a seq with that
2287+
value as its only element."
2288+
:examples [["(flatten [[1 2 [3 4] [5 6]] 7])" nil [1 2 3 4 5 6 7]]
2289+
["(flatten :this)" nil [:this]]]}
2290+
(if (not (satisfies? ISeqable x)) [x]
2291+
(transduce (comp (map flatten) cat)
2292+
conj []
2293+
(seq x))))
2294+
2295+
(defn juxt [& fns]
2296+
{:doc "Returns a function that applies all fns to its arguments,
2297+
and returns a vector of the results."
2298+
:examples [["((juxt + - *) 2 3)" nil [5 -1 6]]]}
2299+
(fn [& args]
2300+
(mapv #(apply % args) fns)))
2301+
2302+
(defn map-invert
2303+
{:doc "Returns a map where the vals are mapped to the keys."
2304+
:examples [["(map-invert {:a :b, :c :d})" nil {:b :a, :d :c}]]}
2305+
[m]
2306+
(reduce (fn [m* ent]
2307+
(assoc m* (val ent) (key ent)))
2308+
{} m))
2309+
21792310
(defn mapv
21802311
([f col]
21812312
(transduce (map f) conj col)))
21822313

2314+
(def *1)
2315+
(def *2)
2316+
(def *3)
21832317
(defn -push-history [x]
21842318
(def *3 *2)
21852319
(def *2 *1)
21862320
(def *1 x))
21872321

2322+
(def *e)
21882323
(defn -set-*e [e]
21892324
(def *e e))

pixie/string.pxi

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
; reexport native string functions
55
(def substring si/substring)
6-
(def index-of si/index-of)
6+
(def index-of (comp #(if (not= -1 %) %) si/index-of))
77
(def split si/split)
88

99
(def ends-with? si/ends-with)
@@ -17,24 +17,33 @@
1717
(def lower-case si/lower-case)
1818
(def upper-case si/upper-case)
1919

20+
; TODO: There should be locale-aware variants of these values
21+
(def lower "abcdefghijklmnopqrstuvwxyz")
22+
(def upper "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
23+
(def digits "0123456789")
24+
(def punctuation "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
25+
(def whitespace (str \space \newline \tab \backspace \formfeed \return))
26+
(def letters (str lower upper))
27+
(def printable (str letters digits punctuation whitespace))
28+
(def hexdigits "0123456789abcdefABCDEF")
29+
(def octdigits "012345678")
30+
2031
(defn replace
2132
"Replace all occurrences of x in s with r."
2233
[s x r]
2334
(let [offset (if (zero? (count x)) (+ 1 (count r)) (count r))]
2435
(loop [start 0
2536
s s]
26-
(let [i (index-of s x start)]
27-
(if (neg? i)
28-
s
29-
(recur (+ i offset) (str (substring s 0 i) r (substring s (+ i (count x))))))))))
37+
(if-let [i (index-of s x start)]
38+
(recur (+ i offset) (str (substring s 0 i) r (substring s (+ i (count x)))))
39+
s))))
3040

3141
(defn replace-first
3242
"Replace the first occurrence of x in s with r."
3343
[s x r]
34-
(let [i (index-of s x)]
35-
(if (neg? i)
36-
s
37-
(str (substring s 0 i) r (substring s (+ i (count x)))))))
44+
(if-let [i (index-of s x)]
45+
(str (substring s 0 i) r (substring s (+ i (count x))))
46+
s))
3847

3948
(defn join
4049
{:doc "Join the elements of the collection using an optional separator"
@@ -54,7 +63,7 @@
5463
"True if s is nil, empty, or contains only whitespace."
5564
[s]
5665
(if s
57-
(let [white #{\space \newline \tab \backspace \formfeed \return}
66+
(let [white (set whitespace)
5867
length (count s)]
5968
(loop [index 0]
6069
(if (= length index)
@@ -63,3 +72,32 @@
6372
(recur (inc index))
6473
false))))
6574
true))
75+
76+
(defmacro interp
77+
; TODO: This might merit special read syntax
78+
{:doc "String interpolation."
79+
:examples [["(require pixie.string :refer [interp])"]
80+
["(interp \"2 plus 2 is $(+ 2 2)$!\")" nil "2 plus 2 is 4!"]
81+
["(let [x \"locals\"] (interp \"You can use arbitrary forms; for example $x$\"))"
82+
nil "You can use arbitrary forms; for example locals"]
83+
["(interp \"$$$$ is the escape for a literal $$\")"
84+
nil "$$ is the escape for a literal $"]
85+
]}
86+
[txt]
87+
(loop [forms [], txt txt]
88+
(cond
89+
(empty? txt) `(str ~@ forms)
90+
(starts-with? txt "$")
91+
(let [pos (or (index-of txt "$" 1)
92+
(throw "Unmatched $ in interp argument!"))
93+
form-str (subs txt 1 pos)
94+
form (if (empty? form-str) "$"
95+
(read-string form-str))
96+
rest-str (subs txt (inc pos))]
97+
(recur (conj forms form) rest-str))
98+
:else
99+
(let [pos (or (index-of txt "$")
100+
(count txt))
101+
form (subs txt 0 pos)
102+
rest-str (subs txt pos)]
103+
(recur (conj forms form) rest-str)))))

pixie/vm/compiler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ def compile_form(form, ctx):
353353
ctx.push_const(nil)
354354
return
355355

356-
if rt.satisfies_QMARK_(rt.ISeq.deref(), form) and form is not nil:
356+
if rt._satisfies_QMARK_(rt.ISeq.deref(), form) and form is not nil:
357357
form = macroexpand(form)
358358
return compile_cons(form, ctx)
359359
if isinstance(form, numbers.Integer):
@@ -425,7 +425,7 @@ def compile_form(form, ctx):
425425
compile_set_literal(form, ctx)
426426
return
427427

428-
if rt.satisfies_QMARK_(rt.IMap.deref(), form):
428+
if rt._satisfies_QMARK_(rt.IMap.deref(), form):
429429
compile_map_literal(form, ctx)
430430
return
431431

@@ -480,7 +480,7 @@ def compile_fn(form, ctx):
480480

481481

482482

483-
if rt.satisfies_QMARK_(rt.ISeq.deref(), rt.first(form)):
483+
if rt._satisfies_QMARK_(rt.ISeq.deref(), rt.first(form)):
484484
arities = []
485485
while form is not nil:
486486
required_arity, argc = compile_fn_body(name, rt.first(rt.first(form)), rt.next(rt.first(form)), ctx)

pixie/vm/libs/pxic/writer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,11 @@ def write_object(obj, wtr):
255255
elif isinstance(obj, Var):
256256
#wtr.write_cached_obj(obj, write_var)
257257
write_var(obj, wtr)
258-
elif rt.satisfies_QMARK_(rt.IMap.deref(), obj):
258+
elif rt._satisfies_QMARK_(rt.IMap.deref(), obj):
259259
write_map(obj, wtr)
260-
elif rt.satisfies_QMARK_(rt.IVector.deref(), obj):
260+
elif rt._satisfies_QMARK_(rt.IVector.deref(), obj):
261261
write_vector(obj, wtr)
262-
elif rt.satisfies_QMARK_(rt.ISeq.deref(), obj):
262+
elif rt._satisfies_QMARK_(rt.ISeq.deref(), obj):
263263
write_seq(obj, wtr)
264264
elif isinstance(obj, Keyword):
265265
wtr.write_cached_obj(obj, write_keyword)

pixie/vm/persistent_vector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ def _eq(self, obj):
456456
return false
457457
return true
458458
else:
459-
if obj is nil or not rt.satisfies_QMARK_(proto.ISeqable, obj):
459+
if obj is nil or not rt._satisfies_QMARK_(proto.ISeqable, obj):
460460
return false
461461
seq = rt.seq(obj)
462462
for i in range(0, intmask(self._cnt)):

0 commit comments

Comments
 (0)