Skip to content

Commit 6588d12

Browse files
authored
Additional core functions and macros (#293)
* Additional core functions and macros * Docstring
1 parent 5304dbc commit 6588d12

File tree

5 files changed

+131
-6
lines changed

5 files changed

+131
-6
lines changed

src/basilisp/core/__init__.lpy

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,12 @@
241241

242242
(defn nth
243243
"Returns the ith element of coll (0-indexed), if it exists.
244-
nil otherwise."
245-
[coll i]
246-
(basilisp.lang.runtime/nth coll i))
244+
nil otherwise. If i is out of bounds, throws an IndexError unless
245+
notfound is specified."
246+
([coll i]
247+
(basilisp.lang.runtime/nth coll i))
248+
([coll i notfound]
249+
(basilisp.lang.runtime/nth coll i notfound)))
247250

248251
(defn nthnext
249252
"Returns the nth next sequence of coll.
@@ -415,6 +418,39 @@
415418
{:first (first clauses)})))
416419
(cons 'basilisp.core/cond (nthrest clauses 2)))))
417420

421+
(defmacro condp
422+
"Take a predicate and an expression and a series of clauses, call
423+
(pred test expr) on the first expression for each clause. The result
424+
expression from first the set of clauses for which this expression
425+
returns a truthy value will be returned from the condp expression.
426+
427+
Clauses can take two forms:
428+
429+
- test-expr result-expr
430+
- test-expr :>> result-fn, where :>> is a keyword literal
431+
432+
For the ternary expression clause, the unary result-fn will be called
433+
with the result of the predicate."
434+
[pred expr & clauses]
435+
(when (seq clauses)
436+
(let [test-expr (first clauses)
437+
remaining (rest clauses)]
438+
(if (seq remaining)
439+
(let [result (first remaining)
440+
remaining (rest remaining)]
441+
(cond
442+
(= result :>>) `(let [res# ~(list pred test-expr expr)]
443+
(if res#
444+
(~(first remaining) res#)
445+
(condp ~pred ~expr ~@(rest remaining))))
446+
result `(if ~(list pred test-expr expr)
447+
~result
448+
(condp ~pred ~expr ~@remaining))
449+
:else (throw
450+
(ex-info "expected result expression"
451+
{:test test-expr}))))
452+
test-expr))))
453+
418454
(defn not
419455
"Return the logical negation of expr."
420456
[expr]
@@ -704,6 +740,16 @@
704740
[entry]
705741
(.-value entry))
706742

743+
(defn keys
744+
"Return a seq of the keys from a map."
745+
[m]
746+
(seq (.keys m)))
747+
748+
(defn vals
749+
"Return a seq of the values from a map."
750+
[m]
751+
(seq (.values m)))
752+
707753
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
708754
;; Higher Order Functions ;;
709755
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -842,6 +888,18 @@
842888
(cons (apply f (first coll) (map first colls))
843889
(apply map f (rest coll) (map rest colls)))))))
844890

891+
(defn map-indexed
892+
"Return a lazy sequence of (f idx elem) for elements in coll. The
893+
index starts at 0."
894+
[f coll]
895+
(map f (range) coll))
896+
897+
(defn mapcat
898+
"Return a lazy sequence of the concatenated results of mapping f over
899+
colls."
900+
[f & colls]
901+
(apply concat (apply map f colls)))
902+
845903
(defn filter
846904
"Return elements from coll where (pred elem) returns true."
847905
[pred coll]
@@ -973,6 +1031,11 @@
9731031
[o method & args]
9741032
`(apply (.- ~o ~method) ~@args))
9751033

1034+
(defmacro comment
1035+
"Ignore all the forms passed, returning nil."
1036+
[& forms]
1037+
nil)
1038+
9761039
(defmacro new
9771040
"Create a new instance of class with args.
9781041

src/basilisp/lang/runtime.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,21 +565,31 @@ def apply(f, args):
565565
return f(*final)
566566

567567

568-
def nth(coll, i):
568+
__nth_sentinel = object()
569+
570+
571+
def nth(coll, i, notfound=__nth_sentinel):
569572
"""Returns the ith element of coll (0-indexed), if it exists.
570-
None otherwise."""
573+
None otherwise. If i is out of bounds, throws an IndexError unless
574+
notfound is specified."""
571575
if coll is None:
572576
return None
573577

574578
try:
575579
return coll[i]
580+
except IndexError as ex:
581+
if notfound is not __nth_sentinel:
582+
return notfound
583+
raise ex
576584
except TypeError:
577585
pass
578586

579587
try:
580588
for j, e in enumerate(coll):
581589
if i == j:
582590
return e
591+
if notfound is not __nth_sentinel:
592+
return notfound
583593
raise IndexError(f"Index {i} out of bounds")
584594
except TypeError:
585595
pass

tests/basilisp/core_macros_test.lpy

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
(ns basilisp.core-macros-test
22
(:require
3-
[basilisp.test :refer [deftest is]]))
3+
[basilisp.test :refer [deftest is testing]]))
4+
5+
(deftest condp-test
6+
(testing "condp result value"
7+
(is (= :a (condp = "a" :a)))
8+
(is (= :a (condp = "a"
9+
"b" :b
10+
:a)))
11+
(is (= :a (condp = "a"
12+
"b" :b
13+
"c" :c
14+
:a))))
15+
16+
(testing "condp result function"
17+
(is (= :a (condp some [1 8 10 12]
18+
#{2 4 6} :>> inc
19+
:a)))
20+
(is (= 0 (condp some [1 8 10 12]
21+
#{2 4 6} :>> inc
22+
#{1 3 5} :>> dec
23+
:a)))))
24+
25+
(deftest comment-test
26+
(is (= nil (comment 1)))
27+
(is (= nil (comment [1 2 3]))))
428

529
(deftest ->-test
630
(is (= :a (-> :a)))

tests/basilisp/core_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,18 @@ def test_get():
493493
assert None is core.get(vec.v(1, 2, 3), -4)
494494

495495

496+
def test_keys():
497+
assert None is core.keys(lmap.map({}))
498+
assert llist.l("a") == core.keys(lmap.map({"a": 1}))
499+
assert lset.s("a", "b") == lset.set(core.keys(lmap.map({"a": 1, "b": 2})))
500+
501+
502+
def test_vals():
503+
assert None is core.vals(lmap.map({}))
504+
assert llist.l(1) == core.vals(lmap.map({"a": 1}))
505+
assert lset.s(1, 2) == lset.set(core.vals(lmap.map({"a": 1, "b": 2})))
506+
507+
496508
def test_range():
497509
assert llist.l(1) == core.range_(1, 1)
498510
assert llist.l(1, 2, 3, 4, 5) == core.range_(1, 5)
@@ -622,6 +634,16 @@ def test_map():
622634
assert llist.l(5, 7, 9) == core.map_(core.__PLUS__, vec.v(1, 2, 3), core.range_(4))
623635

624636

637+
def test_map_indexed():
638+
assert llist.l(vec.v(0, 1), vec.v(1, 2), vec.v(2, 3)) == core.map_indexed(core.vector, vec.v(1, 2, 3))
639+
640+
641+
def test_mapcat():
642+
assert llist.List.empty() == core.mapcat(lambda x: vec.v(x, x + 1), vec.Vector.empty())
643+
assert llist.l(1, 2, 2, 3, 3, 4) == core.mapcat(lambda x: vec.v(x, x + 1), vec.v(1, 2, 3))
644+
assert llist.l(1, 4, 2, 5, 3, 6) == core.mapcat(core.vector, vec.v(1, 2, 3), vec.v(4, 5, 6))
645+
646+
625647
def test_filter():
626648
assert llist.List.empty() == core.filter_(core.identity, vec.Vector.empty())
627649
assert llist.l(1, 3) == core.filter_(core.odd__Q__, vec.v(1, 2, 3, 4))

tests/basilisp/runtime_test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ def test_nth():
148148
assert "l" == runtime.nth(vec.v("h", "e", "l", "l", "o"), 2)
149149
assert "l" == runtime.nth(lseq.sequence(["h", "e", "l", "l", "o"]), 2)
150150

151+
assert "Z" == runtime.nth(llist.l("h", "e", "l", "l", "o"), 7, "Z")
152+
assert "Z" == runtime.nth(lseq.sequence(["h", "e", "l", "l", "o"]), 7, "Z")
153+
151154
with pytest.raises(IndexError):
152155
runtime.nth(llist.l("h", "e", "l", "l", "o"), 7)
153156

@@ -157,6 +160,9 @@ def test_nth():
157160
with pytest.raises(TypeError):
158161
runtime.nth(3, 1)
159162

163+
with pytest.raises(TypeError):
164+
runtime.nth(3, 1, "Z")
165+
160166

161167
def test_assoc():
162168
assert lmap.Map.empty() == runtime.assoc(None)

0 commit comments

Comments
 (0)