• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

basilisp-lang / basilisp / 7379247703

01 Jan 2024 06:37PM UTC coverage: 99.008%. Remained the same
7379247703

push

github

web-flow
callable var (#768)

Hi,

could you please review compatibility patch with Clojure to make vars
callable. It addresses #767.

I am not sure this if the `class Var` is the right place to make vars
callable or the analyzer, which should expand them to a callable var
value last node.

Nevertheless, I will kindly request your help with the type hinting,
currently it will fail the linter with the following error

```
src/basilisp/lang/runtime.py:279:15: E1102: self.value is not callable (not-callable)
```

but not sure how to fix it, I've tried the class Var `value` property
method to return a maybe Callable but it didn't work.

Thanks

Co-authored-by: ikappaki <ikappaki@users.noreply.github.com>

1691 of 1693 branches covered (0.0%)

Branch coverage included in aggregate %.

7892 of 7986 relevant lines covered (98.82%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.01
/src/basilisp/lang/runtime.py
1
# pylint: disable=too-many-lines
2

3
import contextlib
1✔
4
import decimal
1✔
5
import functools
1✔
6
import importlib.metadata
1✔
7
import inspect
1✔
8
import itertools
1✔
9
import logging
1✔
10
import math
1✔
11
import numbers
1✔
12
import platform
1✔
13
import re
1✔
14
import sys
1✔
15
import threading
1✔
16
import types
1✔
17
from collections.abc import Sequence
1✔
18
from fractions import Fraction
1✔
19
from typing import (
1✔
20
    AbstractSet,
21
    Any,
22
    Callable,
23
    Dict,
24
    FrozenSet,
25
    Iterable,
26
    Iterator,
27
    List,
28
    Mapping,
29
    Optional,
30
    Set,
31
    Tuple,
32
    Type,
33
    TypeVar,
34
    Union,
35
    cast,
36
)
37

38
from readerwriterlock.rwlock import RWLockFair
1✔
39

40
from basilisp.lang import keyword as kw
1✔
41
from basilisp.lang import list as llist
1✔
42
from basilisp.lang import map as lmap
1✔
43
from basilisp.lang import obj as lobj
1✔
44
from basilisp.lang import seq as lseq
1✔
45
from basilisp.lang import set as lset
1✔
46
from basilisp.lang import symbol as sym
1✔
47
from basilisp.lang import vector as vec
1✔
48
from basilisp.lang.atom import Atom
1✔
49
from basilisp.lang.interfaces import (
1✔
50
    IAssociative,
51
    IBlockingDeref,
52
    IDeref,
53
    ILookup,
54
    IPersistentCollection,
55
    IPersistentList,
56
    IPersistentMap,
57
    IPersistentSet,
58
    IPersistentStack,
59
    IPersistentVector,
60
    ISeq,
61
    ITransientSet,
62
)
63
from basilisp.lang.reference import RefBase, ReferenceBase
1✔
64
from basilisp.lang.typing import CompilerOpts, LispNumber
1✔
65
from basilisp.lang.util import OBJECT_DUNDER_METHODS, demunge, is_abstract, munge
1✔
66
from basilisp.util import Maybe
1✔
67

68
logger = logging.getLogger(__name__)
1✔
69

70
# Public constants
71
CORE_NS = "basilisp.core"
1✔
72
CORE_NS_SYM = sym.symbol(CORE_NS)
1✔
73
NS_VAR_NAME = "*ns*"
1✔
74
NS_VAR_SYM = sym.symbol(NS_VAR_NAME, ns=CORE_NS)
1✔
75
NS_VAR_NS = CORE_NS
1✔
76
REPL_DEFAULT_NS = "basilisp.user"
1✔
77
SUPPORTED_PYTHON_VERSIONS = frozenset({(3, 8), (3, 9), (3, 10), (3, 11), (3, 12)})
1✔
78
BASILISP_VERSION_STRING = importlib.metadata.version("basilisp")
1✔
79
BASILISP_VERSION = vec.vector(
1✔
80
    (int(s) if s.isdigit() else s) for s in BASILISP_VERSION_STRING.split(".")
81
)
82

83
# Public basilisp.core symbol names
84
COMPILER_OPTIONS_VAR_NAME = "*compiler-options*"
1✔
85
DEFAULT_READER_FEATURES_VAR_NAME = "*default-reader-features*"
1✔
86
GENERATED_PYTHON_VAR_NAME = "*generated-python*"
1✔
87
PRINT_GENERATED_PY_VAR_NAME = "*print-generated-python*"
1✔
88
PRINT_DUP_VAR_NAME = "*print-dup*"
1✔
89
PRINT_LENGTH_VAR_NAME = "*print-length*"
1✔
90
PRINT_LEVEL_VAR_NAME = "*print-level*"
1✔
91
PRINT_META_VAR_NAME = "*print-meta*"
1✔
92
PRINT_READABLY_VAR_NAME = "*print-readably*"
1✔
93
PYTHON_VERSION_VAR_NAME = "*python-version*"
1✔
94
BASILISP_VERSION_VAR_NAME = "*basilisp-version*"
1✔
95

96
# Common meta keys
97
_DOC_META_KEY = kw.keyword("doc")
1✔
98
_DYNAMIC_META_KEY = kw.keyword("dynamic")
1✔
99
_PRIVATE_META_KEY = kw.keyword("private")
1✔
100
_REDEF_META_KEY = kw.keyword("redef")
1✔
101

102
# Special form values, used for resolving Vars
103
_AWAIT = sym.symbol("await")
1✔
104
_CATCH = sym.symbol("catch")
1✔
105
_DEF = sym.symbol("def")
1✔
106
_DEFTYPE = sym.symbol("deftype*")
1✔
107
_DO = sym.symbol("do")
1✔
108
_FINALLY = sym.symbol("finally")
1✔
109
_FN = sym.symbol("fn*")
1✔
110
_IF = sym.symbol("if")
1✔
111
_IMPORT = sym.symbol("import*")
1✔
112
_INTEROP_CALL = sym.symbol(".")
1✔
113
_INTEROP_PROP = sym.symbol(".-")
1✔
114
_LET = sym.symbol("let*")
1✔
115
_LETFN = sym.symbol("letfn*")
1✔
116
_LOOP = sym.symbol("loop*")
1✔
117
_QUOTE = sym.symbol("quote")
1✔
118
_REIFY = sym.symbol("reify*")
1✔
119
_RECUR = sym.symbol("recur")
1✔
120
_REQUIRE = sym.symbol("require*")
1✔
121
_SET_BANG = sym.symbol("set!")
1✔
122
_THROW = sym.symbol("throw")
1✔
123
_TRY = sym.symbol("try")
1✔
124
_VAR = sym.symbol("var")
1✔
125
_YIELD = sym.symbol("yield")
1✔
126
_SPECIAL_FORMS = lset.s(
1✔
127
    _AWAIT,
128
    _CATCH,
129
    _DEF,
130
    _DEFTYPE,
131
    _DO,
132
    _FINALLY,
133
    _FN,
134
    _IF,
135
    _IMPORT,
136
    _INTEROP_CALL,
137
    _INTEROP_PROP,
138
    _LET,
139
    _LETFN,
140
    _LOOP,
141
    _QUOTE,
142
    _RECUR,
143
    _REIFY,
144
    _REQUIRE,
145
    _SET_BANG,
146
    _THROW,
147
    _TRY,
148
    _VAR,
149
    _YIELD,
150
)
151

152

153
# Reader Conditional default features
154
def _supported_python_versions_features() -> Iterable[kw.Keyword]:
1✔
155
    """Yield successive reader features corresponding to the various Python
156
    `major`.`minor` versions the current Python VM corresponds to amongst the
157
    set of supported Python versions.
158

159
    For example, for Python 3.6, we would emit:
160
     - :lpy36  - to exactly match Basilisp running on Python 3.6
161
     - :lpy36+ - to match Basilisp running on Python 3.6 and later versions
162
     - :lpy36- - to match Basilisp running on Python 3.6 and earlier versions
163
     - :lpy37- - to match Basilisp running on Python 3.7 and earlier versions
164
     - :lpy38- - to match Basilisp running on Python 3.8 and earlier versions"""
165
    feature_kw = lambda major, minor, suffix="": kw.keyword(
1✔
166
        f"lpy{major}{minor}{suffix}"
167
    )
168

169
    yield feature_kw(sys.version_info.major, sys.version_info.minor)
1✔
170

171
    current = (sys.version_info.major, sys.version_info.minor)
1✔
172
    for version in SUPPORTED_PYTHON_VERSIONS:
1✔
173
        if current <= version:
1✔
174
            yield feature_kw(version[0], version[1], suffix="-")
1✔
175
        if current >= version:
1✔
176
            yield feature_kw(version[0], version[1], suffix="+")
1✔
177

178

179
READER_COND_BASILISP_FEATURE_KW = kw.keyword("lpy")
1✔
180
READER_COND_DEFAULT_FEATURE_KW = kw.keyword("default")
1✔
181
READER_COND_PLATFORM = kw.keyword(platform.system().lower())
1✔
182
READER_COND_DEFAULT_FEATURE_SET = lset.s(
1✔
183
    READER_COND_BASILISP_FEATURE_KW,
184
    READER_COND_DEFAULT_FEATURE_KW,
185
    READER_COND_PLATFORM,
186
    *_supported_python_versions_features(),
187
)
188

189
CompletionMatcher = Callable[[Tuple[sym.Symbol, Any]], bool]
1✔
190
CompletionTrimmer = Callable[[Tuple[sym.Symbol, Any]], str]
1✔
191

192

193
class BasilispModule(types.ModuleType):
1✔
194
    __basilisp_namespace__: "Namespace"
1✔
195
    __basilisp_bootstrapped__: bool = False
1✔
196

197

198
# pylint: disable=attribute-defined-outside-init
199
def _new_module(name: str, doc=None) -> BasilispModule:
1✔
200
    """Create a new empty Basilisp Python module.
201
    Modules are created for each Namespace when it is created."""
202
    mod = BasilispModule(name, doc=doc)
1✔
203
    mod.__loader__ = None
1✔
204
    mod.__package__ = None
1✔
205
    mod.__spec__ = None
1✔
206
    mod.__basilisp_bootstrapped__ = False
1✔
207
    return mod
1✔
208

209

210
class RuntimeException(Exception):
1✔
211
    pass
1✔
212

213

214
class _VarBindings(threading.local):
1✔
215
    def __init__(self):
1✔
216
        self.bindings: List = []
1✔
217

218

219
class Unbound:
1✔
220
    __slots__ = ("var",)
1✔
221

222
    def __init__(self, v: "Var"):
1✔
223
        self.var = v
1✔
224

225
    def __repr__(self):  # pragma: no cover
226
        return f"Unbound(var={self.var})"
227

228
    def __eq__(self, other):
1✔
229
        return self is other or (isinstance(other, Unbound) and self.var == other.var)
1✔
230

231

232
class Var(RefBase):
1✔
233
    __slots__ = (
1✔
234
        "_name",
235
        "_ns",
236
        "_root",
237
        "_dynamic",
238
        "_is_bound",
239
        "_tl",
240
        "_meta",
241
        "_lock",
242
        "_watches",
243
        "_validator",
244
    )
245

246
    def __init__(
1✔
247
        self,
248
        ns: "Namespace",
249
        name: sym.Symbol,
250
        dynamic: bool = False,
251
        meta: Optional[IPersistentMap] = None,
252
    ) -> None:
253
        self._ns = ns
1✔
254
        self._name = name
1✔
255
        self._root = Unbound(self)
1✔
256
        self._dynamic = dynamic
1✔
257
        self._is_bound = False
1✔
258
        self._tl = None
1✔
259
        self._meta = meta
1✔
260
        self._lock = RWLockFair()
1✔
261
        self._watches = lmap.PersistentMap.empty()
1✔
262
        self._validator = None
1✔
263

264
        if dynamic:
1✔
265
            self._tl = _VarBindings()
1✔
266

267
            # If this var was created with the dynamic keyword argument, then the
268
            # Var metadata should also specify that the Var is dynamic.
269
            if isinstance(self._meta, lmap.PersistentMap):
1✔
270
                if not self._meta.val_at(_DYNAMIC_META_KEY):
1✔
271
                    self._meta = self._meta.assoc(_DYNAMIC_META_KEY, True)
1✔
272
            else:
273
                self._meta = lmap.map({_DYNAMIC_META_KEY: True})
1✔
274

275
    def __repr__(self):
276
        return f"#'{self.ns.name}/{self.name}"
277

278
    def __call__(self, *args, **kwargs):
1✔
279
        return self.value(*args, *kwargs)  # pylint: disable=not-callable
1✔
280

281
    @property
1✔
282
    def ns(self) -> "Namespace":
1✔
283
        return self._ns
1✔
284

285
    @property
1✔
286
    def name(self) -> sym.Symbol:
1✔
287
        return self._name
1✔
288

289
    @property
1✔
290
    def dynamic(self) -> bool:
1✔
291
        return self._dynamic
1✔
292

293
    def set_dynamic(self, dynamic: bool) -> None:
1✔
294
        with self._lock.gen_wlock():
1✔
295
            if dynamic == self._dynamic:
1✔
296
                return
1✔
297

298
            self._dynamic = dynamic
1✔
299
            self._tl = _VarBindings() if dynamic else None
1✔
300

301
    @property
1✔
302
    def is_private(self) -> Optional[bool]:
1✔
303
        if self._meta is not None:
1✔
304
            return self._meta.val_at(_PRIVATE_META_KEY)
1✔
305
        return False
1✔
306

307
    @property
1✔
308
    def is_bound(self) -> bool:
1✔
309
        return self._is_bound or self.is_thread_bound
1✔
310

311
    def _set_root(self, newval) -> None:
1✔
312
        # Private setter which does not include lock so it can be used
313
        # by other properties and methods which already manage the lock.
314
        oldval = self._root
1✔
315
        self._validate(newval)
1✔
316
        self._is_bound = True
1✔
317
        self._root = newval
1✔
318
        self._notify_watches(oldval, newval)
1✔
319

320
    @property
1✔
321
    def root(self):
1✔
322
        with self._lock.gen_rlock():
1✔
323
            return self._root
1✔
324

325
    def bind_root(self, val) -> None:
1✔
326
        """Atomically update the root binding of this Var to val."""
327
        with self._lock.gen_wlock():
1✔
328
            self._set_root(val)
1✔
329

330
    def alter_root(self, f, *args) -> None:
1✔
331
        """Atomically alter the root binding of this Var to the result of calling
332
        f with the existing root value and any additional arguments."""
333
        with self._lock.gen_wlock():
1✔
334
            self._set_root(f(self._root, *args))
1✔
335

336
    def push_bindings(self, val):
1✔
337
        if not self._dynamic or self._tl is None:
1✔
338
            raise RuntimeException("Can only push bindings to dynamic Vars")
1✔
339
        self._validate(val)
1✔
340
        self._tl.bindings.append(val)
1✔
341

342
    def pop_bindings(self):
1✔
343
        if not self._dynamic or self._tl is None:
1✔
344
            raise RuntimeException("Can only pop bindings from dynamic Vars")
1✔
345
        return self._tl.bindings.pop()
1✔
346

347
    def deref(self):
1✔
348
        return self.value
1✔
349

350
    @property
1✔
351
    def is_thread_bound(self):
1✔
352
        return bool(self._dynamic and self._tl and self._tl.bindings)
1✔
353

354
    @property
1✔
355
    def value(self):
1✔
356
        """Return the current value of the Var visible in the current thread.
357

358
        For non-dynamic Vars, this will just be the root. For dynamic Vars, this will
359
        be any thread-local binding if one is defined. Otherwise, the root value."""
360
        with self._lock.gen_rlock():
1✔
361
            if self._dynamic:
1✔
362
                assert self._tl is not None
1✔
363
                if len(self._tl.bindings) > 0:
1✔
364
                    return self._tl.bindings[-1]
1✔
365
            return self._root
1✔
366

367
    def set_value(self, v) -> None:
1✔
368
        """Set the current value of the Var.
369

370
        If the Var is not dynamic, this is equivalent to binding the root value. If the
371
        Var is dynamic, this will set the thread-local bindings for the Var."""
372
        with self._lock.gen_wlock():
1✔
373
            if self._dynamic:
1✔
374
                assert self._tl is not None
1✔
375
                self._validate(v)
1✔
376
                if len(self._tl.bindings) > 0:
1✔
377
                    self._tl.bindings[-1] = v
1✔
378
                else:
379
                    self.push_bindings(v)
1✔
380
                return
1✔
381
            self._set_root(v)
1✔
382

383
    __UNBOUND_SENTINEL = object()
1✔
384

385
    @classmethod
1✔
386
    def intern(  # pylint: disable=too-many-arguments
1✔
387
        cls,
388
        ns: Union["Namespace", sym.Symbol],
389
        name: sym.Symbol,
390
        val,
391
        dynamic: bool = False,
392
        meta: Optional[IPersistentMap] = None,
393
    ) -> "Var":
394
        """Intern the value bound to the symbol `name` in namespace `ns`.
395

396
        If the Var already exists, it will have its root value updated to `val`,
397
        its meta will be reset to match the provided `meta`, and the dynamic flag
398
        will be updated to match the provided `dynamic` argument."""
399
        if isinstance(ns, sym.Symbol):
1✔
400
            ns = Namespace.get_or_create(ns)
1✔
401
        new_var = cls(ns, name, dynamic=dynamic, meta=meta)
1✔
402
        var = ns.intern(name, new_var)
1✔
403
        if val is not cls.__UNBOUND_SENTINEL:
1✔
404
            var.bind_root(val)
1✔
405
        # Namespace.intern will return an existing interned Var for the same name
406
        # if one exists. We only want to set the meta and/or dynamic flag if the
407
        # Var is not new.
408
        if var is not new_var:
1✔
409
            var.reset_meta(meta)
1✔
410
            var.set_dynamic(dynamic)
1✔
411
        return var
1✔
412

413
    @classmethod
1✔
414
    def intern_unbound(
1✔
415
        cls,
416
        ns: Union["Namespace", sym.Symbol],
417
        name: sym.Symbol,
418
        dynamic: bool = False,
419
        meta: Optional[IPersistentMap] = None,
420
    ) -> "Var":
421
        """Create a new unbound `Var` instance to the symbol `name` in namespace `ns`."""
422
        return cls.intern(ns, name, cls.__UNBOUND_SENTINEL, dynamic=dynamic, meta=meta)
1✔
423

424
    @staticmethod
1✔
425
    def find_in_ns(
1✔
426
        ns_or_sym: Union["Namespace", sym.Symbol], name_sym: sym.Symbol
427
    ) -> "Optional[Var]":
428
        """Return the value current bound to the name `name_sym` in the namespace
429
        specified by `ns_sym`."""
430
        ns = (
1✔
431
            Namespace.get(ns_or_sym) if isinstance(ns_or_sym, sym.Symbol) else ns_or_sym
432
        )
433
        if ns is not None:
1✔
434
            return ns.find(name_sym)
1✔
435
        return None
1✔
436

437
    @classmethod
1✔
438
    def find(cls, ns_qualified_sym: sym.Symbol) -> "Optional[Var]":
1✔
439
        """Return the value currently bound to the name in the namespace specified
440
        by `ns_qualified_sym`."""
441
        ns = Maybe(ns_qualified_sym.ns).or_else_raise(
1✔
442
            lambda: ValueError(
443
                f"Namespace must be specified in Symbol {ns_qualified_sym}"
444
            )
445
        )
446
        ns_sym = sym.symbol(ns)
1✔
447
        name_sym = sym.symbol(ns_qualified_sym.name)
1✔
448
        return cls.find_in_ns(ns_sym, name_sym)
1✔
449

450
    @classmethod
1✔
451
    def find_safe(cls, ns_qualified_sym: sym.Symbol) -> "Var":
1✔
452
        """Return the Var currently bound to the name in the namespace specified
453
        by `ns_qualified_sym`. If no Var is bound to that name, raise an exception.
454

455
        This is a utility method to return useful debugging information when code
456
        refers to an invalid symbol at runtime."""
457
        v = cls.find(ns_qualified_sym)
1✔
458
        if v is None:
1✔
459
            raise RuntimeException(
1✔
460
                f"Unable to resolve symbol {ns_qualified_sym} in this context"
461
            )
462
        return v
1✔
463

464

465
Frame = IPersistentSet[Var]
1✔
466
FrameStack = IPersistentStack[Frame]
1✔
467

468

469
class _ThreadBindings(threading.local):
1✔
470
    def __init__(self):
1✔
471
        self._bindings: FrameStack = vec.PersistentVector.empty()
1✔
472

473
    def get_bindings(self) -> FrameStack:
1✔
474
        return self._bindings
1✔
475

476
    def push_bindings(self, frame: Frame) -> None:
1✔
477
        self._bindings = self._bindings.cons(frame)
1✔
478

479
    def pop_bindings(self) -> Frame:
1✔
480
        frame = self._bindings.peek()
1✔
481
        self._bindings = self._bindings.pop()
1✔
482
        assert frame is not None
1✔
483
        return frame
1✔
484

485

486
_THREAD_BINDINGS = _ThreadBindings()
1✔
487

488

489
AliasMap = lmap.PersistentMap[sym.Symbol, sym.Symbol]
1✔
490
Module = Union[BasilispModule, types.ModuleType]
1✔
491
ModuleMap = lmap.PersistentMap[sym.Symbol, Module]
1✔
492
NamespaceMap = lmap.PersistentMap[sym.Symbol, "Namespace"]
1✔
493
VarMap = lmap.PersistentMap[sym.Symbol, Var]
1✔
494

495

496
class Namespace(ReferenceBase):
1✔
497
    """Namespaces serve as organizational units in Basilisp code, just as
498
    they do in Clojure code. Vars are mutable containers for functions and
499
    data which may be interned in a namespace and referred to by a Symbol.
500
    Namespaces additionally may have aliases to other namespaces, so code
501
    organized in one namespace may conveniently refer to code or data in
502
    other namespaces using that alias as the Symbol's namespace.
503
    Namespaces are constructed def-by-def as Basilisp reads in each form
504
    in a file (which will typically declare a namespace at the top).
505
    Namespaces have the following fields of interest:
506
    - `aliases` is a mapping between a symbolic alias and another
507
      Namespace. The fully qualified name of a namespace is also
508
      an alias for itself.
509

510
    - `imports` is a mapping of names to Python modules imported
511
      into the current namespace.
512

513
    - `interns` is a mapping between a symbolic name and a Var. The
514
      Var may point to code, data, or nothing, if it is unbound. Vars
515
      in `interns` are interned in _this_ namespace.
516

517
    - `refers` is a mapping between a symbolic name and a Var. Vars in
518
      `refers` are interned in another namespace and are only referred
519
      to without an alias in this namespace.
520
    """
521

522
    DEFAULT_IMPORTS = lset.set(
1✔
523
        map(
524
            sym.symbol,
525
            [
526
                "attr",
527
                "builtins",
528
                "functools",
529
                "io",
530
                "importlib",
531
                "operator",
532
                "sys",
533
                "basilisp.lang.atom",
534
                "basilisp.lang.compiler",
535
                "basilisp.lang.delay",
536
                "basilisp.lang.exception",
537
                "basilisp.lang.futures",
538
                "basilisp.lang.interfaces",
539
                "basilisp.lang.keyword",
540
                "basilisp.lang.list",
541
                "basilisp.lang.map",
542
                "basilisp.lang.multifn",
543
                "basilisp.lang.promise",
544
                "basilisp.lang.queue",
545
                "basilisp.lang.reader",
546
                "basilisp.lang.reduced",
547
                "basilisp.lang.runtime",
548
                "basilisp.lang.seq",
549
                "basilisp.lang.set",
550
                "basilisp.lang.symbol",
551
                "basilisp.lang.vector",
552
                "basilisp.lang.volatile",
553
                "basilisp.lang.util",
554
            ],
555
        )
556
    )
557

558
    _NAMESPACES: Atom[NamespaceMap] = Atom(lmap.PersistentMap.empty())
1✔
559

560
    __slots__ = (
1✔
561
        "_name",
562
        "_module",
563
        "_meta",
564
        "_lock",
565
        "_interns",
566
        "_refers",
567
        "_aliases",
568
        "_imports",
569
        "_import_aliases",
570
    )
571

572
    def __init__(
1✔
573
        self, name: sym.Symbol, module: Optional[BasilispModule] = None
574
    ) -> None:
575
        self._name = name
1✔
576
        self._module = Maybe(module).or_else(lambda: _new_module(name.as_python_sym()))
1✔
577

578
        self._meta: Optional[IPersistentMap] = None
1✔
579
        self._lock = RWLockFair()
1✔
580

581
        self._aliases: NamespaceMap = lmap.PersistentMap.empty()
1✔
582
        self._imports: ModuleMap = lmap.map(
1✔
583
            dict(
584
                map(
585
                    lambda s: (s, importlib.import_module(s.name)),
586
                    Namespace.DEFAULT_IMPORTS,
587
                )
588
            )
589
        )
590
        self._import_aliases: AliasMap = lmap.PersistentMap.empty()
1✔
591
        self._interns: VarMap = lmap.PersistentMap.empty()
1✔
592
        self._refers: VarMap = lmap.PersistentMap.empty()
1✔
593

594
    @property
1✔
595
    def name(self) -> str:
1✔
596
        return self._name.name
1✔
597

598
    @property
1✔
599
    def module(self) -> BasilispModule:
1✔
600
        return self._module
1✔
601

602
    @module.setter
1✔
603
    def module(self, m: BasilispModule):
1✔
604
        """Override the Python module for this Namespace.
605

606
        ***WARNING**
607
        This should only be done by basilisp.importer code to make sure the
608
        correct module is generated for `basilisp.core`."""
609
        self._module = m
1✔
610

611
    @property
1✔
612
    def aliases(self) -> NamespaceMap:
1✔
613
        """A mapping between a symbolic alias and another Namespace. The
614
        fully qualified name of a namespace is also an alias for itself."""
615
        with self._lock.gen_rlock():
1✔
616
            return self._aliases
1✔
617

618
    @property
1✔
619
    def imports(self) -> ModuleMap:
1✔
620
        """A mapping of names to Python modules imported into the current
621
        namespace."""
622
        with self._lock.gen_rlock():
1✔
623
            return self._imports
1✔
624

625
    @property
1✔
626
    def import_aliases(self) -> AliasMap:
1✔
627
        """A mapping of a symbolic alias and a Python module name."""
628
        with self._lock.gen_rlock():
1✔
629
            return self._import_aliases
1✔
630

631
    @property
1✔
632
    def interns(self) -> VarMap:
1✔
633
        """A mapping between a symbolic name and a Var. The Var may point to
634
        code, data, or nothing, if it is unbound. Vars in `interns` are
635
        interned in _this_ namespace."""
636
        with self._lock.gen_rlock():
1✔
637
            return self._interns
1✔
638

639
    @property
1✔
640
    def refers(self) -> VarMap:
1✔
641
        """A mapping between a symbolic name and a Var. Vars in refers are
642
        interned in another namespace and are only referred to without an
643
        alias in this namespace."""
644
        with self._lock.gen_rlock():
1✔
645
            return self._refers
1✔
646

647
    def __repr__(self):
648
        return f"{self._name}"
649

650
    def __hash__(self):
1✔
651
        return hash(self._name)
1✔
652

653
    def require(self, ns_name: str, *aliases: sym.Symbol) -> BasilispModule:
1✔
654
        """Require the Basilisp Namespace named by `ns_name` and add any aliases given
655
        to this Namespace.
656

657
        This method is called in code generated for the `require*` special form."""
658
        try:
1✔
659
            ns_module = importlib.import_module(munge(ns_name))
1✔
660
        except ModuleNotFoundError as e:
1✔
661
            raise ImportError(
1✔
662
                f"Basilisp namespace '{ns_name}' not found",
663
            ) from e
664
        else:
665
            assert isinstance(ns_module, BasilispModule)
1✔
666
            ns_sym = sym.symbol(ns_name)
1✔
667
            ns = self.get(ns_sym)
1✔
668
            assert ns is not None, "Namespace must exist after being required"
1✔
669
            if aliases:
1✔
670
                self.add_alias(ns, *aliases)
1✔
671
            return ns_module
1✔
672

673
    def add_alias(self, namespace: "Namespace", *aliases: sym.Symbol) -> None:
1✔
674
        """Add Symbol aliases for the given Namespace."""
675
        with self._lock.gen_wlock():
1✔
676
            new_m = self._aliases
1✔
677
            for alias in aliases:
1✔
678
                new_m = new_m.assoc(alias, namespace)
1✔
679
            self._aliases = new_m
1✔
680

681
    def get_alias(self, alias: sym.Symbol) -> "Optional[Namespace]":
1✔
682
        """Get the Namespace aliased by Symbol or None if it does not exist."""
683
        with self._lock.gen_rlock():
1✔
684
            return self._aliases.val_at(alias, None)
1✔
685

686
    def remove_alias(self, alias: sym.Symbol) -> None:
1✔
687
        """Remove the Namespace aliased by Symbol. Return None."""
688
        with self._lock.gen_wlock():
1✔
689
            self._aliases = self._aliases.dissoc(alias)
1✔
690

691
    def intern(self, sym: sym.Symbol, var: Var, force: bool = False) -> Var:
1✔
692
        """Intern the Var given in this namespace mapped by the given Symbol.
693
        If the Symbol already maps to a Var, this method _will not overwrite_
694
        the existing Var mapping unless the force keyword argument is given
695
        and is True."""
696
        with self._lock.gen_wlock():
1✔
697
            old_var = self._interns.val_at(sym, None)
1✔
698
            if old_var is None or force:
1✔
699
                self._interns = self._interns.assoc(sym, var)
1✔
700
            return self._interns.val_at(sym)
1✔
701

702
    def unmap(self, sym: sym.Symbol) -> None:
1✔
703
        with self._lock.gen_wlock():
1✔
704
            self._interns = self._interns.dissoc(sym)
1✔
705

706
    def find(self, sym: sym.Symbol) -> Optional[Var]:
1✔
707
        """Find Vars mapped by the given Symbol input or None if no Vars are
708
        mapped by that Symbol."""
709
        with self._lock.gen_rlock():
1✔
710
            v = self._interns.val_at(sym, None)
1✔
711
            if v is None:
1✔
712
                return self._refers.val_at(sym, None)
1✔
713
            return v
1✔
714

715
    def add_import(self, sym: sym.Symbol, module: Module, *aliases: sym.Symbol) -> None:
1✔
716
        """Add the Symbol as an imported Symbol in this Namespace. If aliases are given,
717
        the aliases will be applied to the"""
718
        with self._lock.gen_wlock():
1✔
719
            self._imports = self._imports.assoc(sym, module)
1✔
720
            if aliases:
1✔
721
                m = self._import_aliases
1✔
722
                for alias in aliases:
1✔
723
                    m = m.assoc(alias, sym)
1✔
724
                self._import_aliases = m
1✔
725

726
    def get_import(self, sym: sym.Symbol) -> Optional[BasilispModule]:
1✔
727
        """Return the module if a moduled named by sym has been imported into
728
        this Namespace, None otherwise.
729

730
        First try to resolve a module directly with the given name. If no module
731
        can be resolved, attempt to resolve the module using import aliases."""
732
        with self._lock.gen_rlock():
1✔
733
            mod = self._imports.val_at(sym, None)
1✔
734
            if mod is None:
1✔
735
                alias = self._import_aliases.get(sym, None)
1✔
736
                if alias is None:
1✔
737
                    return None
1✔
738
                return self._imports.val_at(alias, None)
1✔
739
            return mod
1✔
740

741
    def add_refer(self, sym: sym.Symbol, var: Var) -> None:
1✔
742
        """Refer var in this namespace under the name sym."""
743
        if not var.is_private:
1✔
744
            with self._lock.gen_wlock():
1✔
745
                self._refers = self._refers.assoc(sym, var)
1✔
746

747
    def get_refer(self, sym: sym.Symbol) -> Optional[Var]:
1✔
748
        """Get the Var referred by Symbol or None if it does not exist."""
749
        with self._lock.gen_rlock():
1✔
750
            return self._refers.val_at(sym, None)
1✔
751

752
    def refer_all(self, other_ns: "Namespace") -> None:
1✔
753
        """Refer all the Vars in the other namespace."""
754
        with self._lock.gen_wlock():
1✔
755
            final_refers = self._refers
1✔
756
            for s, var in other_ns.interns.items():
1✔
757
                if not var.is_private:
1✔
758
                    final_refers = final_refers.assoc(s, var)
1✔
759
            self._refers = final_refers
1✔
760

761
    @classmethod
1✔
762
    def ns_cache(cls) -> lmap.PersistentMap:
1✔
763
        """Return a snapshot of the Namespace cache."""
764
        return cls._NAMESPACES.deref()
×
765

766
    @staticmethod
1✔
767
    def __get_or_create(
1✔
768
        ns_cache: NamespaceMap,
769
        name: sym.Symbol,
770
        module: Optional[BasilispModule] = None,
771
    ) -> lmap.PersistentMap:
772
        """Private swap function used by `get_or_create` to atomically swap
773
        the new namespace map into the global cache."""
774
        ns = ns_cache.val_at(name, None)
1✔
775
        if ns is not None:
1✔
776
            return ns_cache
1✔
777
        new_ns = Namespace(name, module=module)
1✔
778
        # The `ns` macro is important for setting up an new namespace,
779
        # but it becomes available only after basilisp.core has been
780
        # loaded.
781
        ns_var = Var.find_in_ns(CORE_NS_SYM, sym.symbol("ns"))
1✔
782
        if ns_var:
1✔
783
            new_ns.add_refer(sym.symbol("ns"), ns_var)
1✔
784

785
        return ns_cache.assoc(name, new_ns)
1✔
786

787
    @classmethod
1✔
788
    def get_or_create(
1✔
789
        cls, name: sym.Symbol, module: Optional[BasilispModule] = None
790
    ) -> "Namespace":
791
        """Get the namespace bound to the symbol `name` in the global namespace
792
        cache, creating it if it does not exist.
793
        Return the namespace."""
794
        return cls._NAMESPACES.swap(Namespace.__get_or_create, name, module=module)[
1✔
795
            name
796
        ]
797

798
    @classmethod
1✔
799
    def get(cls, name: sym.Symbol) -> "Optional[Namespace]":
1✔
800
        """Get the namespace bound to the symbol `name` in the global namespace
801
        cache. Return the namespace if it exists or None otherwise.."""
802
        return cls._NAMESPACES.deref().val_at(name, None)
1✔
803

804
    @classmethod
1✔
805
    def remove(cls, name: sym.Symbol) -> Optional["Namespace"]:
1✔
806
        """Remove the namespace bound to the symbol `name` in the global
807
        namespace cache and return that namespace.
808
        Return None if the namespace did not exist in the cache."""
809
        if name == CORE_NS_SYM:
1✔
810
            raise ValueError("Cannot remove the Basilisp core namespace")
1✔
811
        while True:
1✔
812
            oldval: lmap.PersistentMap = cls._NAMESPACES.deref()
1✔
813
            ns: Optional[Namespace] = oldval.val_at(name, None)
1✔
814
            newval = oldval
1✔
815
            if ns is not None:
1✔
816
                newval = oldval.dissoc(name)
1✔
817
            if cls._NAMESPACES.compare_and_set(oldval, newval):
1✔
818
                return ns
1✔
819

820
    # REPL Completion support
821

822
    @staticmethod
1✔
823
    def __completion_matcher(text: str) -> CompletionMatcher:
1✔
824
        """Return a function which matches any symbol keys from map entries
825
        against the given text."""
826

827
        def is_match(entry: Tuple[sym.Symbol, Any]) -> bool:
1✔
828
            return entry[0].name.startswith(text)
1✔
829

830
        return is_match
1✔
831

832
    def __complete_alias(
1✔
833
        self, prefix: str, name_in_ns: Optional[str] = None
834
    ) -> Iterable[str]:
835
        """Return an iterable of possible completions matching the given
836
        prefix from the list of aliased namespaces. If name_in_ns is given,
837
        further attempt to refine the list to matching names in that namespace."""
838
        candidates = filter(
1✔
839
            Namespace.__completion_matcher(prefix),
840
            ((s, n) for s, n in self.aliases.items()),
841
        )
842
        if name_in_ns is not None:
1✔
843
            for _, candidate_ns in candidates:
1✔
844
                for match in candidate_ns.__complete_interns(
1✔
845
                    name_in_ns, include_private_vars=False
846
                ):
847
                    yield f"{prefix}/{match}"
1✔
848
        else:
849
            for alias, _ in candidates:
1✔
850
                yield f"{alias}/"
1✔
851

852
    def __complete_imports_and_aliases(
1✔
853
        self, prefix: str, name_in_module: Optional[str] = None
854
    ) -> Iterable[str]:
855
        """Return an iterable of possible completions matching the given
856
        prefix from the list of imports and aliased imports. If name_in_module
857
        is given, further attempt to refine the list to matching names in that
858
        namespace."""
859
        imports = self.imports
1✔
860
        aliases = lmap.map(
1✔
861
            {
862
                alias: imports.val_at(import_name)
863
                for alias, import_name in self.import_aliases.items()
864
            }
865
        )
866

867
        candidates = filter(
1✔
868
            Namespace.__completion_matcher(prefix),
869
            itertools.chain(aliases.items(), imports.items()),
870
        )
871
        if name_in_module is not None:
1✔
872
            for _, module in candidates:
1✔
873
                for name in module.__dict__:
1✔
874
                    if name.startswith(name_in_module):
1✔
875
                        yield f"{prefix}/{name}"
1✔
876
        else:
877
            for candidate_name, _ in candidates:
1✔
878
                yield f"{candidate_name}/"
1✔
879

880
    def __complete_interns(
1✔
881
        self, value: str, include_private_vars: bool = True
882
    ) -> Iterable[str]:
883
        """Return an iterable of possible completions matching the given
884
        prefix from the list of interned Vars."""
885
        if include_private_vars:
1✔
886
            is_match = Namespace.__completion_matcher(value)
1✔
887
        else:
888
            _is_match = Namespace.__completion_matcher(value)
1✔
889

890
            def is_match(entry: Tuple[sym.Symbol, Var]) -> bool:
1✔
891
                return _is_match(entry) and not entry[1].is_private
1✔
892

893
        return map(
1✔
894
            lambda entry: f"{entry[0].name}",
895
            filter(is_match, ((s, v) for s, v in self.interns.items())),
896
        )
897

898
    def __complete_refers(self, value: str) -> Iterable[str]:
1✔
899
        """Return an iterable of possible completions matching the given
900
        prefix from the list of referred Vars."""
901
        return map(
1✔
902
            lambda entry: f"{entry[0].name}",
903
            filter(
904
                Namespace.__completion_matcher(value),
905
                ((s, v) for s, v in self.refers.items()),
906
            ),
907
        )
908

909
    def complete(self, text: str) -> Iterable[str]:
1✔
910
        """Return an iterable of possible completions for the given text in
911
        this namespace."""
912
        assert not text.startswith(":")
1✔
913

914
        if "/" in text:
1✔
915
            prefix, suffix = text.split("/", maxsplit=1)
1✔
916
            results = itertools.chain(
1✔
917
                self.__complete_alias(prefix, name_in_ns=suffix),
918
                self.__complete_imports_and_aliases(prefix, name_in_module=suffix),
919
            )
920
        else:
921
            results = itertools.chain(
1✔
922
                self.__complete_alias(text),
923
                self.__complete_imports_and_aliases(text),
924
                self.__complete_interns(text),
925
                self.__complete_refers(text),
926
            )
927

928
        return results
1✔
929

930

931
def get_thread_bindings() -> IPersistentMap[Var, Any]:
1✔
932
    """Return the current thread-local bindings."""
933
    bindings = {}
1✔
934
    for frame in _THREAD_BINDINGS.get_bindings():
1✔
935
        bindings.update({var: var.value for var in frame})
1✔
936
    return lmap.map(bindings)
1✔
937

938

939
def push_thread_bindings(m: IPersistentMap[Var, Any]) -> None:
1✔
940
    """Push thread local bindings for the Var keys in m using the values."""
941
    bindings = set()
1✔
942

943
    for var, val in m.items():
1✔
944
        if not var.dynamic:
1✔
945
            raise RuntimeException(
1✔
946
                "cannot set thread-local bindings for non-dynamic Var"
947
            )
948
        var.push_bindings(val)
1✔
949
        bindings.add(var)
1✔
950

951
    _THREAD_BINDINGS.push_bindings(lset.set(bindings))
1✔
952

953

954
def pop_thread_bindings() -> None:
1✔
955
    """Pop the thread local bindings set by push_thread_bindings above."""
956
    try:
1✔
957
        bindings = _THREAD_BINDINGS.pop_bindings()
1✔
958
    except IndexError as e:
1✔
959
        raise RuntimeException(
1✔
960
            "cannot pop thread-local bindings without prior push"
961
        ) from e
962

963
    for var in bindings:
1✔
964
        var.pop_bindings()
1✔
965

966

967
###################
968
# Runtime Support #
969
###################
970

971
T = TypeVar("T")
1✔
972

973

974
@functools.singledispatch
1✔
975
def first(o):
1✔
976
    """If o is a ISeq, return the first element from o. If o is None, return
977
    None. Otherwise, coerces o to a Seq and returns the first."""
978
    s = to_seq(o)
1✔
979
    if s is None:
1✔
980
        return None
1✔
981
    return s.first
1✔
982

983

984
@first.register(type(None))
1✔
985
def _first_none(_: None) -> None:
1✔
986
    return None
1✔
987

988

989
@first.register(ISeq)
1✔
990
def _first_iseq(o: ISeq[T]) -> Optional[T]:
1✔
991
    return o.first
1✔
992

993

994
@functools.singledispatch
1✔
995
def rest(o) -> ISeq:
1✔
996
    """If o is a ISeq, return the elements after the first in o. If o is None,
997
    returns an empty seq. Otherwise, coerces o to a seq and returns the rest."""
998
    n = to_seq(o)
1✔
999
    if n is None:
1✔
1000
        return lseq.EMPTY
1✔
1001
    return n.rest
1✔
1002

1003

1004
@rest.register(type(None))
1✔
1005
def _rest_none(_: None) -> ISeq:
1✔
1006
    return lseq.EMPTY
1✔
1007

1008

1009
@rest.register(type(ISeq))
1✔
1010
def _rest_iseq(o: ISeq[T]) -> ISeq:
1✔
1011
    s = o.rest
×
1012
    if s is None:
×
1013
        return lseq.EMPTY
×
1014
    return s
×
1015

1016

1017
def nthrest(coll, i: int):
1✔
1018
    """Returns the nth rest sequence of coll, or coll if i is 0."""
1019
    while True:
1✔
1020
        if coll is None:
1✔
1021
            return None
1✔
1022
        if i == 0:
1✔
1023
            return coll
1✔
1024
        i -= 1
1✔
1025
        coll = rest(coll)
1✔
1026

1027

1028
def next_(o) -> Optional[ISeq]:
1✔
1029
    """Calls rest on o. If o returns an empty sequence or None, returns None.
1030
    Otherwise, returns the elements after the first in o."""
1031
    return to_seq(rest(o))
1✔
1032

1033

1034
def nthnext(coll, i: int) -> Optional[ISeq]:
1✔
1035
    """Returns the nth next sequence of coll."""
1036
    while True:
1✔
1037
        if coll is None:
1✔
1038
            return None
1✔
1039
        if i == 0:
1✔
1040
            return to_seq(coll)
1✔
1041
        i -= 1
1✔
1042
        coll = next_(coll)
1✔
1043

1044

1045
@functools.singledispatch
1✔
1046
def _cons(seq, o) -> ISeq:
1✔
1047
    return Maybe(to_seq(seq)).map(lambda s: s.cons(o)).or_else(lambda: llist.l(o))
1✔
1048

1049

1050
@_cons.register(type(None))
1✔
1051
def _cons_none(_: None, o) -> ISeq:
1✔
1052
    return llist.l(o)
1✔
1053

1054

1055
@_cons.register(ISeq)
1✔
1056
def _cons_iseq(seq: ISeq, o) -> ISeq:
1✔
1057
    return seq.cons(o)
1✔
1058

1059

1060
def cons(o, seq) -> ISeq:
1✔
1061
    """Creates a new sequence where o is the first element and seq is the rest.
1062
    If seq is None, return a list containing o. If seq is not a ISeq, attempt
1063
    to coerce it to a ISeq and then cons o onto the resulting sequence."""
1064
    return _cons(seq, o)
1✔
1065

1066

1067
to_seq = lseq.to_seq
1✔
1068

1069

1070
def concat(*seqs) -> ISeq:
1✔
1071
    """Concatenate the sequences given by seqs into a single ISeq."""
1072
    allseqs = lseq.sequence(itertools.chain.from_iterable(filter(None, seqs)))
1✔
1073
    if allseqs is None:
1✔
1074
        return lseq.EMPTY
×
1075
    return allseqs
1✔
1076

1077

1078
def apply(f, args):
1✔
1079
    """Apply function f to the arguments provided.
1080
    The last argument must always be coercible to a Seq. Intermediate
1081
    arguments are not modified.
1082
    For example:
1083
        (apply max [1 2 3])   ;=> 3
1084
        (apply max 4 [1 2 3]) ;=> 4"""
1085
    final = list(args[:-1])
1✔
1086

1087
    try:
1✔
1088
        last = args[-1]
1✔
1089
    except TypeError as e:
×
1090
        logger.debug("Ignored %s: %s", type(e).__name__, e)
×
1091

1092
    s = to_seq(last)
1✔
1093
    if s is not None:
1✔
1094
        final.extend(s)
1✔
1095

1096
    return f(*final)
1✔
1097

1098

1099
def apply_kw(f, args):
1✔
1100
    """Apply function f to the arguments provided.
1101
    The last argument must always be coercible to a Mapping. Intermediate
1102
    arguments are not modified.
1103
    For example:
1104
        (apply python/dict {:a 1} {:b 2})   ;=> #py {:a 1 :b 2}
1105
        (apply python/dict {:a 1} {:a 2})   ;=> #py {:a 2}"""
1106
    final = list(args[:-1])
1✔
1107

1108
    try:
1✔
1109
        last = args[-1]
1✔
1110
    except TypeError as e:
×
1111
        logger.debug("Ignored %s: %s", type(e).__name__, e)
×
1112

1113
    kwargs = {
1✔
1114
        to_py(k, lambda kw: munge(kw.name, allow_builtins=True)): v
1115
        for k, v in last.items()
1116
    }
1117
    return f(*final, **kwargs)
1✔
1118

1119

1120
def count(coll) -> int:
1✔
1121
    if coll is None:
1✔
1122
        return 0
1✔
1123
    else:
1124
        try:
1✔
1125
            return len(coll)
1✔
1126
        except (AttributeError, TypeError):
1✔
1127
            try:
1✔
1128
                return sum(1 for _ in coll)
1✔
1129
            except TypeError as e:
×
1130
                raise TypeError(
×
1131
                    f"count not supported on object of type {type(coll)}"
1132
                ) from e
1133

1134

1135
__nth_sentinel = object()
1✔
1136

1137

1138
@functools.singledispatch
1✔
1139
def nth(coll, i: int, notfound=__nth_sentinel):
1✔
1140
    """Returns the ith element of coll (0-indexed), if it exists.
1141
    None otherwise. If i is out of bounds, throws an IndexError unless
1142
    notfound is specified."""
1143
    raise TypeError(f"nth not supported on object of type {type(coll)}")
1✔
1144

1145

1146
@nth.register(type(None))
1✔
1147
def _nth_none(_: None, i: int, notfound=__nth_sentinel) -> None:
1✔
1148
    return notfound if notfound is not __nth_sentinel else None
1✔
1149

1150

1151
@nth.register(Sequence)
1✔
1152
def _nth_sequence(coll: Sequence, i: int, notfound=__nth_sentinel):
1✔
1153
    try:
1✔
1154
        return coll[i]
1✔
1155
    except IndexError as ex:
1✔
1156
        if notfound is not __nth_sentinel:
1✔
1157
            return notfound
1✔
1158
        raise ex
1✔
1159

1160

1161
@nth.register(ISeq)
1✔
1162
def _nth_iseq(coll: ISeq, i: int, notfound=__nth_sentinel):
1✔
1163
    for j, e in enumerate(coll):
1✔
1164
        if i == j:
1✔
1165
            return e
1✔
1166

1167
    if notfound is not __nth_sentinel:
1✔
1168
        return notfound
1✔
1169

1170
    raise IndexError(f"Index {i} out of bounds")
1✔
1171

1172

1173
@functools.singledispatch
1✔
1174
def contains(coll, k):
1✔
1175
    """Return true if o contains the key k."""
1176
    return k in coll
1✔
1177

1178

1179
@contains.register(type(None))
1✔
1180
def _contains_none(_, __):
1✔
1181
    return False
1✔
1182

1183

1184
@contains.register(IAssociative)
1✔
1185
def _contains_iassociative(coll, k):
1✔
1186
    return coll.contains(k)
1✔
1187

1188

1189
@functools.singledispatch
1✔
1190
def get(m, k, default=None):  # pylint: disable=unused-argument
1✔
1191
    """Return the value of k in m. Return default if k not found in m."""
1192
    return default
1✔
1193

1194

1195
@get.register(bytes)
1✔
1196
@get.register(dict)
1✔
1197
@get.register(list)
1✔
1198
@get.register(str)
1✔
1199
@get.register(bytes)
1✔
1200
def _get_others(m, k, default=None):
1✔
1201
    try:
1✔
1202
        return m[k]
1✔
1203
    except (KeyError, IndexError):
1✔
1204
        return default
1✔
1205

1206

1207
@get.register(IPersistentSet)
1✔
1208
@get.register(ITransientSet)
1✔
1209
@get.register(frozenset)
1✔
1210
@get.register(set)
1✔
1211
def _get_settypes(m, k, default=None):
1✔
1212
    if k in m:
1✔
1213
        return k
1✔
1214
    return default
1✔
1215

1216

1217
@get.register(ILookup)
1✔
1218
def _get_ilookup(m, k, default=None):
1✔
1219
    return m.val_at(k, default)
1✔
1220

1221

1222
@functools.singledispatch
1✔
1223
def assoc(m, *kvs):
1✔
1224
    """Associate keys to values in associative data structure m. If m is None,
1225
    returns a new Map with key-values kvs."""
1226
    raise TypeError(
1✔
1227
        f"Object of type {type(m)} does not implement IAssociative interface"
1228
    )
1229

1230

1231
@assoc.register(type(None))
1✔
1232
def _assoc_none(_: None, *kvs) -> lmap.PersistentMap:
1✔
1233
    return lmap.PersistentMap.empty().assoc(*kvs)
1✔
1234

1235

1236
@assoc.register(IAssociative)
1✔
1237
def _assoc_iassociative(m: IAssociative, *kvs):
1✔
1238
    return m.assoc(*kvs)
1✔
1239

1240

1241
@functools.singledispatch
1✔
1242
def update(m, k, f, *args):
1✔
1243
    """Updates the value for key k in associative data structure m with the return value from
1244
    calling f(old_v, *args). If m is None, use an empty map. If k is not in m, old_v will be
1245
    None."""
1246
    raise TypeError(
1✔
1247
        f"Object of type {type(m)} does not implement IAssociative interface"
1248
    )
1249

1250

1251
@update.register(type(None))
1✔
1252
def _update_none(_: None, k, f, *args) -> lmap.PersistentMap:
1✔
1253
    return lmap.PersistentMap.empty().assoc(k, f(None, *args))
1✔
1254

1255

1256
@update.register(IAssociative)
1✔
1257
def _update_iassociative(m: IAssociative, k, f, *args):
1✔
1258
    old_v = m.val_at(k)
1✔
1259
    new_v = f(old_v, *args)
1✔
1260
    return m.assoc(k, new_v)
1✔
1261

1262

1263
@functools.singledispatch
1✔
1264
def conj(coll, *xs):
1✔
1265
    """Conjoin xs to collection. New elements may be added in different positions
1266
    depending on the type of coll. conj returns the same type as coll. If coll
1267
    is None, return a list with xs conjoined."""
1268
    raise TypeError(
1✔
1269
        f"Object of type {type(coll)} does not implement "
1270
        "IPersistentCollection interface"
1271
    )
1272

1273

1274
@conj.register(type(None))
1✔
1275
def _conj_none(_: None, *xs):
1✔
1276
    l = llist.PersistentList.empty()
1✔
1277
    return l.cons(*xs)
1✔
1278

1279

1280
@conj.register(IPersistentCollection)
1✔
1281
def _conj_ipersistentcollection(coll: IPersistentCollection, *xs):
1✔
1282
    return coll.cons(*xs)
1✔
1283

1284

1285
def partial(f, *args, **kwargs):
1✔
1286
    """Return a function which is the partial application of f with args and kwargs."""
1287

1288
    @functools.wraps(f)
1✔
1289
    def partial_f(*inner_args, **inner_kwargs):
1✔
1290
        return f(*itertools.chain(args, inner_args), **{**kwargs, **inner_kwargs})
1✔
1291

1292
    return partial_f
1✔
1293

1294

1295
@functools.singledispatch
1✔
1296
def deref(o, timeout_s=None, timeout_val=None):
1✔
1297
    """Dereference a Deref object and return its contents.
1298

1299
    If o is an object implementing IBlockingDeref and timeout_s and
1300
    timeout_val are supplied, deref will wait at most timeout_s seconds,
1301
    returning timeout_val if timeout_s seconds elapse and o has not
1302
    returned."""
1303
    raise TypeError(f"Object of type {type(o)} cannot be dereferenced")
1✔
1304

1305

1306
@deref.register(IBlockingDeref)
1✔
1307
def _deref_blocking(
1✔
1308
    o: IBlockingDeref, timeout_s: Optional[float] = None, timeout_val=None
1309
):
1310
    return o.deref(timeout_s, timeout_val)
1✔
1311

1312

1313
@deref.register(IDeref)
1✔
1314
def _deref(o: IDeref):
1✔
1315
    return o.deref()
1✔
1316

1317

1318
def equals(v1, v2) -> bool:
1✔
1319
    """Compare two objects by value. Unlike the standard Python equality operator,
1320
    this function does not consider 1 == True or 0 == False. All other equality
1321
    operations are the same and performed using Python's equality operator."""
1322
    if isinstance(v1, (bool, type(None))) or isinstance(v2, (bool, type(None))):
1✔
1323
        return v1 is v2
1✔
1324
    return v1 == v2
1✔
1325

1326

1327
@functools.singledispatch
1✔
1328
def divide(x: LispNumber, y: LispNumber) -> LispNumber:
1✔
1329
    """Division reducer. If both arguments are integers, return a Fraction.
1330
    Otherwise, return the true division of x and y."""
1331
    return x / y
1✔
1332

1333

1334
@divide.register(int)
1✔
1335
def _divide_ints(x: int, y: LispNumber) -> LispNumber:
1✔
1336
    if isinstance(y, int):
1✔
1337
        return Fraction(x, y)
1✔
1338
    return x / y
1✔
1339

1340

1341
def quotient(num, div) -> LispNumber:
1✔
1342
    """Return the integral quotient resulting from the division of num by div."""
1343
    return math.trunc(num / div)
1✔
1344

1345

1346
@functools.singledispatch
1✔
1347
def compare(x, y) -> int:
1✔
1348
    """Return either -1, 0, or 1 to indicate the relationship between x and y.
1349

1350
    This is a 3-way comparator commonly used in Java-derived systems. Python does not
1351
    typically use 3-way comparators, so this function convert's Python's `__lt__` and
1352
    `__gt__` method returns into one of the 3-way comparator return values."""
1353
    if y is None:
1✔
1354
        assert x is not None, "x cannot be nil"
1✔
1355
        return 1
1✔
1356
    return (x > y) - (x < y)
1✔
1357

1358

1359
@compare.register(type(None))
1✔
1360
def _compare_nil(_: None, y) -> int:
1✔
1361
    # nil is less than all values, except itself.
1362
    return 0 if y is None else -1
1✔
1363

1364

1365
@compare.register(decimal.Decimal)
1✔
1366
def _compare_decimal(x: decimal.Decimal, y) -> int:
1✔
1367
    # Decimal instances will not compare with float("nan"), so we need a special case
1368
    if isinstance(y, float):
1✔
1369
        return -compare(y, x)  # pylint: disable=arguments-out-of-order
1✔
1370
    return (x > y) - (x < y)
1✔
1371

1372

1373
@compare.register(float)
1✔
1374
def _compare_float(x, y) -> int:
1✔
1375
    if y is None:
1✔
1376
        return 1
1✔
1377
    if math.isnan(x):
1✔
1378
        return 0
1✔
1379
    return (x > y) - (x < y)
1✔
1380

1381

1382
@compare.register(IPersistentSet)
1✔
1383
def _compare_sets(x: IPersistentSet, y) -> int:
1✔
1384
    # Sets are not comparable (because there is no total ordering between sets).
1385
    # However, in Python comparison is done using __lt__ and __gt__, which AbstractSet
1386
    # inconveniently also uses as part of it's API for comparing sets with subset and
1387
    # superset relationships. To "break" that, we just override the comparison method.
1388
    # One consequence of this is that it may be possible to sort a collection of sets,
1389
    # since `compare` isn't actually consulted in sorting.
1390
    raise TypeError(
1✔
1391
        f"cannot compare instances of '{type(x).__name__}' and '{type(y).__name__}'"
1392
    )
1393

1394

1395
def _fn_to_comparator(f):
1✔
1396
    """Coerce F comparator fn to a 3 way comparator fn."""
1397

1398
    if f == compare:  # pylint: disable=comparison-with-callable
1✔
1399
        return f
1✔
1400

1401
    def cmp(x, y):
1✔
1402
        r = f(x, y)
1✔
1403
        if isinstance(r, numbers.Number) and not isinstance(r, bool):
1✔
1404
            return r
1✔
1405
        elif r:
1✔
1406
            return -1
1✔
1407
        elif f(y, x):
1✔
1408
            return 1
1✔
1409
        else:
1410
            return 0
1✔
1411

1412
    return cmp
1✔
1413

1414

1415
def sort(coll, f=compare) -> Optional[ISeq]:
1✔
1416
    """Return a sorted sequence of the elements in coll. If a
1417
    comparator function f is provided, compare elements in coll
1418
    using f or use the `compare` fn if not.
1419

1420
    The comparator fn can be either a boolean or 3-way comparison fn."""
1421
    seq = lseq.to_seq(coll)
1✔
1422
    if seq:
1✔
1423
        if isinstance(coll, IPersistentMap):
1✔
1424
            coll = seq
1✔
1425

1426
        comparator = _fn_to_comparator(f)
1✔
1427

1428
        class key:
1✔
1429
            __slots__ = ("obj",)
1✔
1430

1431
            def __init__(self, obj):
1✔
1432
                self.obj = obj
1✔
1433

1434
            def __lt__(self, other):
1✔
1435
                return comparator(self.obj, other.obj) < 0
1✔
1436

1437
            def __gt__(self, other):
1✔
1438
                return comparator(self.obj, other.obj) > 0
×
1439

1440
            def __eq__(self, other):
1✔
1441
                return comparator(self.obj, other.obj) == 0
×
1442

1443
            def __le__(self, other):
1✔
1444
                return comparator(self.obj, other.obj) <= 0
×
1445

1446
            def __ge__(self, other):
1✔
1447
                return comparator(self.obj, other.obj) >= 0
×
1448

1449
            __hash__ = None  # type: ignore
1✔
1450

1451
        return lseq.sequence(sorted(coll, key=key))
1✔
1452
    else:
1453
        return llist.EMPTY
1✔
1454

1455

1456
def sort_by(keyfn, coll, cmp=compare) -> Optional[ISeq]:
1✔
1457
    """Return a sorted sequence of the elements in coll. If a
1458
    comparator function cmp is provided, compare elements in coll
1459
    using cmp or use the `compare` fn if not.
1460

1461
    The comparator fn can be either a boolean or 3-way comparison fn."""
1462
    seq = lseq.to_seq(coll)
1✔
1463
    if seq:
1✔
1464
        if isinstance(coll, IPersistentMap):
1✔
1465
            coll = seq
1✔
1466

1467
        comparator = _fn_to_comparator(cmp)
1✔
1468

1469
        class key:
1✔
1470
            __slots__ = ("obj",)
1✔
1471

1472
            def __init__(self, obj):
1✔
1473
                self.obj = obj
1✔
1474

1475
            def __lt__(self, other):
1✔
1476
                return comparator(keyfn(self.obj), keyfn(other.obj)) < 0
1✔
1477

1478
            def __gt__(self, other):
1✔
1479
                return comparator(keyfn(self.obj), keyfn(other.obj)) > 0
×
1480

1481
            def __eq__(self, other):
1✔
1482
                return comparator(keyfn(self.obj), keyfn(other.obj)) == 0
×
1483

1484
            def __le__(self, other):
1✔
1485
                return comparator(keyfn(self.obj), keyfn(other.obj)) <= 0
×
1486

1487
            def __ge__(self, other):
1✔
1488
                return comparator(keyfn(self.obj), keyfn(other.obj)) >= 0
×
1489

1490
            __hash__ = None  # type: ignore
1✔
1491

1492
        return lseq.sequence(sorted(coll, key=key))
1✔
1493
    else:
1494
        return llist.EMPTY
1✔
1495

1496

1497
def is_special_form(s: sym.Symbol) -> bool:
1✔
1498
    """Return True if s names a special form."""
1499
    return s in _SPECIAL_FORMS
1✔
1500

1501

1502
@functools.singledispatch
1✔
1503
def to_lisp(o, keywordize_keys: bool = True):  # pylint: disable=unused-argument
1✔
1504
    """Recursively convert Python collections into Lisp collections."""
1505
    return o
1✔
1506

1507

1508
@to_lisp.register(list)
1✔
1509
@to_lisp.register(tuple)
1✔
1510
def _to_lisp_vec(o: Iterable, keywordize_keys: bool = True) -> vec.PersistentVector:
1✔
1511
    return vec.vector(
1✔
1512
        map(functools.partial(to_lisp, keywordize_keys=keywordize_keys), o)
1513
    )
1514

1515

1516
@functools.singledispatch
1✔
1517
def _keywordize_keys(k, keywordize_keys: bool = True):
1✔
1518
    return to_lisp(k, keywordize_keys=keywordize_keys)
1✔
1519

1520

1521
@_keywordize_keys.register(str)
1✔
1522
def _keywordize_keys_str(k, keywordize_keys: bool = True):
1✔
1523
    return kw.keyword(k)
1✔
1524

1525

1526
@to_lisp.register(dict)
1✔
1527
def _to_lisp_map(o: Mapping, keywordize_keys: bool = True) -> lmap.PersistentMap:
1✔
1528
    process_key = _keywordize_keys if keywordize_keys else to_lisp
1✔
1529
    return lmap.map(
1✔
1530
        {
1531
            process_key(k, keywordize_keys=keywordize_keys): to_lisp(
1532
                v, keywordize_keys=keywordize_keys
1533
            )
1534
            for k, v in o.items()
1535
        }
1536
    )
1537

1538

1539
@to_lisp.register(frozenset)
1✔
1540
@to_lisp.register(set)
1✔
1541
def _to_lisp_set(o: AbstractSet, keywordize_keys: bool = True) -> lset.PersistentSet:
1✔
1542
    return lset.set(map(functools.partial(to_lisp, keywordize_keys=keywordize_keys), o))
1✔
1543

1544

1545
def _kw_name(kw: kw.Keyword) -> str:
1✔
1546
    return kw.name
1✔
1547

1548

1549
@functools.singledispatch
1✔
1550
def to_py(
1✔
1551
    o, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
1552
):  # pylint: disable=unused-argument
1553
    """Recursively convert Lisp collections into Python collections."""
1554
    return o
1✔
1555

1556

1557
@to_py.register(kw.Keyword)
1✔
1558
def _to_py_kw(o: kw.Keyword, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name) -> Any:
1✔
1559
    return keyword_fn(o)
1✔
1560

1561

1562
@to_py.register(IPersistentList)
1✔
1563
@to_py.register(ISeq)
1✔
1564
@to_py.register(IPersistentVector)
1✔
1565
def _to_py_list(
1✔
1566
    o: Union[IPersistentList, ISeq, IPersistentVector],
1567
    keyword_fn: Callable[[kw.Keyword], Any] = _kw_name,
1568
) -> list:
1569
    return list(map(functools.partial(to_py, keyword_fn=keyword_fn), o))
1✔
1570

1571

1572
@to_py.register(IPersistentMap)
1✔
1573
def _to_py_map(
1✔
1574
    o: IPersistentMap, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
1575
) -> dict:
1576
    return {
1✔
1577
        to_py(key, keyword_fn=keyword_fn): to_py(value, keyword_fn=keyword_fn)
1578
        for key, value in o.items()
1579
    }
1580

1581

1582
@to_py.register(IPersistentSet)
1✔
1583
def _to_py_set(
1✔
1584
    o: IPersistentSet, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
1585
) -> set:
1586
    return set(to_py(e, keyword_fn=keyword_fn) for e in o)
1✔
1587

1588

1589
def lrepr(o, human_readable: bool = False) -> str:
1✔
1590
    """Produce a string representation of an object. If human_readable is False,
1591
    the string representation of Lisp objects is something that can be read back
1592
    in by the reader as the same object."""
1593
    core_ns = Namespace.get(CORE_NS_SYM)
1✔
1594
    assert core_ns is not None
1✔
1595
    return lobj.lrepr(
1✔
1596
        o,
1597
        human_readable=human_readable,
1598
        print_dup=core_ns.find(sym.symbol(PRINT_DUP_VAR_NAME)).value,  # type: ignore
1599
        print_length=core_ns.find(  # type: ignore
1600
            sym.symbol(PRINT_LENGTH_VAR_NAME)
1601
        ).value,
1602
        print_level=core_ns.find(  # type: ignore
1603
            sym.symbol(PRINT_LEVEL_VAR_NAME)
1604
        ).value,
1605
        print_meta=core_ns.find(sym.symbol(PRINT_META_VAR_NAME)).value,  # type: ignore
1606
        print_readably=core_ns.find(  # type: ignore
1607
            sym.symbol(PRINT_READABLY_VAR_NAME)
1608
        ).value,
1609
    )
1610

1611

1612
def lstr(o) -> str:
1✔
1613
    """Produce a human readable string representation of an object."""
1614
    return lrepr(o, human_readable=True)
1✔
1615

1616

1617
__NOT_COMPLETEABLE = re.compile(r"^[0-9].*")
1✔
1618

1619

1620
def repl_completions(text: str) -> Iterable[str]:
1✔
1621
    """Return an optional iterable of REPL completions."""
1622
    # Can't complete Keywords, Numerals
1623
    if __NOT_COMPLETEABLE.match(text):
1✔
1624
        return ()
×
1625
    elif text.startswith(":"):
1✔
1626
        return kw.complete(text)
×
1627
    else:
1628
        ns = get_current_ns()
1✔
1629
        return ns.complete(text)
1✔
1630

1631

1632
####################
1633
# Compiler Support #
1634
####################
1635

1636

1637
@functools.singledispatch
1✔
1638
def _collect_args(args) -> ISeq:
1✔
1639
    """Collect Python starred arguments into a Basilisp list."""
1640
    raise TypeError("Python variadic arguments should always be a tuple")
×
1641

1642

1643
@_collect_args.register(tuple)
1✔
1644
def _collect_args_tuple(args: tuple) -> ISeq:
1✔
1645
    return llist.list(args)
1✔
1646

1647

1648
class _TrampolineArgs:
1✔
1649
    __slots__ = ("_has_varargs", "_args", "_kwargs")
1✔
1650

1651
    def __init__(self, has_varargs: bool, *args, **kwargs) -> None:
1✔
1652
        self._has_varargs = has_varargs
1✔
1653
        self._args = args
1✔
1654
        self._kwargs = kwargs
1✔
1655

1656
    @property
1✔
1657
    def args(self) -> Tuple:
1✔
1658
        """Return the arguments for a trampolined function. If the function
1659
        that is being trampolined has varargs, unroll the final argument if
1660
        it is a sequence."""
1661
        if not self._has_varargs:
1✔
1662
            return self._args
1✔
1663

1664
        try:
1✔
1665
            final = self._args[-1]
1✔
1666
            if isinstance(final, ISeq):
1✔
1667
                inits = self._args[:-1]
1✔
1668
                return tuple(itertools.chain(inits, final))
1✔
1669
            return self._args
1✔
1670
        except IndexError:
1✔
1671
            return ()
1✔
1672

1673
    @property
1✔
1674
    def kwargs(self) -> Dict:
1✔
1675
        return self._kwargs
1✔
1676

1677

1678
def _trampoline(f):
1✔
1679
    """Trampoline a function repeatedly until it is finished recurring to help
1680
    avoid stack growth."""
1681

1682
    @functools.wraps(f)
1✔
1683
    def trampoline(*args, **kwargs):
1✔
1684
        while True:
1✔
1685
            ret = f(*args, **kwargs)
1✔
1686
            if isinstance(ret, _TrampolineArgs):
1✔
1687
                args = ret.args
1✔
1688
                kwargs = ret.kwargs
1✔
1689
                continue
1✔
1690
            return ret
1✔
1691

1692
    return trampoline
1✔
1693

1694

1695
def _lisp_fn_apply_kwargs(f):
1✔
1696
    """Convert a Python function into a Lisp function.
1697

1698
    Python keyword arguments will be converted into Lisp keyword/argument pairs
1699
    that can be easily understood by Basilisp.
1700

1701
    Lisp functions annotated with the `:apply` value for the `:kwargs` metadata key
1702
    will be wrapped with this decorator by the compiler."""
1703

1704
    @functools.wraps(f)
1✔
1705
    def wrapped_f(*args, **kwargs):
1✔
1706
        return f(
1✔
1707
            *args,
1708
            *itertools.chain.from_iterable(
1709
                (kw.keyword(demunge(k)), v) for k, v in kwargs.items()
1710
            ),
1711
        )
1712

1713
    return wrapped_f
1✔
1714

1715

1716
def _lisp_fn_collect_kwargs(f):
1✔
1717
    """Convert a Python function into a Lisp function.
1718

1719
    Python keyword arguments will be collected into a single map, which is supplied
1720
    as the final positional argument.
1721

1722
    Lisp functions annotated with the `:collect` value for the `:kwargs` metadata key
1723
    will be wrapped with this decorator by the compiler."""
1724

1725
    @functools.wraps(f)
1✔
1726
    def wrapped_f(*args, **kwargs):
1✔
1727
        return f(
1✔
1728
            *args,
1729
            lmap.map({kw.keyword(demunge(k)): v for k, v in kwargs.items()}),
1730
        )
1731

1732
    return wrapped_f
1✔
1733

1734

1735
def _with_attrs(**kwargs):
1✔
1736
    """Decorator to set attributes on a function. Returns the original
1737
    function after setting the attributes named by the keyword arguments."""
1738

1739
    def decorator(f):
1✔
1740
        for k, v in kwargs.items():
1✔
1741
            setattr(f, k, v)
1✔
1742
        return f
1✔
1743

1744
    return decorator
1✔
1745

1746

1747
def _fn_with_meta(f, meta: Optional[lmap.PersistentMap]):
1✔
1748
    """Return a new function with the given meta. If the function f already
1749
    has a meta map, then merge the new meta with the existing meta."""
1750

1751
    if not isinstance(meta, lmap.PersistentMap):
1✔
1752
        raise TypeError("meta must be a map")
1✔
1753

1754
    if inspect.iscoroutinefunction(f):
1✔
1755

1756
        @functools.wraps(f)
1✔
1757
        async def wrapped_f(*args, **kwargs):
1✔
1758
            return await f(*args, **kwargs)
1✔
1759

1760
    else:
1761

1762
        @functools.wraps(f)
1✔
1763
        def wrapped_f(*args, **kwargs):
1✔
1764
            return f(*args, **kwargs)
1✔
1765

1766
    wrapped_f.meta = (  # type: ignore
1✔
1767
        f.meta.update(meta)
1768
        if hasattr(f, "meta") and isinstance(f.meta, lmap.PersistentMap)
1769
        else meta
1770
    )
1771
    wrapped_f.with_meta = partial(_fn_with_meta, wrapped_f)  # type: ignore
1✔
1772
    return wrapped_f
1✔
1773

1774

1775
def _basilisp_fn(arities: Tuple[Union[int, kw.Keyword]]):
1✔
1776
    """Create a Basilisp function, setting meta and supplying a with_meta
1777
    method implementation."""
1778

1779
    def wrap_fn(f):
1✔
1780
        assert not hasattr(f, "meta")
1✔
1781
        f._basilisp_fn = True
1✔
1782
        f.arities = lset.set(arities)
1✔
1783
        f.meta = None
1✔
1784
        f.with_meta = partial(_fn_with_meta, f)
1✔
1785
        return f
1✔
1786

1787
    return wrap_fn
1✔
1788

1789

1790
def _basilisp_type(
1✔
1791
    fields: Iterable[str],
1792
    interfaces: Iterable[Type],
1793
    artificially_abstract_bases: AbstractSet[Type],
1794
    members: Iterable[str],
1795
):
1796
    """Check that a Basilisp type (defined by `deftype*`) only declares abstract
1797
    super-types and that all abstract methods are implemented."""
1798

1799
    def wrap_class(cls: Type):
1✔
1800
        field_names = frozenset(fields)
1✔
1801
        member_names = frozenset(members)
1✔
1802
        artificially_abstract_base_members: Set[str] = set()
1✔
1803
        all_member_names = field_names.union(member_names)
1✔
1804
        all_interface_methods: Set[str] = set()
1✔
1805
        for interface in interfaces:
1✔
1806
            if interface is object:
1✔
1807
                continue
1✔
1808

1809
            if is_abstract(interface):
1✔
1810
                interface_names: FrozenSet[str] = interface.__abstractmethods__
1✔
1811
                interface_property_names: FrozenSet[str] = frozenset(
1✔
1812
                    method
1813
                    for method in interface_names
1814
                    if isinstance(getattr(interface, method), property)
1815
                )
1816
                interface_method_names = interface_names - interface_property_names
1✔
1817
                if not interface_method_names.issubset(member_names):
1✔
1818
                    missing_methods = ", ".join(interface_method_names - member_names)
1✔
1819
                    raise RuntimeException(
1✔
1820
                        "deftype* definition missing interface members for interface "
1821
                        f"{interface}: {missing_methods}",
1822
                    )
1823
                elif not interface_property_names.issubset(all_member_names):
1✔
1824
                    missing_fields = ", ".join(interface_property_names - field_names)
1✔
1825
                    raise RuntimeException(
1✔
1826
                        "deftype* definition missing interface properties for interface "
1827
                        f"{interface}: {missing_fields}",
1828
                    )
1829

1830
                all_interface_methods.update(interface_names)
1✔
1831
            elif interface in artificially_abstract_bases:
1✔
1832
                artificially_abstract_base_members.update(
1✔
1833
                    map(
1834
                        lambda v: v[0],
1835
                        inspect.getmembers(
1836
                            interface,
1837
                            predicate=lambda v: inspect.isfunction(v)
1838
                            or isinstance(v, (property, staticmethod))
1839
                            or inspect.ismethod(v),
1840
                        ),
1841
                    )
1842
                )
1843
            else:
1844
                raise RuntimeException(
1✔
1845
                    "deftype* interface must be Python abstract class or object",
1846
                )
1847

1848
        extra_methods = member_names - all_interface_methods - OBJECT_DUNDER_METHODS
1✔
1849
        if extra_methods and not extra_methods.issubset(
1✔
1850
            artificially_abstract_base_members
1851
        ):
1852
            extra_method_str = ", ".join(extra_methods)
1✔
1853
            raise RuntimeException(
1✔
1854
                "deftype* definition for interface includes members not part of "
1855
                f"defined interfaces: {extra_method_str}"
1856
            )
1857

1858
        return cls
1✔
1859

1860
    return wrap_class
1✔
1861

1862

1863
###############################
1864
# Symbol and Alias Resolution #
1865
###############################
1866

1867

1868
def resolve_alias(s: sym.Symbol, ns: Optional[Namespace] = None) -> sym.Symbol:
1✔
1869
    """Resolve the aliased symbol in the current namespace."""
1870
    if s in _SPECIAL_FORMS:
1✔
1871
        return s
1✔
1872

1873
    ns = Maybe(ns).or_else(get_current_ns)
1✔
1874
    if s.ns is not None:
1✔
1875
        aliased_ns = ns.get_alias(sym.symbol(s.ns))
1✔
1876
        if aliased_ns is not None:
1✔
1877
            return sym.symbol(s.name, aliased_ns.name)
1✔
1878
        else:
1879
            return s
1✔
1880
    else:
1881
        which_var = ns.find(sym.symbol(s.name))
1✔
1882
        if which_var is not None:
1✔
1883
            return sym.symbol(which_var.name.name, which_var.ns.name)
1✔
1884
        else:
1885
            return sym.symbol(s.name, ns=ns.name)
1✔
1886

1887

1888
def resolve_var(s: sym.Symbol, ns: Optional[Namespace] = None) -> Optional[Var]:
1✔
1889
    """Resolve the aliased symbol to a Var from the specified namespace, or the
1890
    current namespace if none is specified."""
1891
    ns_qualified_sym = resolve_alias(s, ns)
1✔
1892
    return Var.find(resolve_alias(s, ns)) if ns_qualified_sym.ns else None
1✔
1893

1894

1895
#######################
1896
# Namespace Utilities #
1897
#######################
1898

1899

1900
@contextlib.contextmanager
1✔
1901
def bindings(bindings: Optional[Mapping[Var, Any]] = None):
1✔
1902
    """Context manager for temporarily changing the value thread-local value for
1903
    Basilisp dynamic Vars."""
1904
    m = lmap.map(bindings or {})
1✔
1905
    logger.debug(
1✔
1906
        f"Binding thread-local values for Vars: {', '.join(map(str, m.keys()))}"
1907
    )
1908
    try:
1✔
1909
        push_thread_bindings(m)
1✔
1910
        yield
1✔
1911
    finally:
1912
        pop_thread_bindings()
1✔
1913
        logger.debug(
1✔
1914
            f"Reset thread-local bindings for Vars: {', '.join(map(str, m.keys()))}"
1915
        )
1916

1917

1918
@contextlib.contextmanager
1✔
1919
def ns_bindings(
1✔
1920
    ns_name: str, module: Optional[BasilispModule] = None
1921
) -> Iterator[Namespace]:
1922
    """Context manager for temporarily changing the value of basilisp.core/*ns*."""
1923
    symbol = sym.symbol(ns_name)
1✔
1924
    ns = Namespace.get_or_create(symbol, module=module)
1✔
1925
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
1926
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
1927
    )
1928

1929
    with bindings({ns_var: ns}):
1✔
1930
        yield ns_var.value
1✔
1931

1932

1933
@contextlib.contextmanager
1✔
1934
def remove_ns_bindings():
1✔
1935
    """Context manager to pop the most recent bindings for basilisp.core/*ns* after
1936
    completion of the code under management."""
1937
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
1938
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
1939
    )
1940
    try:
1✔
1941
        yield
1✔
1942
    finally:
1943
        ns_var.pop_bindings()
1✔
1944
        logger.debug(f"Reset bindings for {NS_VAR_SYM} to {ns_var.value}")
1✔
1945

1946

1947
def get_current_ns() -> Namespace:
1✔
1948
    """Get the value of the dynamic variable `*ns*` in the current thread."""
1949
    ns: Namespace = (
1✔
1950
        Maybe(Var.find(NS_VAR_SYM))
1951
        .map(lambda v: v.value)
1952
        .or_else_raise(lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!"))
1953
    )
1954
    return ns
1✔
1955

1956

1957
def set_current_ns(
1✔
1958
    ns_name: str,
1959
    module: Optional[BasilispModule] = None,
1960
) -> Var:
1961
    """Set the value of the dynamic variable `*ns*` in the current thread."""
1962
    symbol = sym.symbol(ns_name)
1✔
1963
    ns = Namespace.get_or_create(symbol, module=module)
1✔
1964
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
1965
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
1966
    )
1967
    ns_var.push_bindings(ns)
1✔
1968
    logger.debug(f"Setting {NS_VAR_SYM} to {ns}")
1✔
1969
    return ns_var
1✔
1970

1971

1972
##############################
1973
# Emit Generated Python Code #
1974
##############################
1975

1976

1977
def add_generated_python(
1✔
1978
    generated_python: str,
1979
    which_ns: Optional[Namespace] = None,
1980
) -> None:
1981
    """Add generated Python code to a dynamic variable in which_ns."""
1982
    if which_ns is None:
1✔
1983
        which_ns = get_current_ns()
×
1984
    v = Maybe(which_ns.find(sym.symbol(GENERATED_PYTHON_VAR_NAME))).or_else(
1✔
1985
        lambda: Var.intern(
1986
            which_ns,  # type: ignore[arg-type, unused-ignore]
1987
            sym.symbol(GENERATED_PYTHON_VAR_NAME),
1988
            "",
1989
            dynamic=True,
1990
            meta=lmap.map({_PRIVATE_META_KEY: True}),
1991
        )
1992
    )
1993
    # Accessing the Var root via the property uses a lock, which is the
1994
    # desired behavior for Basilisp code, but it introduces additional
1995
    # startup time when there will not realistically be any contention.
1996
    v._root = v._root + generated_python  # type: ignore
1✔
1997

1998

1999
def print_generated_python() -> bool:
1✔
2000
    """Return the value of the `*print-generated-python*` dynamic variable."""
2001
    ns_sym = sym.symbol(PRINT_GENERATED_PY_VAR_NAME, ns=CORE_NS)
1✔
2002
    return (
1✔
2003
        Maybe(Var.find(ns_sym))
2004
        .map(lambda v: v.value)
2005
        .or_else_raise(lambda: RuntimeException(f"Dynamic Var {ns_sym} not bound!"))
2006
    )
2007

2008

2009
#########################
2010
# Bootstrap the Runtime #
2011
#########################
2012

2013

2014
def init_ns_var() -> Var:
1✔
2015
    """Initialize the dynamic `*ns*` variable in the `basilisp.core` Namespace."""
2016
    core_ns = Namespace.get_or_create(CORE_NS_SYM)
1✔
2017
    ns_var = Var.intern(
1✔
2018
        core_ns,
2019
        sym.symbol(NS_VAR_NAME),
2020
        core_ns,
2021
        dynamic=True,
2022
        meta=lmap.map(
2023
            {
2024
                _DOC_META_KEY: (
2025
                    "Pointer to the current namespace.\n\n"
2026
                    "This value is used by both the compiler and runtime to determine where "
2027
                    "newly defined Vars should be bound, so users should not alter or bind "
2028
                    "this Var unless they know what they're doing."
2029
                )
2030
            }
2031
        ),
2032
    )
2033
    logger.debug(f"Created namespace variable {NS_VAR_SYM}")
1✔
2034
    return ns_var
1✔
2035

2036

2037
def bootstrap_core(compiler_opts: CompilerOpts) -> None:
1✔
2038
    """Bootstrap the environment with functions that are either difficult to express
2039
    with the very minimal Lisp environment or which are expected by the compiler."""
2040
    _NS = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
2041
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
2042
    )
2043

2044
    def in_ns(s: sym.Symbol):
1✔
2045
        ns = Namespace.get_or_create(s)
1✔
2046
        _NS.set_value(ns)
1✔
2047
        return ns
1✔
2048

2049
    # Vars used in bootstrapping the runtime
2050
    Var.intern_unbound(
1✔
2051
        CORE_NS_SYM,
2052
        sym.symbol("unquote"),
2053
        meta=lmap.map(
2054
            {
2055
                _DOC_META_KEY: (
2056
                    "Placeholder Var so the compiler does not throw an error while syntax quoting.\n\n"
2057
                    "See :ref:`macros` and :ref:`syntax_quoting` for more details."
2058
                )
2059
            }
2060
        ),
2061
    )
2062
    Var.intern_unbound(
1✔
2063
        CORE_NS_SYM,
2064
        sym.symbol("unquote-splicing"),
2065
        meta=lmap.map(
2066
            {
2067
                _DOC_META_KEY: (
2068
                    "Placeholder Var so the compiler does not throw an error while syntax quoting.\n\n"
2069
                    "See :ref:`macros` and :ref:`syntax_quoting` for more details."
2070
                )
2071
            }
2072
        ),
2073
    )
2074
    Var.intern(
1✔
2075
        CORE_NS_SYM, sym.symbol("in-ns"), in_ns, meta=lmap.map({_REDEF_META_KEY: True})
2076
    )
2077

2078
    # Dynamic Var examined by the compiler when importing new Namespaces
2079
    Var.intern(
1✔
2080
        CORE_NS_SYM,
2081
        sym.symbol(COMPILER_OPTIONS_VAR_NAME),
2082
        compiler_opts,
2083
        dynamic=True,
2084
    )
2085

2086
    # Dynamic Var for introspecting the default reader featureset
2087
    Var.intern(
1✔
2088
        CORE_NS_SYM,
2089
        sym.symbol(DEFAULT_READER_FEATURES_VAR_NAME),
2090
        READER_COND_DEFAULT_FEATURE_SET,
2091
        dynamic=True,
2092
        meta=lmap.map(
2093
            {
2094
                _DOC_META_KEY: (
2095
                    "The set of all currently supported "
2096
                    ":ref:`reader features <reader_conditions>`."
2097
                )
2098
            }
2099
        ),
2100
    )
2101

2102
    # Dynamic Vars examined by the compiler for generating Python code for debugging
2103
    Var.intern(
1✔
2104
        CORE_NS_SYM,
2105
        sym.symbol(PRINT_GENERATED_PY_VAR_NAME),
2106
        False,
2107
        dynamic=True,
2108
        meta=lmap.map({_PRIVATE_META_KEY: True}),
2109
    )
2110
    Var.intern(
1✔
2111
        CORE_NS_SYM,
2112
        sym.symbol(GENERATED_PYTHON_VAR_NAME),
2113
        "",
2114
        dynamic=True,
2115
        meta=lmap.map({_PRIVATE_META_KEY: True}),
2116
    )
2117

2118
    # Dynamic Vars for controlling printing
2119
    Var.intern(
1✔
2120
        CORE_NS_SYM, sym.symbol(PRINT_DUP_VAR_NAME), lobj.PRINT_DUP, dynamic=True
2121
    )
2122
    Var.intern(
1✔
2123
        CORE_NS_SYM, sym.symbol(PRINT_LENGTH_VAR_NAME), lobj.PRINT_LENGTH, dynamic=True
2124
    )
2125
    Var.intern(
1✔
2126
        CORE_NS_SYM, sym.symbol(PRINT_LEVEL_VAR_NAME), lobj.PRINT_LEVEL, dynamic=True
2127
    )
2128
    Var.intern(
1✔
2129
        CORE_NS_SYM, sym.symbol(PRINT_META_VAR_NAME), lobj.PRINT_META, dynamic=True
2130
    )
2131
    Var.intern(
1✔
2132
        CORE_NS_SYM,
2133
        sym.symbol(PRINT_READABLY_VAR_NAME),
2134
        lobj.PRINT_READABLY,
2135
        dynamic=True,
2136
    )
2137

2138
    # Version info
2139
    Var.intern(
1✔
2140
        CORE_NS_SYM,
2141
        sym.symbol(PYTHON_VERSION_VAR_NAME),
2142
        vec.vector(sys.version_info),
2143
        dynamic=True,
2144
        meta=lmap.map(
2145
            {
2146
                _DOC_META_KEY: (
2147
                    "The current Python version as a vector of "
2148
                    "``[major, minor, revision]``."
2149
                )
2150
            }
2151
        ),
2152
    )
2153
    Var.intern(
1✔
2154
        CORE_NS_SYM,
2155
        sym.symbol(BASILISP_VERSION_VAR_NAME),
2156
        BASILISP_VERSION,
2157
        dynamic=True,
2158
        meta=lmap.map(
2159
            {
2160
                _DOC_META_KEY: (
2161
                    "The current Basilisp version as a vector of "
2162
                    "``[major, minor, revision]``."
2163
                )
2164
            }
2165
        ),
2166
    )
2167

2168

2169
def get_compiler_opts() -> CompilerOpts:
1✔
2170
    """Return the current compiler options map."""
2171
    v = Var.find_in_ns(CORE_NS_SYM, sym.symbol(COMPILER_OPTIONS_VAR_NAME))
1✔
2172
    assert v is not None, "*compiler-options* Var not defined"
1✔
2173
    return cast(CompilerOpts, v.value)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc