-
Notifications
You must be signed in to change notification settings - Fork 0
/
pilisp_core_pilisp.dart
2340 lines (2053 loc) · 74.8 KB
/
pilisp_core_pilisp.dart
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// ;; -*- mode: pilisp -*-
final piLispCore = r'''
(def fn
{:macro true
:doc "Define functions."}
(fn* fn [& args] (cons 'fn* args)))
(def let
{:macro true
:doc "Create local bindings."}
(fn* let [& args] (cons 'let* args)))
(declare map)
(def prn
{:doc "Print to STDOUT in a way that can be read by PiLisp, and append a newline."}
(fn* [& xs]
;; NB: print is Dart's cross-platform print() and always appends a newline.
(apply print (map pr-str xs))))
(def gensym
{:doc "Returns a new symbol with a unique name. If a prefix string is supplied, the name is prefix# where # is some unique number. If prefix is not supplied, the prefix is 'PL__'."}
(fn* gensym
([] (gensym "PL__"))
([prefix-string] (symbol (str prefix-string (next-symbol-id))))))
;; defmacro <- defn <- or
(def or
{:macro true
:doc "Return first truthy value or last value if all are falsey."}
(fn* or
([] nil)
([x] x)
([x & next]
(let [or_ (gensym "or")]
(list 'let [or_ x]
(list 'if or_
or_
(cons 'or next)))))))
;; defmacro <- defn <- string?
(def string?
{:doc "Returns true if the argument is a string."}
(fn string?
[x]
(= 'String (type x))))
;; defmacro <- defn <- map?
(def map?
{:doc "Returns true if the argument is a map."}
(fn map?
[x]
(= 'map (type x))))
(def defn
{.macro true}
(fn defn [name metadata & arity-definitions]
(let [explicit-metadata? (or (string? metadata) (map? metadata))
arity-definitions (if explicit-metadata?
arity-definitions
(cons metadata arity-definitions))]
(list 'def name (if explicit-metadata?
metadata
{})
(cons 'fn* (cons name arity-definitions))))))
(defn when-not
{:doc "Execute the body when the test is falsey. Returns nil otherwise."
:macro true}
[test & body]
(list 'if test nil (cons 'do body)))
(defn assert
{:macro true
:doc "Evaluates expr and throws an exception if it does not evaluate to logical true."}
([x]
(list 'when-not x
(list 'throw (list 'ex-info (list 'str "Assert failed: " (list 'quote x)) {}))))
([x message]
(list 'when-not x
(list 'throw (list 'ex-info (list 'str "Assert failed: " message "\n" (list 'quote x)) {})))))
(defn not
{:doc "Returns true if falsey, else false."}
[x]
(if x false true))
(defn println
{:doc "Print to STDOUT and append a newline."}
[& xs]
;; NB. For core PiLisp, Dart's print is used, which appends a new line.
(apply print xs #_(conj xs "\n")))
;; # State/"Atom"
(def ! write-state)
(def atom state)
(def reset! write-state)
(def swap! write-state)
;; # Math
;; dart/dart-math-pow
;; dart/dart-math-acos
;; dart/dart-math-log
;; dart/dart-math-cos
;; dart/dart-math-tan
;; dart/dart-math-sin
;; dart/dart-math-exp
;; dart/dart-math-asin
;; dart/dart-math-atan
;; dart/dart-math-sqrt
;; dart/dart-math-atan2
(defn inc [n] (+ n 1))
(defn dec [n] (- n 1))
(defn zero? [n] (= 0 n))
(defn pos? [n] (> n 0))
(defn neg? [n] (< n 0))
(defn nat-int? [n] (>= n 0))
(defn mod
{:doc "Modulus of num and div."}
[num div]
(let [m (rem num div)]
(if (or (zero? m) (= (pos? num) (pos? div)))
m
(+ m div))))
(defn even? [n] (zero? (mod n 2)))
(defn odd? [n] (not (even? n)))
;; # Collections
(def rest next)
(defn nnext [coll] (next (next coll)))
(defn nth
{:doc "Return nth item from collection, or default if not present."}
([coll idx] (nth* coll idx))
([coll idx default]
(assert (= 'int (type idx)))
(if (>= idx (count coll))
default
(nth* coll idx))))
(defn first [coll] (nth coll 0))
(defn second [coll] (nth coll 1))
(defn third [coll] (nth coll 2))
(defn fourth [coll] (nth coll 3))
(defn fifth [coll] (nth coll 4))
(defn sixth [coll] (nth coll 5))
(defn seventh [coll] (nth coll 6))
(defn eighth [coll] (nth coll 7))
(defn ninth [coll] (nth coll 8))
(defn tenth [coll] (nth coll 9))
(defn last [coll] (nth coll (dec (count coll))))
(defn lst
{:doc "Construct a list from the coll. See also: map, set, vec"}
[coll]
(apply list coll))
(def defmacro
{.macro true}
(fn defmacro [name metadata & arity-definitions]
(let [explicit-metadata? (or (string? metadata) (map? metadata))
arity-definitions (if explicit-metadata?
arity-definitions
(cons metadata arity-definitions))]
(list 'def name (assoc*
(if explicit-metadata?
metadata
{})
.macro true)
(cons 'fn* (cons name arity-definitions))))))
(defmacro comment
{:doc "Ignores body, yields nil"}
[& body])
;; TODO Hierarchies
;; TODO Method preference (after hierarchies)
(defmacro defmulti
{:doc "Create a new type or dispatch-based multi-method."}
[name metadata dispatch]
(let [explicit-metadata? (or (string? metadata) (map? metadata))]
(list 'def name (if explicit-metadata?
metadata
{})
(dart/PLMultiMethod. name (= 'type dispatch)))))
(declare resolve)
(defmacro defmethod
{:doc "Add a new method implementation for the given multi-method."}
[name dispatch-value metadata & arity-definitions]
(let [explicit-metadata? (or (string? metadata) (map? metadata))
arity-definitions (if explicit-metadata?
arity-definitions
(cons metadata arity-definitions))
multi-method (.value (resolve name))]
(if (dart/PLMultiMethod.isTypeDispatched multi-method)
(list 'dart/PLMultiMethod.addTypeDispatchedMethod
multi-method
(if (= :default dispatch-value)
(list 'quote '__multi-method-default)
(list 'quote dispatch-value))
(cons 'fn* (cons name arity-definitions)))
(list 'throw (list 'ex-info "Unimplemented")))))
(defn methods [multi-method]
(if (dart/PLMultiMethod.isTypeDispatched multi-method)
(dart/PLMultiMethod.allMethodsByType multi-method)
(dart/PLMultiMethod.allMethodsByDispatch multi-method)))
(defn remove-method [multi-method dispatch-value]
(if (dart/PLMultiMethod.isTypeDispatched multi-method)
(dart/PLMultiMethod.removeTypeDispatchedMethod multi-method dispatch-value)
(throw (ex-info "Unimplemented"))))
(defn into
{:doc "Returns a new coll consisting of to-coll with all of the items of from-coll conjoined. A transducer may be supplied."}
([] [])
([to] to)
([to from]
(reduce conj to from)))
(defmacro when
{:doc "Executes the body when the test is truthy. Returns nil otherwise."}
[test & body]
(list 'if test (cons 'do body) nil))
(defn set?
{:doc "Returns true if the argument is a set."}
[x]
(= 'set (type x)))
(defn get
{:doc "Return the item found at the key or index provided. Different from nth in that it returns `nil` and never throws, even if index is out of range."}
([coll key] (get coll key nil))
([coll key default]
(if (or (map? coll)
(set? coll))
(get* coll key default)
(nth coll key default))))
(defn int?
{:doc "Returns true if the argument is an int."}
[x]
(= 'int (type x)))
(defn double?
{:doc "Returns true if the argument is a double."}
[x]
(= 'double (type x)))
(defn symbol?
{:doc "Returns true if the argument is a symbol."}
[x]
(= 'symbol (type x)))
(defn keyword?
{:doc "Returns true if the argument is a term (keyword equivalent). For compatibility with Clojure expectations."}
[x]
(= 'term (type x)))
(defn term?
{:doc "Returns true if the argument is a term (keyword equivalent)."}
[x]
(= 'term (type x)))
(defn ident?
{:doc "Returns true if x is a symbol or term (keyword)"}
[x] (or (term? x) (symbol? x)))
(defn list?
{:doc "Returns true if the argument is a list."}
[x]
(= 'list (type x)))
(defn vector?
{:doc "Returns true if the argument is a vector."}
[x]
(= 'vector (type x)))
(defn seq?
{:doc "Returns true if the argument is a seq-able collection."}
[x]
(or (list? x)
(vector? x)))
(defn coll?
{:doc "Returns true if the argument is an immutable collection."}
[x]
(or (list? x)
(vector? x)
(map? x)
(set? x)))
(defn reg-exp?
{:doc "Returns true if the argument is a regular expression."}
[x]
(= 'RegExp (type x)))
(def regex reg-exp?)
(def regexp reg-exp?)
(defn named?
{:doc "Returns true if the argument is a string, symbol, or term."}
[x]
(let [t (type x)]
(or (= 'String t)
(= 'symbol t)
(= 'term t))))
(defmacro cond
{:doc "Takes a set of test/expr pairs. It evaluates each test one at a time. If a test returns logical true, cond evaluates and returns the value of the corresponding expr and doesn't evaluate any of the other tests or exprs. (cond) returns nil."}
[& clauses]
(when (seq clauses)
(list 'if (first clauses)
(if (next clauses)
(second clauses)
(throw
(ex-info "cond requires an even number of forms"
{:num-forms (count clauses)
:clauses (quote clauses)})))
(cons 'cond (next (next clauses))))))
(defn empty
{:doc "Returns an empty collection of the same category as coll, or nil"}
[coll]
(cond
(list? coll) '()
(vector? coll) []
(map? coll) {}
(set? coll) #{}
:else nil))
(defmacro ->
{:doc "Threads the expr through the forms. Inserts x as the second item in the first form, making a list of it if it is not a list already. If there are more forms, inserts the first form as the second item in second form, etc."}
[x & forms]
(loop [x x
forms forms]
(if forms
(let [form (first forms)
threaded (if (seq? form)
(cons (first form) (cons x (next form)))
(list form x))]
(recur threaded (next forms)))
x)))
(defn reverse
{:doc "Returns items of coll in reverse order. Vectors returned as vectors."}
[coll]
(if (vector? coll)
(into [] (reduce conj () coll))
(reduce conj () coll)))
(defn concat
{:doc "Returns a lazy seq representing the concatenation of the elements in the supplied colls."}
([] nil)
([x] x)
([x y]
(let [s (seq x)]
(if s
(reduce
(fn concat-reduce-2 [acc s-item]
(cons s-item acc))
y
(reverse s))
y)))
([x y & zs]
(let [all (cons x (cons y zs))]
(reduce
(fn concat-reduce-3 [acc coll]
(let [s (seq coll)]
(if s
(reduce
(fn concat-reduce-3-inner [acc-inner s-item]
(cons s-item acc-inner))
acc
(reverse s))
acc)))
()
(reverse all)))))
(defmacro and
{:doc "Evaluates exprs one at a time, from left to right. If a form returns logical false (nil or false), and returns that value and doesn't evaluate any of the other expressions, otherwise it returns the value of the last expr. (and) returns true."}
([] true)
([x] x)
([x & next]
(let [and_ (gensym "and")]
(list 'let* [and_ x]
(list 'if and_ (concat (list 'and) next) and_)))))
(defn partial
{:doc "Partially apply the given function with the given arguments."}
([f] f)
([f arg1]
(fn
([] (f arg1))
([x] (f arg1 x))
([x y] (f arg1 x y))
([x y z] (f arg1 x y z))
([x y z & args] (apply f arg1 x y z args))))
([f arg1 arg2]
(fn
([] (f arg1 arg2))
([x] (f arg1 arg2 x))
([x y] (f arg1 arg2 x y))
([x y z] (f arg1 arg2 x y z))
([x y z & args] (apply f arg1 arg2 x y z args))))
([f arg1 arg2 arg3]
(fn
([] (f arg1 arg2 arg3))
([x] (f arg1 arg2 arg3 x))
([x y] (f arg1 arg2 arg3 x y))
([x y z] (f arg1 arg2 arg3 x y z))
([x y z & args] (apply f arg1 arg2 arg3 x y z args))))
([f arg1 arg2 arg3 & more]
(fn [& args] (apply f arg1 arg2 arg3 (concat more args)))))
(defn complement
{:doc "Takes a fn f and returns a fn that takes the same arguments as f, has the same effects, if any, and returns the opposite truth value."}
[f]
(fn
([] (not (f)))
([x] (not (f x)))
([x y] (not (f x y)))
([x y & zs] (not (apply f x y zs)))))
(defn identity
{:doc "Return the argument provided."}
[x] x)
(defn id [x] x) ;; alias for ease of typing
(defn comp
{:doc "Returns the composition of the given functions."}
([] identity)
([f] f)
([f g]
(fn
([] (f (g)))
([x] (f (g x)))
([x y] (f (g x y)))
([x y z] (f (g x y z)))
([x y z & args] (f (apply g x y z args)))))
([f g & fs]
(reduce comp (cons f (cons g fs)))))
(defmacro when-let
{:doc "When test is true, evaluates body with binding-form bound to the value of test"}
[bindings & body]
;; (assert-args
;; (vector? bindings) "a vector for its binding"
;; (= 2 (count bindings)) "exactly 2 forms in binding vector")
(let [form (nth bindings 0)
tst (nth bindings 1)
temp_ (gensym "temp")]
(list 'let
[temp_ tst]
(list 'when temp_
(list 'let [form temp_]
(cons 'do body))))))
(defmacro if-let
{:doc "bindings => binding-form test
If test is true, evaluates then with binding-form bound to the value of
test, if not, yields else"}
([bindings then]
(list 'if-let bindings then nil))
([bindings then else & oldform]
;; (assert-args
;; (vector? bindings) "a vector for its binding"
;; (nil? oldform) "1 or 2 forms after binding vector"
;; (= 2 (count bindings)) "exactly 2 forms in binding vector")
(let [form (bindings 0)
tst (bindings 1)
temp (gensym)]
(list 'let [temp tst]
(list 'if temp
(list 'let [form temp]
then)
else)))))
(defmacro time
{:doc "Evaluates expr and prints the time it took. Returns the value of expr."}
[expr]
(let [sw-g (gensym 'stopwatch)
ret-g (gensym 'return)]
(list 'let
[sw-g (list 'dart/Stopwatch.)
'_ (list 'dart/Stopwatch.start sw-g)
ret-g expr
'_ (list 'dart/Stopwatch.stop sw-g)]
(list 'println (list 'str "Elapsed time: " (list '/ (list 'dart/Stopwatch.elapsedMicroseconds sw-g) 1000) " milliseconds"))
ret-g)))
(defn nthrest
{:doc "Returns the nth rest of coll, coll when n is 0."}
[coll n]
(cond
(neg? n) coll
(> n (count coll)) (empty coll)
:else
(reduce
(fn [acc _]
(next acc))
coll
(range n))))
(defn drop
[n coll]
(nthrest coll n))
(defn repeat
{:doc "Returns a sequence of xs of length n."}
([x] (repeat 1 x))
([n x]
(let [c (state 0)
r (state [])]
(while (< @c n)
(! c inc)
(! r conj x))
@r)))
(defn iterate
{:doc "Returns a sequence of x, (f x), (f (f x)) etc."}
([f x] (iterate 1 x))
([n f x]
(let [c (state 1)
prev (state x)
ret (state [x])]
(while (< @c n)
(! c inc)
(let [next-value (f @prev)]
(! prev next-value)
(! ret conj next-value))))))
(declare update)
(defn take
{:doc "Returns a sequence of the first n items in coll, or all items if there are fewer than n. Returns a stateful transducer when no collection is provided."}
[n coll]
(cond
(<= n 0) ()
(> n (count coll)) coll
:else
;; CONSIDER: reduce + reduced
(let [c (state 0)
coll (state coll)
ret (state [])]
(while (< @c n)
(! c inc)
(! ret conj (first @coll))
(! coll (next @coll)))
@ret)))
(defn take-while
{:doc "Returns a sequence of successive items from coll while (pred item) returns logical true."}
[pred coll]
(reduce
(fn take-while-reduce [acc item]
(if (pred item)
(conj acc item)
(reduced acc)))
[]
coll))
(defn butlast
{:doc "Return the collection with all but the last item."}
[coll]
(let [length (count coll)
penultimate (dec length)
ret (reduce
(fn butlast-reduce [acc x]
(if (= (.idx acc) penultimate)
(reduced (.ret acc))
(-> acc
(update .idx inc)
(update .ret conj x))))
{:ret (empty coll)
:idx 0}
coll)]
(if (list? coll)
(reverse ret)
ret)))
;; NB. The partition reduction helpers avoid stack consumption via recursive calls with cons.
(defn partition-reduction-list
{:doc "Returns map of parts, whole, and step-n for partitioning the given list with n-sized buckets at step intervals."
:private true}
[n step coll]
(reduce
(fn partition-reduction-list-reduce [acc item]
;; NB. Destructuring not available yet, see below
(let [parts (:parts acc) whole (:whole acc) step-n (:step-n acc)
;; Handle completed partition
v (if (= n (count (first parts)))
[(next parts) (concat whole (list (reverse (first parts))))]
[parts whole])
parts (first v) whole (second v)
;; Add new partition if step
v (if (> step-n 0)
[(dec step-n) parts]
;; Start next step, include this step as 1st
[(dec step) (concat parts '(()))])
step-n (first v) parts (second v)
;; Fill in partitions with current item
parts (map #(cons item %) parts)]
{:parts parts
:step-n step-n
:whole whole}))
{:parts '(())
:step-n step
:whole ()}
coll))
(defn partition-reduction-vector
{:doc "Returns map of parts, whole, and step-n for partitioning the given vector with n-sized buckets at step intervals."
:private true}
[n step coll]
(reduce
(fn partition-reduction-vector-reduce [acc item]
;; NB. Destructuring not available yet, see below
(let [parts (:parts acc) whole (:whole acc) step-n (:step-n acc)
;; Handle completed partition
v (if (= n (count (first parts)))
[(next parts) (conj whole (first parts))]
[parts whole])
parts (first v) whole (second v)
;; Add new partition if step
v (if (> step-n 0)
[(dec step-n) parts]
;; Start next step, include this step as 1st
[(dec step) (conj parts [])])
step-n (first v) parts (second v)
;; Fill in partitions with current item
parts (map #(conj % item) parts)]
{:parts parts
:step-n step-n
:whole whole}))
{:parts [[]]
:step-n step
:whole []}
coll))
(declare empty?)
(defn partition
{:doc "Returns a sequence of lists of n items each, at offsets step apart. If step is not supplied, defaults to n, i.e. the partitions do not overlap. If a pad collection is supplied, use its elements as necessary to complete last partition upto n items. In case there are not enough padding elements, return a partition with less than n items."}
([n coll]
(partition n n coll))
([n step coll]
(assert (or (vector? coll) (list? coll)))
(let [lst? (list? coll)
m (if lst?
(partition-reduction-list n step coll)
(partition-reduction-vector n step coll))
parts (:parts m) whole (:whole m)
remnant (first parts)]
(if (= n (count remnant))
(if lst?
(concat whole (list (reverse remnant)))
(conj whole remnant))
whole)))
([n step pad coll]
(assert (or (vector? coll) (list? coll)))
(let [lst? (list? coll)
m (if lst?
(partition-reduction-list n step coll)
(partition-reduction-vector n step coll))
parts (:parts m) whole (:whole m)
remnant (first parts)]
(if (empty? remnant)
whole
(if lst?
(concat whole
(list (concat (reverse remnant)
(take (- n (count remnant)) pad))))
(conj whole
(apply conj
remnant
(take (- n (count remnant)) pad))))))))
(defn partition-by*
[f coll]
(when-let [s (seq coll)]
(let [fst (first s)
fv (f fst)
run (cons fst (take-while (fn partition-by-take-while [x] (= fv (f x))) (next s)))]
(cons run (partition-by* f (drop (count run) s))))))
(defn partition-by
[f coll]
(let [s (seq coll)
m (reduce
(fn [acc item]
(let [prev (:prev acc) part (:part acc) whole (:whole acc)
check (f item)]
(if (= check prev)
{:prev prev
:part (conj part item)
:whole whole}
{:prev check
:part [item]
:whole (conj whole part)})))
{:prev (f (first s))
:part [(first s)]
:whole []}
(next s))
whole (:whole m) part (:part m)]
(conj whole part)))
;; # Predicates
(defn true?
{:doc "Returns true if identical to the boolean value true. Prefer truthy/falsey semantics where possible."}
[x]
(= x true))
(defn false?
{:doc "Returns false if identical to the boolean value false. Prefer truthy/falsey semantics where possible."}
[x]
(= x false))
(defn boolean?
{:doc "Returns true if the argument is either the boolean value true or false."}
[x]
(or (true? x) (false? x)))
(defn nil?
{:doc "Returns true if the argument is identical to nil. Prefer truthy/falsey semantics where possible."}
[x]
(= x nil))
(defn some?
{:doc "Returns true if the argument is not nil. Returns false otherwise."}
[x]
(not (nil? x)))
(defn some
{:doc "Returns the first logical true value of (pred x) for any x in coll, else nil. One common idiom is to use a set as pred, for example this will return :fred if :fred is in the sequence, otherwise nil: (some #{:fred} coll)"}
[pred coll]
(when-let [s (seq coll)]
(or (pred (first s)) (some pred (next s)))))
(defn any?
{:doc "Returns true regardless of argument."}
[_x]
true)
(defn fnil
{:doc "Takes a function f, and returns a function that calls f, replacing a nil first argument to f with the supplied value x. Higher arity versions can replace arguments in the second and third positions (y, z). Note that the function f can take any number of arguments, not just the one(s) being nil-patched."}
([f x]
(fn
([a] (f (if (nil? a) x a)))
([a b] (f (if (nil? a) x a) b))
([a b c] (f (if (nil? a) x a) b c))
([a b c & ds] (apply f (if (nil? a) x a) b c ds))))
([f x y]
(fn
([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c))
([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds))))
([f x y z]
(fn
([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c)))
([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds)))))
;; # Names & "Namespaces"
(defn full-name
[x]
(name* x))
(defn name
[x]
(cond
(string? x) x
(or (term? x)
(symbol? x))
(let [s (full-name x)]
(if (dart/String.contains s "/")
(second (dart/String.split (name* x) "/"))
s))))
(defn namespace
{:doc "PiLisp does not currently support namespaces. This returns anything before the first / in a symbol or term's name, since that is useful as a visual cue and makes destructuring useful for more concise bindings."}
[x]
(if (or (term? x)
(symbol? x))
(let [s (full-name x)]
(when (dart/String.contains s "/")
(first (dart/String.split s "/"))))
(throw (ex-info (str "Cannot get the namespace of a " (type x) " value." {})))))
(defn ->>*
{:doc "Helper for ->>"
:private true}
[x forms]
(if forms
(let [form (first forms)
threaded (if (list? form)
(let [fst (first form)
nxt (next form)]
(concat (cons fst nxt) [x]))
(list form x))]
(->>* threaded (next forms)))
x))
(defmacro ->>
{:doc "Threads the expr through the forms. Inserts x as the last item in the first form, making a list of it if it is not a list already. If there are more forms, inserts the first form as the last item in second form, etc."}
[x & forms]
(->>* x (seq forms)))
(defn set
{:doc "Construct a set from the coll. See also: lst, map, vec"}
[coll]
(if (set? coll)
coll
(into #{} coll)))
(defn vec
{:doc "Construct a vector from the coll. See also: lst, map, set"}
[coll]
(if (vector? coll)
coll
(apply vector (seq coll))))
(defn to-map
{:doc "Make a map out of coll. Also available via map function for consistency in three-letter collection constructor names (lst, map, set, vec)"}
[coll]
(if (map? coll)
coll
(into {} (seq coll))))
(defn every?
"Returns true if (pred x) is logical true for every x in coll, else false."
[pred coll]
(cond
(nil? (seq coll)) true
(pred (first coll)) (every? pred (next coll))
:else false))
(defn empty? [coll] (not (seq coll)))
(defn contains-key?
[coll x]
(cond
(nil? coll)
false
(map? coll)
(dart/Map.containsKey (to-dart-map coll) x)
(or (vector? coll)
(list? coll))
(> (count coll) x 0)
(set? coll)
(dart/Iterable.contains coll x)
:else
(throw (ex-info (str "Don't know how to check whether a value of type " (type coll) " contains keys.") {}))))
(defn contains-value?
[coll x]
(cond
(nil? coll)
false
(map? coll)
(dart/Map.containsValue (to-dart-map coll) x)
(or (vector? coll)
(list? coll))
(dart/Iterable.contains (to-dart-list coll) x)
(set? coll)
(dart/Iterable.contains coll x)
(string? coll)
(dart/String.contains coll x)
:else
(throw (ex-info (str "Don't know how to check whether a value of type " (type coll) " contains values.") {}))))
(def contains? contains-key?)
(def disj dissoc)
(declare interleave)
(defn map
([f]
(if (not (fn? f))
(to-map f)
(throw (ex-info "Transducers are not yet implemented in PiLisp."))))
([f coll]
(when-let [s (seq coll)]
(let [ret (reduce
(fn map-reduce-1 [acc item]
(conj acc (f item)))
(empty s)
s)]
;; NB. Clojure's map returns a lazy seq using cons, and so order
;; is not an issue. This and further checks on list? ensure
;; the order is correct given conj with lists appends to
;; the head, rather than the tail.
(if (list? ret)
(reverse ret)
ret))))
([f c1 c2]
(let [s1 (seq c1) s2 (seq c2)]
(when (and s1 s2)
(let [ret (reduce
(fn map-reduce-2 [acc item]
(let [item-a (first item) item-b (second item)]
(conj acc (f item-a item-b))))
(empty s1)
(partition 2 (interleave s1 s2)))]
(if (list? ret)
(reverse ret)
ret)))))
([f c1 c2 c3]
(let [s1 (seq c1) s2 (seq c2) s3 (seq c3)]
(when (and s1 s2 s3)
(let [ret (reduce
(fn map-reduce-3 [acc item]
(let [item-a (first item) item-b (second item) item-c (third item)]
(conj acc (f item-a item-b item-c))))
(empty s1)
(partition 3 (interleave s1 s2 s3)))]
(if (list? ret)
(reverse ret)
ret)))))
([f c1 c2 c3 c4]
(let [s1 (seq c1) s2 (seq c2) s3 (seq c3) s4 (seq c4)]
(when (and s1 s2 s3 s4)
(let [ret (reduce
(fn map-reduce-3 [acc item]
(let [item-a (first item) item-b (second item) item-c (third item) item-d (fourth item)]
(conj acc (f item-a item-b item-c item-d))))
(empty s1)
(partition 4 (interleave s1 s2 s3 s4)))]
(if (list? ret)
(reverse ret)
ret)))))
;; NB. Submit a pull request if you want more.
)
(def mapv (comp vec map))
(defn mapcat
{:doc "Returns the result of applying concat to the result of applying map to f and colls. Thus function f should return a collection."}
[f & colls]
(apply concat (apply map f colls)))
;; NB. Implemented with reduce to prevent stack consumption.
(defn filter
[pred coll]
(reduce
(fn filter-reduce [acc item]
(if (pred item)
(conj acc item)
acc))
[]
coll))
(def filterv (comp vec filter))
(defn remove
[pred coll]
(filter (complement pred) coll))
(defn assoc
{:doc "assoc[iate]. When applied to a map, returns a new map of the same (hashed/sorted) type, that contains the mapping of key(s) to val(s). When applied to a vector, returns a new vector that contains val at index. Note - index must be <= (count vector)."}
([map key val] (assoc* map key val))
([map key val & kvs]
(let [ret (assoc* map key val)]
(if kvs
(if (next kvs)
(apply assoc ret (first kvs) (second kvs) (nnext kvs))
(throw (ex-info
"assoc expects even number of arguments after map/vector, found odd number")))
ret))))
(defn assoc!
{:doc "Like assoc, but for Dart maps, mutating the original maps provided."}
([map key val] (assoc!* map key val))
([map key val & kvs]
(let [ret (assoc!* map key val)]
(if kvs
(if (next kvs)