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

basilisp-lang / basilisp / 5697667142

29 Jul 2023 01:09AM UTC coverage: 99.159%. First build
5697667142

push

github

chrisrink10
Use Tox 4.0 run command

1634 of 1636 branches covered (99.88%)

Branch coverage included in aggregate %.

7686 of 7763 relevant lines covered (99.01%)

0.99 hits per line

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

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

34
from readerwriterlock.rwlock import RWLockFair
1✔
35

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

64
logger = logging.getLogger(__name__)
1✔
65

66
# Public constants
67
CORE_NS = "basilisp.core"
1✔
68
CORE_NS_SYM = sym.symbol(CORE_NS)
1✔
69
NS_VAR_NAME = "*ns*"
1✔
70
NS_VAR_SYM = sym.symbol(NS_VAR_NAME, ns=CORE_NS)
1✔
71
NS_VAR_NS = CORE_NS
1✔
72
REPL_DEFAULT_NS = "basilisp.user"
1✔
73
SUPPORTED_PYTHON_VERSIONS = frozenset({(3, 8), (3, 9), (3, 10), (3, 11)})
1✔
74

75
# Public basilisp.core symbol names
76
COMPILER_OPTIONS_VAR_NAME = "*compiler-options*"
1✔
77
DEFAULT_READER_FEATURES_VAR_NAME = "*default-reader-features*"
1✔
78
GENERATED_PYTHON_VAR_NAME = "*generated-python*"
1✔
79
PRINT_GENERATED_PY_VAR_NAME = "*print-generated-python*"
1✔
80
PRINT_DUP_VAR_NAME = "*print-dup*"
1✔
81
PRINT_LENGTH_VAR_NAME = "*print-length*"
1✔
82
PRINT_LEVEL_VAR_NAME = "*print-level*"
1✔
83
PRINT_META_VAR_NAME = "*print-meta*"
1✔
84
PRINT_READABLY_VAR_NAME = "*print-readably*"
1✔
85
PYTHON_VERSION_VAR_NAME = "*python-version*"
1✔
86
BASILISP_VERSION_VAR_NAME = "*basilisp-version*"
1✔
87

88
# Common meta keys
89
_DOC_META_KEY = kw.keyword("doc")
1✔
90
_DYNAMIC_META_KEY = kw.keyword("dynamic")
1✔
91
_PRIVATE_META_KEY = kw.keyword("private")
1✔
92
_REDEF_META_KEY = kw.keyword("redef")
1✔
93

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

144

145
# Reader Conditional default features
146
def _supported_python_versions_features() -> Iterable[kw.Keyword]:
1✔
147
    """Yield successive reader features corresponding to the various Python
148
    `major`.`minor` versions the current Python VM corresponds to amongst the
149
    set of supported Python versions.
150

151
    For example, for Python 3.6, we would emit:
152
     - :lpy36  - to exactly match Basilisp running on Python 3.6
153
     - :lpy36+ - to match Basilisp running on Python 3.6 and later versions
154
     - :lpy36- - to match Basilisp running on Python 3.6 and earlier versions
155
     - :lpy37- - to match Basilisp running on Python 3.7 and earlier versions
156
     - :lpy38- - to match Basilisp running on Python 3.8 and earlier versions"""
157
    feature_kw = lambda major, minor, suffix="": kw.keyword(
1✔
158
        f"lpy{major}{minor}{suffix}"
159
    )
160

161
    yield feature_kw(sys.version_info.major, sys.version_info.minor)
1✔
162

163
    current = (sys.version_info.major, sys.version_info.minor)
1✔
164
    for version in SUPPORTED_PYTHON_VERSIONS:
1✔
165
        if current <= version:
1✔
166
            yield feature_kw(version[0], version[1], suffix="-")
1✔
167
        if current >= version:
1✔
168
            yield feature_kw(version[0], version[1], suffix="+")
1✔
169

170

171
READER_COND_BASILISP_FEATURE_KW = kw.keyword("lpy")
1✔
172
READER_COND_DEFAULT_FEATURE_KW = kw.keyword("default")
1✔
173
READER_COND_DEFAULT_FEATURE_SET = lset.s(
1✔
174
    READER_COND_BASILISP_FEATURE_KW,
175
    READER_COND_DEFAULT_FEATURE_KW,
176
    *_supported_python_versions_features(),
177
)
178

179
CompletionMatcher = Callable[[Tuple[sym.Symbol, Any]], bool]
1✔
180
CompletionTrimmer = Callable[[Tuple[sym.Symbol, Any]], str]
1✔
181

182

183
class BasilispModule(types.ModuleType):
1✔
184
    __basilisp_namespace__: "Namespace"
1✔
185
    __basilisp_bootstrapped__: bool = False
1✔
186

187

188
def _new_module(name: str, doc=None) -> BasilispModule:
1✔
189
    """Create a new empty Basilisp Python module.
190
    Modules are created for each Namespace when it is created."""
191
    mod = BasilispModule(name, doc=doc)
1✔
192
    mod.__loader__ = None
1✔
193
    mod.__package__ = None
1✔
194
    mod.__spec__ = None
1✔
195
    mod.__basilisp_bootstrapped__ = False
1✔
196
    return mod
1✔
197

198

199
class RuntimeException(Exception):
1✔
200
    pass
1✔
201

202

203
class _VarBindings(threading.local):
1✔
204
    def __init__(self):
1✔
205
        self.bindings: List = []
1✔
206

207

208
class Unbound:
1✔
209
    __slots__ = ("var",)
1✔
210

211
    def __init__(self, v: "Var"):
1✔
212
        self.var = v
1✔
213

214
    def __repr__(self):  # pragma: no cover
215
        return f"Unbound(var={self.var})"
216

217
    def __eq__(self, other):
1✔
218
        return self is other or (isinstance(other, Unbound) and self.var == other.var)
1✔
219

220

221
class Var(RefBase):
1✔
222
    __slots__ = (
1✔
223
        "_name",
224
        "_ns",
225
        "_root",
226
        "_dynamic",
227
        "_is_bound",
228
        "_tl",
229
        "_meta",
230
        "_rlock",
231
        "_wlock",
232
        "_watches",
233
        "_validator",
234
    )
235

236
    def __init__(
1✔
237
        self,
238
        ns: "Namespace",
239
        name: sym.Symbol,
240
        dynamic: bool = False,
241
        meta: Optional[IPersistentMap] = None,
242
    ) -> None:
243
        self._ns = ns
1✔
244
        self._name = name
1✔
245
        self._root = Unbound(self)
1✔
246
        self._dynamic = dynamic
1✔
247
        self._is_bound = False
1✔
248
        self._tl = None
1✔
249
        self._meta = meta
1✔
250
        lock = RWLockFair()
1✔
251
        self._rlock = lock.gen_rlock()
1✔
252
        self._wlock = lock.gen_wlock()
1✔
253
        self._watches = lmap.PersistentMap.empty()
1✔
254
        self._validator = None
1✔
255

256
        if dynamic:
1✔
257
            self._tl = _VarBindings()
1✔
258

259
            # If this var was created with the dynamic keyword argument, then the
260
            # Var metadata should also specify that the Var is dynamic.
261
            if isinstance(self._meta, lmap.PersistentMap):
1✔
262
                if not self._meta.val_at(_DYNAMIC_META_KEY):
1✔
263
                    self._meta = self._meta.assoc(_DYNAMIC_META_KEY, True)
1✔
264
            else:
265
                self._meta = lmap.map({_DYNAMIC_META_KEY: True})
1✔
266

267
    def __repr__(self):
268
        return f"#'{self.ns.name}/{self.name}"
269

270
    @property
1✔
271
    def ns(self) -> "Namespace":
1✔
272
        return self._ns
1✔
273

274
    @property
1✔
275
    def name(self) -> sym.Symbol:
1✔
276
        return self._name
1✔
277

278
    @property
1✔
279
    def dynamic(self) -> bool:
1✔
280
        return self._dynamic
1✔
281

282
    def set_dynamic(self, dynamic: bool) -> None:
1✔
283
        with self._wlock:
1✔
284
            if dynamic == self._dynamic:
1✔
285
                return
1✔
286

287
            self._dynamic = dynamic
1✔
288
            self._tl = _VarBindings() if dynamic else None
1✔
289

290
    @property
1✔
291
    def is_private(self) -> Optional[bool]:
1✔
292
        if self._meta is not None:
1✔
293
            return self._meta.val_at(_PRIVATE_META_KEY)
1✔
294
        return False
1✔
295

296
    @property
1✔
297
    def is_bound(self) -> bool:
1✔
298
        return self._is_bound or self.is_thread_bound
1✔
299

300
    def _set_root(self, newval) -> None:
1✔
301
        # Private setter which does not include lock so it can be used
302
        # by other properties and methods which already manage the lock.
303
        oldval = self._root
1✔
304
        self._validate(newval)
1✔
305
        self._is_bound = True
1✔
306
        self._root = newval
1✔
307
        self._notify_watches(oldval, newval)
1✔
308

309
    @property
1✔
310
    def root(self):
1✔
311
        with self._rlock:
1✔
312
            return self._root
1✔
313

314
    def bind_root(self, val) -> None:
1✔
315
        """Atomically update the root binding of this Var to val."""
316
        with self._wlock:
1✔
317
            self._set_root(val)
1✔
318

319
    def alter_root(self, f, *args) -> None:
1✔
320
        """Atomically alter the root binding of this Var to the result of calling
321
        f with the existing root value and any additional arguments."""
322
        with self._wlock:
1✔
323
            self._set_root(f(self._root, *args))
1✔
324

325
    def push_bindings(self, val):
1✔
326
        if not self._dynamic or self._tl is None:
1✔
327
            raise RuntimeException("Can only push bindings to dynamic Vars")
1✔
328
        self._validate(val)
1✔
329
        self._tl.bindings.append(val)
1✔
330

331
    def pop_bindings(self):
1✔
332
        if not self._dynamic or self._tl is None:
1✔
333
            raise RuntimeException("Can only pop bindings from dynamic Vars")
1✔
334
        return self._tl.bindings.pop()
1✔
335

336
    def deref(self):
1✔
337
        return self.value
1✔
338

339
    @property
1✔
340
    def is_thread_bound(self):
1✔
341
        return bool(self._dynamic and self._tl and self._tl.bindings)
1✔
342

343
    @property
1✔
344
    def value(self):
1✔
345
        """Return the current value of the Var visible in the current thread.
346

347
        For non-dynamic Vars, this will just be the root. For dynamic Vars, this will
348
        be any thread-local binding if one is defined. Otherwise, the root value."""
349
        with self._rlock:
1✔
350
            if self._dynamic:
1✔
351
                assert self._tl is not None
1✔
352
                if len(self._tl.bindings) > 0:
1✔
353
                    return self._tl.bindings[-1]
1✔
354
            return self._root
1✔
355

356
    def set_value(self, v) -> None:
1✔
357
        """Set the current value of the Var.
358

359
        If the Var is not dynamic, this is equivalent to binding the root value. If the
360
        Var is dynamic, this will set the thread-local bindings for the Var."""
361
        with self._wlock:
1✔
362
            if self._dynamic:
1✔
363
                assert self._tl is not None
1✔
364
                self._validate(v)
1✔
365
                if len(self._tl.bindings) > 0:
1✔
366
                    self._tl.bindings[-1] = v
1✔
367
                else:
368
                    self.push_bindings(v)
1✔
369
                return
1✔
370
            self._set_root(v)
1✔
371

372
    __UNBOUND_SENTINEL = object()
1✔
373

374
    @classmethod
1✔
375
    def intern(  # pylint: disable=too-many-arguments
1✔
376
        cls,
377
        ns: Union["Namespace", sym.Symbol],
378
        name: sym.Symbol,
379
        val,
380
        dynamic: bool = False,
381
        meta: Optional[IPersistentMap] = None,
382
    ) -> "Var":
383
        """Intern the value bound to the symbol `name` in namespace `ns`.
384

385
        If the Var already exists, it will have its root value updated to `val`,
386
        its meta will be reset to match the provided `meta`, and the dynamic flag
387
        will be updated to match the provided `dynamic` argument."""
388
        if isinstance(ns, sym.Symbol):
1✔
389
            ns = Namespace.get_or_create(ns)
1✔
390
        new_var = cls(ns, name, dynamic=dynamic, meta=meta)
1✔
391
        var = ns.intern(name, new_var)
1✔
392
        if val is not cls.__UNBOUND_SENTINEL:
1✔
393
            var.bind_root(val)
1✔
394
        # Namespace.intern will return an existing interned Var for the same name
395
        # if one exists. We only want to set the meta and/or dynamic flag if the
396
        # Var is not new.
397
        if var is not new_var:
1✔
398
            var.reset_meta(meta)
1✔
399
            var.set_dynamic(dynamic)
1✔
400
        return var
1✔
401

402
    @classmethod
1✔
403
    def intern_unbound(
1✔
404
        cls,
405
        ns: Union["Namespace", sym.Symbol],
406
        name: sym.Symbol,
407
        dynamic: bool = False,
408
        meta: Optional[IPersistentMap] = None,
409
    ) -> "Var":
410
        """Create a new unbound `Var` instance to the symbol `name` in namespace `ns`."""
411
        return cls.intern(ns, name, cls.__UNBOUND_SENTINEL, dynamic=dynamic, meta=meta)
1✔
412

413
    @staticmethod
1✔
414
    def find_in_ns(
1✔
415
        ns_or_sym: Union["Namespace", sym.Symbol], name_sym: sym.Symbol
416
    ) -> "Optional[Var]":
417
        """Return the value current bound to the name `name_sym` in the namespace
418
        specified by `ns_sym`."""
419
        ns = (
1✔
420
            Namespace.get(ns_or_sym) if isinstance(ns_or_sym, sym.Symbol) else ns_or_sym
421
        )
422
        if ns is not None:
1✔
423
            return ns.find(name_sym)
1✔
424
        return None
1✔
425

426
    @classmethod
1✔
427
    def find(cls, ns_qualified_sym: sym.Symbol) -> "Optional[Var]":
1✔
428
        """Return the value currently bound to the name in the namespace specified
429
        by `ns_qualified_sym`."""
430
        ns = Maybe(ns_qualified_sym.ns).or_else_raise(
1✔
431
            lambda: ValueError(
432
                f"Namespace must be specified in Symbol {ns_qualified_sym}"
433
            )
434
        )
435
        ns_sym = sym.symbol(ns)
1✔
436
        name_sym = sym.symbol(ns_qualified_sym.name)
1✔
437
        return cls.find_in_ns(ns_sym, name_sym)
1✔
438

439
    @classmethod
1✔
440
    def find_safe(cls, ns_qualified_sym: sym.Symbol) -> "Var":
1✔
441
        """Return the Var currently bound to the name in the namespace specified
442
        by `ns_qualified_sym`. If no Var is bound to that name, raise an exception.
443

444
        This is a utility method to return useful debugging information when code
445
        refers to an invalid symbol at runtime."""
446
        v = cls.find(ns_qualified_sym)
1✔
447
        if v is None:
1✔
448
            raise RuntimeException(
1✔
449
                f"Unable to resolve symbol {ns_qualified_sym} in this context"
450
            )
451
        return v
1✔
452

453

454
Frame = IPersistentSet[Var]
1✔
455
FrameStack = IPersistentStack[Frame]
1✔
456

457

458
class _ThreadBindings(threading.local):
1✔
459
    def __init__(self):
1✔
460
        self._bindings: FrameStack = vec.PersistentVector.empty()
1✔
461

462
    def get_bindings(self) -> FrameStack:
1✔
463
        return self._bindings
1✔
464

465
    def push_bindings(self, frame: Frame) -> None:
1✔
466
        self._bindings = self._bindings.cons(frame)
1✔
467

468
    def pop_bindings(self) -> Frame:
1✔
469
        frame = self._bindings.peek()
1✔
470
        self._bindings = self._bindings.pop()
1✔
471
        assert frame is not None
1✔
472
        return frame
1✔
473

474

475
_THREAD_BINDINGS = _ThreadBindings()
1✔
476

477

478
AliasMap = lmap.PersistentMap[sym.Symbol, sym.Symbol]
1✔
479
Module = Union[BasilispModule, types.ModuleType]
1✔
480
ModuleMap = lmap.PersistentMap[sym.Symbol, Module]
1✔
481
NamespaceMap = lmap.PersistentMap[sym.Symbol, "Namespace"]
1✔
482
VarMap = lmap.PersistentMap[sym.Symbol, Var]
1✔
483

484

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

499
    - `imports` is a mapping of names to Python modules imported
500
      into the current namespace.
501

502
    - `interns` is a mapping between a symbolic name and a Var. The
503
      Var may point to code, data, or nothing, if it is unbound. Vars
504
      in `interns` are interned in _this_ namespace.
505

506
    - `refers` is a mapping between a symbolic name and a Var. Vars in
507
      `refers` are interned in another namespace and are only referred
508
      to without an alias in this namespace.
509
    """
510

511
    DEFAULT_IMPORTS = lset.set(
1✔
512
        map(
513
            sym.symbol,
514
            [
515
                "attr",
516
                "builtins",
517
                "functools",
518
                "io",
519
                "importlib",
520
                "operator",
521
                "sys",
522
                "basilisp.lang.atom",
523
                "basilisp.lang.compiler",
524
                "basilisp.lang.delay",
525
                "basilisp.lang.exception",
526
                "basilisp.lang.futures",
527
                "basilisp.lang.interfaces",
528
                "basilisp.lang.keyword",
529
                "basilisp.lang.list",
530
                "basilisp.lang.map",
531
                "basilisp.lang.multifn",
532
                "basilisp.lang.promise",
533
                "basilisp.lang.queue",
534
                "basilisp.lang.reader",
535
                "basilisp.lang.reduced",
536
                "basilisp.lang.runtime",
537
                "basilisp.lang.seq",
538
                "basilisp.lang.set",
539
                "basilisp.lang.symbol",
540
                "basilisp.lang.vector",
541
                "basilisp.lang.volatile",
542
                "basilisp.lang.util",
543
            ],
544
        )
545
    )
546

547
    _NAMESPACES: Atom[NamespaceMap] = Atom(lmap.PersistentMap.empty())
1✔
548

549
    __slots__ = (
1✔
550
        "_name",
551
        "_module",
552
        "_meta",
553
        "_rlock",
554
        "_wlock",
555
        "_interns",
556
        "_refers",
557
        "_aliases",
558
        "_imports",
559
        "_import_aliases",
560
    )
561

562
    def __init__(
1✔
563
        self, name: sym.Symbol, module: Optional[BasilispModule] = None
564
    ) -> None:
565
        self._name = name
1✔
566
        self._module = Maybe(module).or_else(lambda: _new_module(name.as_python_sym()))
1✔
567

568
        self._meta: Optional[IPersistentMap] = None
1✔
569
        lock = RWLockFair()
1✔
570
        self._rlock = lock.gen_rlock()
1✔
571
        self._wlock = lock.gen_wlock()
1✔
572

573
        self._aliases: NamespaceMap = lmap.PersistentMap.empty()
1✔
574
        self._imports: ModuleMap = lmap.map(
1✔
575
            dict(
576
                map(
577
                    lambda s: (s, importlib.import_module(s.name)),
578
                    Namespace.DEFAULT_IMPORTS,
579
                )
580
            )
581
        )
582
        self._import_aliases: AliasMap = lmap.PersistentMap.empty()
1✔
583
        self._interns: VarMap = lmap.PersistentMap.empty()
1✔
584
        self._refers: VarMap = lmap.PersistentMap.empty()
1✔
585

586
    @property
1✔
587
    def name(self) -> str:
1✔
588
        return self._name.name
1✔
589

590
    @property
1✔
591
    def module(self) -> BasilispModule:
1✔
592
        return self._module
1✔
593

594
    @module.setter
1✔
595
    def module(self, m: BasilispModule):
1✔
596
        """Override the Python module for this Namespace.
597

598
        ***WARNING**
599
        This should only be done by basilisp.importer code to make sure the
600
        correct module is generated for `basilisp.core`."""
601
        self._module = m
1✔
602

603
    @property
1✔
604
    def aliases(self) -> NamespaceMap:
1✔
605
        """A mapping between a symbolic alias and another Namespace. The
606
        fully qualified name of a namespace is also an alias for itself."""
607
        with self._rlock:
1✔
608
            return self._aliases
1✔
609

610
    @property
1✔
611
    def imports(self) -> ModuleMap:
1✔
612
        """A mapping of names to Python modules imported into the current
613
        namespace."""
614
        with self._rlock:
1✔
615
            return self._imports
1✔
616

617
    @property
1✔
618
    def import_aliases(self) -> AliasMap:
1✔
619
        """A mapping of a symbolic alias and a Python module name."""
620
        with self._rlock:
1✔
621
            return self._import_aliases
1✔
622

623
    @property
1✔
624
    def interns(self) -> VarMap:
1✔
625
        """A mapping between a symbolic name and a Var. The Var may point to
626
        code, data, or nothing, if it is unbound. Vars in `interns` are
627
        interned in _this_ namespace."""
628
        with self._rlock:
1✔
629
            return self._interns
1✔
630

631
    @property
1✔
632
    def refers(self) -> VarMap:
1✔
633
        """A mapping between a symbolic name and a Var. Vars in refers are
634
        interned in another namespace and are only referred to without an
635
        alias in this namespace."""
636
        with self._rlock:
1✔
637
            return self._refers
1✔
638

639
    def __repr__(self):
640
        return f"{self._name}"
641

642
    def __hash__(self):
1✔
643
        return hash(self._name)
1✔
644

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

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

665
    def add_alias(self, namespace: "Namespace", *aliases: sym.Symbol) -> None:
1✔
666
        """Add Symbol aliases for the given Namespace."""
667
        with self._wlock:
1✔
668
            new_m = self._aliases
1✔
669
            for alias in aliases:
1✔
670
                new_m = new_m.assoc(alias, namespace)
1✔
671
            self._aliases = new_m
1✔
672

673
    def get_alias(self, alias: sym.Symbol) -> "Optional[Namespace]":
1✔
674
        """Get the Namespace aliased by Symbol or None if it does not exist."""
675
        with self._rlock:
1✔
676
            return self._aliases.val_at(alias, None)
1✔
677

678
    def remove_alias(self, alias: sym.Symbol) -> None:
1✔
679
        """Remove the Namespace aliased by Symbol. Return None."""
680
        with self._wlock:
1✔
681
            self._aliases = self._aliases.dissoc(alias)
1✔
682

683
    def intern(self, sym: sym.Symbol, var: Var, force: bool = False) -> Var:
1✔
684
        """Intern the Var given in this namespace mapped by the given Symbol.
685
        If the Symbol already maps to a Var, this method _will not overwrite_
686
        the existing Var mapping unless the force keyword argument is given
687
        and is True."""
688
        with self._wlock:
1✔
689
            old_var = self._interns.val_at(sym, None)
1✔
690
            if old_var is None or force:
1✔
691
                self._interns = self._interns.assoc(sym, var)
1✔
692
            return self._interns.val_at(sym)
1✔
693

694
    def unmap(self, sym: sym.Symbol) -> None:
1✔
695
        with self._wlock:
1✔
696
            self._interns = self._interns.dissoc(sym)
1✔
697

698
    def find(self, sym: sym.Symbol) -> Optional[Var]:
1✔
699
        """Find Vars mapped by the given Symbol input or None if no Vars are
700
        mapped by that Symbol."""
701
        with self._rlock:
1✔
702
            v = self._interns.val_at(sym, None)
1✔
703
            if v is None:
1✔
704
                return self._refers.val_at(sym, None)
1✔
705
            return v
1✔
706

707
    def add_import(self, sym: sym.Symbol, module: Module, *aliases: sym.Symbol) -> None:
1✔
708
        """Add the Symbol as an imported Symbol in this Namespace. If aliases are given,
709
        the aliases will be applied to the"""
710
        with self._wlock:
1✔
711
            self._imports = self._imports.assoc(sym, module)
1✔
712
            if aliases:
1✔
713
                m = self._import_aliases
1✔
714
                for alias in aliases:
1✔
715
                    m = m.assoc(alias, sym)
1✔
716
                self._import_aliases = m
1✔
717

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

722
        First try to resolve a module directly with the given name. If no module
723
        can be resolved, attempt to resolve the module using import aliases."""
724
        with self._rlock:
1✔
725
            mod = self._imports.val_at(sym, None)
1✔
726
            if mod is None:
1✔
727
                alias = self._import_aliases.get(sym, None)
1✔
728
                if alias is None:
1✔
729
                    return None
1✔
730
                return self._imports.val_at(alias, None)
1✔
731
            return mod
1✔
732

733
    def add_refer(self, sym: sym.Symbol, var: Var) -> None:
1✔
734
        """Refer var in this namespace under the name sym."""
735
        if not var.is_private:
1✔
736
            with self._wlock:
1✔
737
                self._refers = self._refers.assoc(sym, var)
1✔
738

739
    def get_refer(self, sym: sym.Symbol) -> Optional[Var]:
1✔
740
        """Get the Var referred by Symbol or None if it does not exist."""
741
        with self._rlock:
1✔
742
            return self._refers.val_at(sym, None)
1✔
743

744
    def refer_all(self, other_ns: "Namespace") -> None:
1✔
745
        """Refer all the Vars in the other namespace."""
746
        with self._wlock:
1✔
747
            final_refers = self._refers
1✔
748
            for s, var in other_ns.interns.items():
1✔
749
                if not var.is_private:
1✔
750
                    final_refers = final_refers.assoc(s, var)
1✔
751
            self._refers = final_refers
1✔
752

753
    @classmethod
1✔
754
    def ns_cache(cls) -> lmap.PersistentMap:
1✔
755
        """Return a snapshot of the Namespace cache."""
756
        return cls._NAMESPACES.deref()
×
757

758
    @staticmethod
1✔
759
    def __get_or_create(
1✔
760
        ns_cache: NamespaceMap,
761
        name: sym.Symbol,
762
        module: Optional[BasilispModule] = None,
763
    ) -> lmap.PersistentMap:
764
        """Private swap function used by `get_or_create` to atomically swap
765
        the new namespace map into the global cache."""
766
        ns = ns_cache.val_at(name, None)
1✔
767
        if ns is not None:
1✔
768
            return ns_cache
1✔
769
        new_ns = Namespace(name, module=module)
1✔
770
        return ns_cache.assoc(name, new_ns)
1✔
771

772
    @classmethod
1✔
773
    def get_or_create(
1✔
774
        cls, name: sym.Symbol, module: Optional[BasilispModule] = None
775
    ) -> "Namespace":
776
        """Get the namespace bound to the symbol `name` in the global namespace
777
        cache, creating it if it does not exist.
778
        Return the namespace."""
779
        return cls._NAMESPACES.swap(Namespace.__get_or_create, name, module=module)[
1✔
780
            name
781
        ]
782

783
    @classmethod
1✔
784
    def get(cls, name: sym.Symbol) -> "Optional[Namespace]":
1✔
785
        """Get the namespace bound to the symbol `name` in the global namespace
786
        cache. Return the namespace if it exists or None otherwise.."""
787
        return cls._NAMESPACES.deref().val_at(name, None)
1✔
788

789
    @classmethod
1✔
790
    def remove(cls, name: sym.Symbol) -> Optional["Namespace"]:
1✔
791
        """Remove the namespace bound to the symbol `name` in the global
792
        namespace cache and return that namespace.
793
        Return None if the namespace did not exist in the cache."""
794
        if name == CORE_NS_SYM:
1✔
795
            raise ValueError("Cannot remove the Basilisp core namespace")
1✔
796
        while True:
1✔
797
            oldval: lmap.PersistentMap = cls._NAMESPACES.deref()
1✔
798
            ns: Optional[Namespace] = oldval.val_at(name, None)
1✔
799
            newval = oldval
1✔
800
            if ns is not None:
1✔
801
                newval = oldval.dissoc(name)
1✔
802
            if cls._NAMESPACES.compare_and_set(oldval, newval):
1✔
803
                return ns
1✔
804

805
    # REPL Completion support
806

807
    @staticmethod
1✔
808
    def __completion_matcher(text: str) -> CompletionMatcher:
1✔
809
        """Return a function which matches any symbol keys from map entries
810
        against the given text."""
811

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

815
        return is_match
1✔
816

817
    def __complete_alias(
1✔
818
        self, prefix: str, name_in_ns: Optional[str] = None
819
    ) -> Iterable[str]:
820
        """Return an iterable of possible completions matching the given
821
        prefix from the list of aliased namespaces. If name_in_ns is given,
822
        further attempt to refine the list to matching names in that namespace."""
823
        candidates = filter(
1✔
824
            Namespace.__completion_matcher(prefix),
825
            ((s, n) for s, n in self.aliases.items()),
826
        )
827
        if name_in_ns is not None:
1✔
828
            for _, candidate_ns in candidates:
1✔
829
                for match in candidate_ns.__complete_interns(
1✔
830
                    name_in_ns, include_private_vars=False
831
                ):
832
                    yield f"{prefix}/{match}"
1✔
833
        else:
834
            for alias, _ in candidates:
1✔
835
                yield f"{alias}/"
1✔
836

837
    def __complete_imports_and_aliases(
1✔
838
        self, prefix: str, name_in_module: Optional[str] = None
839
    ) -> Iterable[str]:
840
        """Return an iterable of possible completions matching the given
841
        prefix from the list of imports and aliased imports. If name_in_module
842
        is given, further attempt to refine the list to matching names in that
843
        namespace."""
844
        imports = self.imports
1✔
845
        aliases = lmap.map(
1✔
846
            {
847
                alias: imports.val_at(import_name)
848
                for alias, import_name in self.import_aliases.items()
849
            }
850
        )
851

852
        candidates = filter(
1✔
853
            Namespace.__completion_matcher(prefix),
854
            itertools.chain(aliases.items(), imports.items()),
855
        )
856
        if name_in_module is not None:
1✔
857
            for _, module in candidates:
1✔
858
                for name in module.__dict__:
1✔
859
                    if name.startswith(name_in_module):
1✔
860
                        yield f"{prefix}/{name}"
1✔
861
        else:
862
            for candidate_name, _ in candidates:
1✔
863
                yield f"{candidate_name}/"
1✔
864

865
    def __complete_interns(
1✔
866
        self, value: str, include_private_vars: bool = True
867
    ) -> Iterable[str]:
868
        """Return an iterable of possible completions matching the given
869
        prefix from the list of interned Vars."""
870
        if include_private_vars:
1✔
871
            is_match = Namespace.__completion_matcher(value)
1✔
872
        else:
873
            _is_match = Namespace.__completion_matcher(value)
1✔
874

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

878
        return map(
1✔
879
            lambda entry: f"{entry[0].name}",
880
            filter(is_match, ((s, v) for s, v in self.interns.items())),
881
        )
882

883
    def __complete_refers(self, value: str) -> Iterable[str]:
1✔
884
        """Return an iterable of possible completions matching the given
885
        prefix from the list of referred Vars."""
886
        return map(
1✔
887
            lambda entry: f"{entry[0].name}",
888
            filter(
889
                Namespace.__completion_matcher(value),
890
                ((s, v) for s, v in self.refers.items()),
891
            ),
892
        )
893

894
    def complete(self, text: str) -> Iterable[str]:
1✔
895
        """Return an iterable of possible completions for the given text in
896
        this namespace."""
897
        assert not text.startswith(":")
1✔
898

899
        if "/" in text:
1✔
900
            prefix, suffix = text.split("/", maxsplit=1)
1✔
901
            results = itertools.chain(
1✔
902
                self.__complete_alias(prefix, name_in_ns=suffix),
903
                self.__complete_imports_and_aliases(prefix, name_in_module=suffix),
904
            )
905
        else:
906
            results = itertools.chain(
1✔
907
                self.__complete_alias(text),
908
                self.__complete_imports_and_aliases(text),
909
                self.__complete_interns(text),
910
                self.__complete_refers(text),
911
            )
912

913
        return results
1✔
914

915

916
def get_thread_bindings() -> IPersistentMap[Var, Any]:
1✔
917
    """Return the current thread-local bindings."""
918
    bindings = {}
1✔
919
    for frame in _THREAD_BINDINGS.get_bindings():
1✔
920
        bindings.update({var: var.value for var in frame})
1✔
921
    return lmap.map(bindings)
1✔
922

923

924
def push_thread_bindings(m: IPersistentMap[Var, Any]) -> None:
1✔
925
    """Push thread local bindings for the Var keys in m using the values."""
926
    bindings = set()
1✔
927

928
    for var, val in m.items():
1✔
929
        if not var.dynamic:
1✔
930
            raise RuntimeException(
1✔
931
                "cannot set thread-local bindings for non-dynamic Var"
932
            )
933
        var.push_bindings(val)
1✔
934
        bindings.add(var)
1✔
935

936
    _THREAD_BINDINGS.push_bindings(lset.set(bindings))
1✔
937

938

939
def pop_thread_bindings() -> None:
1✔
940
    """Pop the thread local bindings set by push_thread_bindings above."""
941
    try:
1✔
942
        bindings = _THREAD_BINDINGS.pop_bindings()
1✔
943
    except IndexError as e:
1✔
944
        raise RuntimeException(
1✔
945
            "cannot pop thread-local bindings without prior push"
946
        ) from e
947

948
    for var in bindings:
1✔
949
        var.pop_bindings()
1✔
950

951

952
###################
953
# Runtime Support #
954
###################
955

956
T = TypeVar("T")
1✔
957

958

959
@functools.singledispatch
1✔
960
def first(o):
1✔
961
    """If o is a ISeq, return the first element from o. If o is None, return
962
    None. Otherwise, coerces o to a Seq and returns the first."""
963
    s = to_seq(o)
1✔
964
    if s is None:
1✔
965
        return None
1✔
966
    return s.first
1✔
967

968

969
@first.register(type(None))
1✔
970
def _first_none(_: None) -> None:
1✔
971
    return None
1✔
972

973

974
@first.register(ISeq)
1✔
975
def _first_iseq(o: ISeq[T]) -> Optional[T]:
1✔
976
    return o.first
1✔
977

978

979
@functools.singledispatch
1✔
980
def rest(o) -> ISeq:
1✔
981
    """If o is a ISeq, return the elements after the first in o. If o is None,
982
    returns an empty seq. Otherwise, coerces o to a seq and returns the rest."""
983
    n = to_seq(o)
1✔
984
    if n is None:
1✔
985
        return lseq.EMPTY
1✔
986
    return n.rest
1✔
987

988

989
@rest.register(type(None))
1✔
990
def _rest_none(_: None) -> ISeq:
1✔
991
    return lseq.EMPTY
1✔
992

993

994
@rest.register(type(ISeq))
1✔
995
def _rest_iseq(o: ISeq[T]) -> ISeq:
1✔
996
    s = o.rest
×
997
    if s is None:
×
998
        return lseq.EMPTY
×
999
    return s
×
1000

1001

1002
def nthrest(coll, i: int):
1✔
1003
    """Returns the nth rest sequence of coll, or coll if i is 0."""
1004
    while True:
1✔
1005
        if coll is None:
1✔
1006
            return None
1✔
1007
        if i == 0:
1✔
1008
            return coll
1✔
1009
        i -= 1
1✔
1010
        coll = rest(coll)
1✔
1011

1012

1013
def next_(o) -> Optional[ISeq]:
1✔
1014
    """Calls rest on o. If o returns an empty sequence or None, returns None.
1015
    Otherwise, returns the elements after the first in o."""
1016
    return to_seq(rest(o))
1✔
1017

1018

1019
def nthnext(coll, i: int) -> Optional[ISeq]:
1✔
1020
    """Returns the nth next sequence of coll."""
1021
    while True:
1✔
1022
        if coll is None:
1✔
1023
            return None
1✔
1024
        if i == 0:
1✔
1025
            return to_seq(coll)
1✔
1026
        i -= 1
1✔
1027
        coll = next_(coll)
1✔
1028

1029

1030
@functools.singledispatch
1✔
1031
def _cons(seq, o) -> ISeq:
1✔
1032
    return Maybe(to_seq(seq)).map(lambda s: s.cons(o)).or_else(lambda: llist.l(o))
1✔
1033

1034

1035
@_cons.register(type(None))
1✔
1036
def _cons_none(_: None, o) -> ISeq:
1✔
1037
    return llist.l(o)
1✔
1038

1039

1040
@_cons.register(ISeq)
1✔
1041
def _cons_iseq(seq: ISeq, o) -> ISeq:
1✔
1042
    return seq.cons(o)
1✔
1043

1044

1045
def cons(o, seq) -> ISeq:
1✔
1046
    """Creates a new sequence where o is the first element and seq is the rest.
1047
    If seq is None, return a list containing o. If seq is not a ISeq, attempt
1048
    to coerce it to a ISeq and then cons o onto the resulting sequence."""
1049
    return _cons(seq, o)
1✔
1050

1051

1052
to_seq = lseq.to_seq
1✔
1053

1054

1055
def concat(*seqs) -> ISeq:
1✔
1056
    """Concatenate the sequences given by seqs into a single ISeq."""
1057
    allseqs = lseq.sequence(itertools.chain.from_iterable(filter(None, seqs)))
1✔
1058
    if allseqs is None:
1✔
1059
        return lseq.EMPTY
×
1060
    return allseqs
1✔
1061

1062

1063
def apply(f, args):
1✔
1064
    """Apply function f to the arguments provided.
1065
    The last argument must always be coercible to a Seq. Intermediate
1066
    arguments are not modified.
1067
    For example:
1068
        (apply max [1 2 3])   ;=> 3
1069
        (apply max 4 [1 2 3]) ;=> 4"""
1070
    final = list(args[:-1])
1✔
1071

1072
    try:
1✔
1073
        last = args[-1]
1✔
1074
    except TypeError as e:
×
1075
        logger.debug("Ignored %s: %s", type(e).__name__, e)
×
1076

1077
    s = to_seq(last)
1✔
1078
    if s is not None:
1✔
1079
        final.extend(s)
1✔
1080

1081
    return f(*final)
1✔
1082

1083

1084
def apply_kw(f, args):
1✔
1085
    """Apply function f to the arguments provided.
1086
    The last argument must always be coercible to a Mapping. Intermediate
1087
    arguments are not modified.
1088
    For example:
1089
        (apply python/dict {:a 1} {:b 2})   ;=> #py {:a 1 :b 2}
1090
        (apply python/dict {:a 1} {:a 2})   ;=> #py {:a 2}"""
1091
    final = list(args[:-1])
1✔
1092

1093
    try:
1✔
1094
        last = args[-1]
1✔
1095
    except TypeError as e:
×
1096
        logger.debug("Ignored %s: %s", type(e).__name__, e)
×
1097

1098
    kwargs = {
1✔
1099
        to_py(k, lambda kw: munge(kw.name, allow_builtins=True)): v
1100
        for k, v in last.items()
1101
    }
1102
    return f(*final, **kwargs)
1✔
1103

1104

1105
def count(coll) -> int:
1✔
1106
    try:
1✔
1107
        return len(coll)
1✔
1108
    except (AttributeError, TypeError):
1✔
1109
        try:
1✔
1110
            return sum(1 for _ in coll)
1✔
1111
        except TypeError as e:
×
1112
            raise TypeError(
×
1113
                f"count not supported on object of type {type(coll)}"
1114
            ) from e
1115

1116

1117
__nth_sentinel = object()
1✔
1118

1119

1120
@functools.singledispatch
1✔
1121
def nth(coll, i: int, notfound=__nth_sentinel):
1✔
1122
    """Returns the ith element of coll (0-indexed), if it exists.
1123
    None otherwise. If i is out of bounds, throws an IndexError unless
1124
    notfound is specified."""
1125
    raise TypeError(f"nth not supported on object of type {type(coll)}")
1✔
1126

1127

1128
@nth.register(type(None))
1✔
1129
def _nth_none(_: None, i: int, notfound=__nth_sentinel) -> None:
1✔
1130
    return notfound if notfound is not __nth_sentinel else None
1✔
1131

1132

1133
@nth.register(Sequence)
1✔
1134
def _nth_sequence(coll: Sequence, i: int, notfound=__nth_sentinel):
1✔
1135
    try:
1✔
1136
        return coll[i]
1✔
1137
    except IndexError as ex:
1✔
1138
        if notfound is not __nth_sentinel:
1✔
1139
            return notfound
1✔
1140
        raise ex
1✔
1141

1142

1143
@nth.register(ISeq)
1✔
1144
def _nth_iseq(coll: ISeq, i: int, notfound=__nth_sentinel):
1✔
1145
    for j, e in enumerate(coll):
1✔
1146
        if i == j:
1✔
1147
            return e
1✔
1148

1149
    if notfound is not __nth_sentinel:
1✔
1150
        return notfound
1✔
1151

1152
    raise IndexError(f"Index {i} out of bounds")
1✔
1153

1154

1155
@functools.singledispatch
1✔
1156
def contains(coll, k):
1✔
1157
    """Return true if o contains the key k."""
1158
    return k in coll
1✔
1159

1160

1161
@contains.register(type(None))
1✔
1162
def _contains_none(_, __):
1✔
1163
    return False
1✔
1164

1165

1166
@contains.register(IAssociative)
1✔
1167
def _contains_iassociative(coll, k):
1✔
1168
    return coll.contains(k)
1✔
1169

1170

1171
@functools.singledispatch
1✔
1172
def get(m, k, default=None):  # pylint: disable=unused-argument
1✔
1173
    """Return the value of k in m. Return default if k not found in m."""
1174
    return default
1✔
1175

1176

1177
@get.register(dict)
1✔
1178
@get.register(list)
1✔
1179
@get.register(str)
1✔
1180
def _get_others(m, k, default=None):
1✔
1181
    try:
1✔
1182
        return m[k]
1✔
1183
    except (KeyError, IndexError):
1✔
1184
        return default
1✔
1185

1186

1187
@get.register(IPersistentSet)
1✔
1188
@get.register(ITransientSet)
1✔
1189
@get.register(frozenset)
1✔
1190
@get.register(set)
1✔
1191
def _get_settypes(m, k, default=None):
1✔
1192
    if k in m:
1✔
1193
        return k
1✔
1194
    return default
1✔
1195

1196

1197
@get.register(ILookup)
1✔
1198
def _get_ilookup(m, k, default=None):
1✔
1199
    return m.val_at(k, default)
1✔
1200

1201

1202
@functools.singledispatch
1✔
1203
def assoc(m, *kvs):
1✔
1204
    """Associate keys to values in associative data structure m. If m is None,
1205
    returns a new Map with key-values kvs."""
1206
    raise TypeError(
1✔
1207
        f"Object of type {type(m)} does not implement IAssociative interface"
1208
    )
1209

1210

1211
@assoc.register(type(None))
1✔
1212
def _assoc_none(_: None, *kvs) -> lmap.PersistentMap:
1✔
1213
    return lmap.PersistentMap.empty().assoc(*kvs)
1✔
1214

1215

1216
@assoc.register(IAssociative)
1✔
1217
def _assoc_iassociative(m: IAssociative, *kvs):
1✔
1218
    return m.assoc(*kvs)
1✔
1219

1220

1221
@functools.singledispatch
1✔
1222
def update(m, k, f, *args):
1✔
1223
    """Updates the value for key k in associative data structure m with the return value from
1224
    calling f(old_v, *args). If m is None, use an empty map. If k is not in m, old_v will be
1225
    None."""
1226
    raise TypeError(
1✔
1227
        f"Object of type {type(m)} does not implement IAssociative interface"
1228
    )
1229

1230

1231
@update.register(type(None))
1✔
1232
def _update_none(_: None, k, f, *args) -> lmap.PersistentMap:
1✔
1233
    return lmap.PersistentMap.empty().assoc(k, f(None, *args))
1✔
1234

1235

1236
@update.register(IAssociative)
1✔
1237
def _update_iassociative(m: IAssociative, k, f, *args):
1✔
1238
    old_v = m.val_at(k)
1✔
1239
    new_v = f(old_v, *args)
1✔
1240
    return m.assoc(k, new_v)
1✔
1241

1242

1243
@functools.singledispatch
1✔
1244
def conj(coll, *xs):
1✔
1245
    """Conjoin xs to collection. New elements may be added in different positions
1246
    depending on the type of coll. conj returns the same type as coll. If coll
1247
    is None, return a list with xs conjoined."""
1248
    raise TypeError(
1✔
1249
        f"Object of type {type(coll)} does not implement "
1250
        "IPersistentCollection interface"
1251
    )
1252

1253

1254
@conj.register(type(None))
1✔
1255
def _conj_none(_: None, *xs):
1✔
1256
    l = llist.PersistentList.empty()
1✔
1257
    return l.cons(*xs)
1✔
1258

1259

1260
@conj.register(IPersistentCollection)
1✔
1261
def _conj_ipersistentcollection(coll: IPersistentCollection, *xs):
1✔
1262
    return coll.cons(*xs)
1✔
1263

1264

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

1268
    @functools.wraps(f)
1✔
1269
    def partial_f(*inner_args, **inner_kwargs):
1✔
1270
        return f(*itertools.chain(args, inner_args), **{**kwargs, **inner_kwargs})
1✔
1271

1272
    return partial_f
1✔
1273

1274

1275
@functools.singledispatch
1✔
1276
def deref(o, timeout_s=None, timeout_val=None):
1✔
1277
    """Dereference a Deref object and return its contents.
1278

1279
    If o is an object implementing IBlockingDeref and timeout_s and
1280
    timeout_val are supplied, deref will wait at most timeout_s seconds,
1281
    returning timeout_val if timeout_s seconds elapse and o has not
1282
    returned."""
1283
    raise TypeError(f"Object of type {type(o)} cannot be dereferenced")
1✔
1284

1285

1286
@deref.register(IBlockingDeref)
1✔
1287
def _deref_blocking(
1✔
1288
    o: IBlockingDeref, timeout_s: Optional[float] = None, timeout_val=None
1289
):
1290
    return o.deref(timeout_s, timeout_val)
1✔
1291

1292

1293
@deref.register(IDeref)
1✔
1294
def _deref(o: IDeref):
1✔
1295
    return o.deref()
1✔
1296

1297

1298
def equals(v1, v2) -> bool:
1✔
1299
    """Compare two objects by value. Unlike the standard Python equality operator,
1300
    this function does not consider 1 == True or 0 == False. All other equality
1301
    operations are the same and performed using Python's equality operator."""
1302
    if isinstance(v1, (bool, type(None))) or isinstance(v2, (bool, type(None))):
1✔
1303
        return v1 is v2
1✔
1304
    return v1 == v2
1✔
1305

1306

1307
@functools.singledispatch
1✔
1308
def divide(x: LispNumber, y: LispNumber) -> LispNumber:
1✔
1309
    """Division reducer. If both arguments are integers, return a Fraction.
1310
    Otherwise, return the true division of x and y."""
1311
    return x / y
1✔
1312

1313

1314
@divide.register(int)
1✔
1315
def _divide_ints(x: int, y: LispNumber) -> LispNumber:
1✔
1316
    if isinstance(y, int):
1✔
1317
        return Fraction(x, y)
1✔
1318
    return x / y
1✔
1319

1320

1321
def quotient(num, div) -> LispNumber:
1✔
1322
    """Return the integral quotient resulting from the division of num by div."""
1323
    return math.trunc(num / div)
1✔
1324

1325

1326
@functools.singledispatch
1✔
1327
def compare(x, y) -> int:
1✔
1328
    """Return either -1, 0, or 1 to indicate the relationship between x and y.
1329

1330
    This is a 3-way comparator commonly used in Java-derived systems. Python does not
1331
    typically use 3-way comparators, so this function convert's Python's `__lt__` and
1332
    `__gt__` method returns into one of the 3-way comparator return values."""
1333
    if y is None:
1✔
1334
        assert x is not None, "x cannot be nil"
1✔
1335
        return 1
1✔
1336
    return (x > y) - (x < y)
1✔
1337

1338

1339
@compare.register(type(None))
1✔
1340
def _compare_nil(_: None, y) -> int:
1✔
1341
    # nil is less than all values, except itself.
1342
    return 0 if y is None else -1
1✔
1343

1344

1345
@compare.register(decimal.Decimal)
1✔
1346
def _compare_decimal(x: decimal.Decimal, y) -> int:
1✔
1347
    # Decimal instances will not compare with float("nan"), so we need a special case
1348
    if isinstance(y, float):
1✔
1349
        return -compare(y, x)  # pylint: disable=arguments-out-of-order
1✔
1350
    return (x > y) - (x < y)
1✔
1351

1352

1353
@compare.register(float)
1✔
1354
def _compare_float(x, y) -> int:
1✔
1355
    if y is None:
1✔
1356
        return 1
1✔
1357
    if math.isnan(x):
1✔
1358
        return 0
1✔
1359
    return (x > y) - (x < y)
1✔
1360

1361

1362
@compare.register(IPersistentSet)
1✔
1363
def _compare_sets(x: IPersistentSet, y) -> int:
1✔
1364
    # Sets are not comparable (because there is no total ordering between sets).
1365
    # However, in Python comparison is done using __lt__ and __gt__, which AbstractSet
1366
    # inconveniently also uses as part of it's API for comparing sets with subset and
1367
    # superset relationships. To "break" that, we just override the comparison method.
1368
    # One consequence of this is that it may be possible to sort a collection of sets,
1369
    # since `compare` isn't actually consulted in sorting.
1370
    raise TypeError(
1✔
1371
        f"cannot compare instances of '{type(x).__name__}' and '{type(y).__name__}'"
1372
    )
1373

1374

1375
def sort(coll, f=None) -> Optional[ISeq]:
1✔
1376
    """Return a sorted sequence of the elements in coll. If a comparator
1377
    function f is provided, compare elements in coll using f."""
1378
    return lseq.sequence(sorted(coll, key=Maybe(f).map(functools.cmp_to_key).value))
1✔
1379

1380

1381
def sort_by(keyfn, coll, cmp=None) -> Optional[ISeq]:
1✔
1382
    """Return a sorted sequence of the elements in coll. If a comparator
1383
    function f is provided, compare elements in coll using f."""
1384
    if cmp is not None:
1✔
1385

1386
        class key:
1✔
1387
            __slots__ = ("obj",)
1✔
1388

1389
            def __init__(self, obj):
1✔
1390
                self.obj = obj
1✔
1391

1392
            def __lt__(self, other):
1✔
1393
                return cmp(keyfn(self.obj), keyfn(other.obj)) < 0
1✔
1394

1395
            def __gt__(self, other):
1✔
1396
                return cmp(keyfn(self.obj), keyfn(other.obj)) > 0
×
1397

1398
            def __eq__(self, other):
1✔
1399
                return cmp(keyfn(self.obj), keyfn(other.obj)) == 0
×
1400

1401
            def __le__(self, other):
1✔
1402
                return cmp(keyfn(self.obj), keyfn(other.obj)) <= 0
×
1403

1404
            def __ge__(self, other):
1✔
1405
                return cmp(keyfn(self.obj), keyfn(other.obj)) >= 0
×
1406

1407
            __hash__ = None  # type: ignore
1✔
1408

1409
    else:
1410
        key = keyfn  # type: ignore
1✔
1411

1412
    return lseq.sequence(sorted(coll, key=key))
1✔
1413

1414

1415
def is_special_form(s: sym.Symbol) -> bool:
1✔
1416
    """Return True if s names a special form."""
1417
    return s in _SPECIAL_FORMS
1✔
1418

1419

1420
@functools.singledispatch
1✔
1421
def to_lisp(o, keywordize_keys: bool = True):  # pylint: disable=unused-argument
1✔
1422
    """Recursively convert Python collections into Lisp collections."""
1423
    return o
1✔
1424

1425

1426
@to_lisp.register(list)
1✔
1427
@to_lisp.register(tuple)
1✔
1428
def _to_lisp_vec(o: Iterable, keywordize_keys: bool = True) -> vec.PersistentVector:
1✔
1429
    return vec.vector(
1✔
1430
        map(functools.partial(to_lisp, keywordize_keys=keywordize_keys), o)
1431
    )
1432

1433

1434
@functools.singledispatch
1✔
1435
def _keywordize_keys(k, keywordize_keys: bool = True):
1✔
1436
    return to_lisp(k, keywordize_keys=keywordize_keys)
1✔
1437

1438

1439
@_keywordize_keys.register(str)
1✔
1440
def _keywordize_keys_str(k, keywordize_keys: bool = True):
1✔
1441
    return kw.keyword(k)
1✔
1442

1443

1444
@to_lisp.register(dict)
1✔
1445
def _to_lisp_map(o: Mapping, keywordize_keys: bool = True) -> lmap.PersistentMap:
1✔
1446
    process_key = _keywordize_keys if keywordize_keys else to_lisp
1✔
1447
    return lmap.map(
1✔
1448
        {
1449
            process_key(k, keywordize_keys=keywordize_keys): to_lisp(
1450
                v, keywordize_keys=keywordize_keys
1451
            )
1452
            for k, v in o.items()
1453
        }
1454
    )
1455

1456

1457
@to_lisp.register(frozenset)
1✔
1458
@to_lisp.register(set)
1✔
1459
def _to_lisp_set(o: AbstractSet, keywordize_keys: bool = True) -> lset.PersistentSet:
1✔
1460
    return lset.set(map(functools.partial(to_lisp, keywordize_keys=keywordize_keys), o))
1✔
1461

1462

1463
def _kw_name(kw: kw.Keyword) -> str:
1✔
1464
    return kw.name
1✔
1465

1466

1467
@functools.singledispatch
1✔
1468
def to_py(
1✔
1469
    o, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
1470
):  # pylint: disable=unused-argument
1471
    """Recursively convert Lisp collections into Python collections."""
1472
    return o
1✔
1473

1474

1475
@to_py.register(kw.Keyword)
1✔
1476
def _to_py_kw(o: kw.Keyword, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name) -> Any:
1✔
1477
    return keyword_fn(o)
1✔
1478

1479

1480
@to_py.register(IPersistentList)
1✔
1481
@to_py.register(ISeq)
1✔
1482
@to_py.register(IPersistentVector)
1✔
1483
def _to_py_list(
1✔
1484
    o: Union[IPersistentList, ISeq, IPersistentVector],
1485
    keyword_fn: Callable[[kw.Keyword], Any] = _kw_name,
1486
) -> list:
1487
    return list(map(functools.partial(to_py, keyword_fn=keyword_fn), o))
1✔
1488

1489

1490
@to_py.register(IPersistentMap)
1✔
1491
def _to_py_map(
1✔
1492
    o: IPersistentMap, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
1493
) -> dict:
1494
    return {
1✔
1495
        to_py(key, keyword_fn=keyword_fn): to_py(value, keyword_fn=keyword_fn)
1496
        for key, value in o.items()
1497
    }
1498

1499

1500
@to_py.register(IPersistentSet)
1✔
1501
def _to_py_set(
1✔
1502
    o: IPersistentSet, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
1503
) -> set:
1504
    return set(to_py(e, keyword_fn=keyword_fn) for e in o)
1✔
1505

1506

1507
def lrepr(o, human_readable: bool = False) -> str:
1✔
1508
    """Produce a string representation of an object. If human_readable is False,
1509
    the string representation of Lisp objects is something that can be read back
1510
    in by the reader as the same object."""
1511
    core_ns = Namespace.get(CORE_NS_SYM)
1✔
1512
    assert core_ns is not None
1✔
1513
    return lobj.lrepr(
1✔
1514
        o,
1515
        human_readable=human_readable,
1516
        print_dup=core_ns.find(sym.symbol(PRINT_DUP_VAR_NAME)).value,  # type: ignore
1517
        print_length=core_ns.find(  # type: ignore
1518
            sym.symbol(PRINT_LENGTH_VAR_NAME)
1519
        ).value,
1520
        print_level=core_ns.find(  # type: ignore
1521
            sym.symbol(PRINT_LEVEL_VAR_NAME)
1522
        ).value,
1523
        print_meta=core_ns.find(sym.symbol(PRINT_META_VAR_NAME)).value,  # type: ignore
1524
        print_readably=core_ns.find(  # type: ignore
1525
            sym.symbol(PRINT_READABLY_VAR_NAME)
1526
        ).value,
1527
    )
1528

1529

1530
def lstr(o) -> str:
1✔
1531
    """Produce a human readable string representation of an object."""
1532
    return lrepr(o, human_readable=True)
1✔
1533

1534

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

1537

1538
def repl_completions(text: str) -> Iterable[str]:
1✔
1539
    """Return an optional iterable of REPL completions."""
1540
    # Can't complete Keywords, Numerals
1541
    if __NOT_COMPLETEABLE.match(text):
1✔
1542
        return ()
×
1543
    elif text.startswith(":"):
1✔
1544
        return kw.complete(text)
×
1545
    else:
1546
        ns = get_current_ns()
1✔
1547
        return ns.complete(text)
1✔
1548

1549

1550
####################
1551
# Compiler Support #
1552
####################
1553

1554

1555
@functools.singledispatch
1✔
1556
def _collect_args(args) -> ISeq:
1✔
1557
    """Collect Python starred arguments into a Basilisp list."""
1558
    raise TypeError("Python variadic arguments should always be a tuple")
×
1559

1560

1561
@_collect_args.register(tuple)
1✔
1562
def _collect_args_tuple(args: tuple) -> ISeq:
1✔
1563
    return llist.list(args)
1✔
1564

1565

1566
class _TrampolineArgs:
1✔
1567
    __slots__ = ("_has_varargs", "_args", "_kwargs")
1✔
1568

1569
    def __init__(self, has_varargs: bool, *args, **kwargs) -> None:
1✔
1570
        self._has_varargs = has_varargs
1✔
1571
        self._args = args
1✔
1572
        self._kwargs = kwargs
1✔
1573

1574
    @property
1✔
1575
    def args(self) -> Tuple:
1✔
1576
        """Return the arguments for a trampolined function. If the function
1577
        that is being trampolined has varargs, unroll the final argument if
1578
        it is a sequence."""
1579
        if not self._has_varargs:
1✔
1580
            return self._args
1✔
1581

1582
        try:
1✔
1583
            final = self._args[-1]
1✔
1584
            if isinstance(final, ISeq):
1✔
1585
                inits = self._args[:-1]
1✔
1586
                return tuple(itertools.chain(inits, final))
1✔
1587
            return self._args
1✔
1588
        except IndexError:
1✔
1589
            return ()
1✔
1590

1591
    @property
1✔
1592
    def kwargs(self) -> Dict:
1✔
1593
        return self._kwargs
1✔
1594

1595

1596
def _trampoline(f):
1✔
1597
    """Trampoline a function repeatedly until it is finished recurring to help
1598
    avoid stack growth."""
1599

1600
    @functools.wraps(f)
1✔
1601
    def trampoline(*args, **kwargs):
1✔
1602
        while True:
1✔
1603
            ret = f(*args, **kwargs)
1✔
1604
            if isinstance(ret, _TrampolineArgs):
1✔
1605
                args = ret.args
1✔
1606
                kwargs = ret.kwargs
1✔
1607
                continue
1✔
1608
            return ret
1✔
1609

1610
    return trampoline
1✔
1611

1612

1613
def _lisp_fn_apply_kwargs(f):
1✔
1614
    """Convert a Python function into a Lisp function.
1615

1616
    Python keyword arguments will be converted into Lisp keyword/argument pairs
1617
    that can be easily understood by Basilisp.
1618

1619
    Lisp functions annotated with the `:apply` value for the `:kwargs` metadata key
1620
    will be wrapped with this decorator by the compiler."""
1621

1622
    @functools.wraps(f)
1✔
1623
    def wrapped_f(*args, **kwargs):
1✔
1624
        return f(
1✔
1625
            *args,
1626
            *itertools.chain.from_iterable(
1627
                (kw.keyword(demunge(k)), v) for k, v in kwargs.items()
1628
            ),
1629
        )
1630

1631
    return wrapped_f
1✔
1632

1633

1634
def _lisp_fn_collect_kwargs(f):
1✔
1635
    """Convert a Python function into a Lisp function.
1636

1637
    Python keyword arguments will be collected into a single map, which is supplied
1638
    as the final positional argument.
1639

1640
    Lisp functions annotated with the `:collect` value for the `:kwargs` metadata key
1641
    will be wrapped with this decorator by the compiler."""
1642

1643
    @functools.wraps(f)
1✔
1644
    def wrapped_f(*args, **kwargs):
1✔
1645
        return f(
1✔
1646
            *args,
1647
            lmap.map({kw.keyword(demunge(k)): v for k, v in kwargs.items()}),
1648
        )
1649

1650
    return wrapped_f
1✔
1651

1652

1653
def _with_attrs(**kwargs):
1✔
1654
    """Decorator to set attributes on a function. Returns the original
1655
    function after setting the attributes named by the keyword arguments."""
1656

1657
    def decorator(f):
1✔
1658
        for k, v in kwargs.items():
1✔
1659
            setattr(f, k, v)
1✔
1660
        return f
1✔
1661

1662
    return decorator
1✔
1663

1664

1665
def _fn_with_meta(f, meta: Optional[lmap.PersistentMap]):
1✔
1666
    """Return a new function with the given meta. If the function f already
1667
    has a meta map, then merge the new meta with the existing meta."""
1668

1669
    if not isinstance(meta, lmap.PersistentMap):
1✔
1670
        raise TypeError("meta must be a map")
1✔
1671

1672
    if inspect.iscoroutinefunction(f):
1✔
1673

1674
        @functools.wraps(f)
1✔
1675
        async def wrapped_f(*args, **kwargs):
1✔
1676
            return await f(*args, **kwargs)
1✔
1677

1678
    else:
1679

1680
        @functools.wraps(f)
1✔
1681
        def wrapped_f(*args, **kwargs):
1✔
1682
            return f(*args, **kwargs)
1✔
1683

1684
    wrapped_f.meta = (  # type: ignore
1✔
1685
        f.meta.update(meta)
1686
        if hasattr(f, "meta") and isinstance(f.meta, lmap.PersistentMap)
1687
        else meta
1688
    )
1689
    wrapped_f.with_meta = partial(_fn_with_meta, wrapped_f)  # type: ignore
1✔
1690
    return wrapped_f
1✔
1691

1692

1693
def _basilisp_fn(arities: Tuple[Union[int, kw.Keyword]]):
1✔
1694
    """Create a Basilisp function, setting meta and supplying a with_meta
1695
    method implementation."""
1696

1697
    def wrap_fn(f):
1✔
1698
        assert not hasattr(f, "meta")
1✔
1699
        f._basilisp_fn = True
1✔
1700
        f.arities = lset.set(arities)
1✔
1701
        f.meta = None
1✔
1702
        f.with_meta = partial(_fn_with_meta, f)
1✔
1703
        return f
1✔
1704

1705
    return wrap_fn
1✔
1706

1707

1708
def _basilisp_type(
1✔
1709
    fields: Iterable[str],
1710
    interfaces: Iterable[Type],
1711
    artificially_abstract_bases: AbstractSet[Type],
1712
    members: Iterable[str],
1713
):
1714
    """Check that a Basilisp type (defined by `deftype*`) only declares abstract
1715
    super-types and that all abstract methods are implemented."""
1716

1717
    def wrap_class(cls: Type):
1✔
1718
        field_names = frozenset(fields)
1✔
1719
        member_names = frozenset(members)
1✔
1720
        artificially_abstract_base_members: Set[str] = set()
1✔
1721
        all_member_names = field_names.union(member_names)
1✔
1722
        all_interface_methods: Set[str] = set()
1✔
1723
        for interface in interfaces:
1✔
1724
            if interface is object:
1✔
1725
                continue
1✔
1726

1727
            if is_abstract(interface):
1✔
1728
                interface_names: FrozenSet[str] = interface.__abstractmethods__
1✔
1729
                interface_property_names: FrozenSet[str] = frozenset(
1✔
1730
                    method
1731
                    for method in interface_names
1732
                    if isinstance(getattr(interface, method), property)
1733
                )
1734
                interface_method_names = interface_names - interface_property_names
1✔
1735
                if not interface_method_names.issubset(member_names):
1✔
1736
                    missing_methods = ", ".join(interface_method_names - member_names)
1✔
1737
                    raise RuntimeException(
1✔
1738
                        "deftype* definition missing interface members for interface "
1739
                        f"{interface}: {missing_methods}",
1740
                    )
1741
                elif not interface_property_names.issubset(all_member_names):
1✔
1742
                    missing_fields = ", ".join(interface_property_names - field_names)
1✔
1743
                    raise RuntimeException(
1✔
1744
                        "deftype* definition missing interface properties for interface "
1745
                        f"{interface}: {missing_fields}",
1746
                    )
1747

1748
                all_interface_methods.update(interface_names)
1✔
1749
            elif interface in artificially_abstract_bases:
1✔
1750
                artificially_abstract_base_members.update(
1✔
1751
                    map(
1752
                        lambda v: v[0],
1753
                        inspect.getmembers(
1754
                            interface,
1755
                            predicate=lambda v: inspect.isfunction(v)
1756
                            or isinstance(v, (property, staticmethod))
1757
                            or inspect.ismethod(v),
1758
                        ),
1759
                    )
1760
                )
1761
            else:
1762
                raise RuntimeException(
1✔
1763
                    "deftype* interface must be Python abstract class or object",
1764
                )
1765

1766
        extra_methods = member_names - all_interface_methods - OBJECT_DUNDER_METHODS
1✔
1767
        if extra_methods and not extra_methods.issubset(
1✔
1768
            artificially_abstract_base_members
1769
        ):
1770
            extra_method_str = ", ".join(extra_methods)
1✔
1771
            raise RuntimeException(
1✔
1772
                "deftype* definition for interface includes members not part of "
1773
                f"defined interfaces: {extra_method_str}"
1774
            )
1775

1776
        return cls
1✔
1777

1778
    return wrap_class
1✔
1779

1780

1781
###############################
1782
# Symbol and Alias Resolution #
1783
###############################
1784

1785

1786
def resolve_alias(s: sym.Symbol, ns: Optional[Namespace] = None) -> sym.Symbol:
1✔
1787
    """Resolve the aliased symbol in the current namespace."""
1788
    if s in _SPECIAL_FORMS:
1✔
1789
        return s
1✔
1790

1791
    ns = Maybe(ns).or_else(get_current_ns)
1✔
1792
    if s.ns is not None:
1✔
1793
        aliased_ns = ns.get_alias(sym.symbol(s.ns))
1✔
1794
        if aliased_ns is not None:
1✔
1795
            return sym.symbol(s.name, aliased_ns.name)
1✔
1796
        else:
1797
            return s
1✔
1798
    else:
1799
        which_var = ns.find(sym.symbol(s.name))
1✔
1800
        if which_var is not None:
1✔
1801
            return sym.symbol(which_var.name.name, which_var.ns.name)
1✔
1802
        else:
1803
            return sym.symbol(s.name, ns=ns.name)
1✔
1804

1805

1806
def resolve_var(s: sym.Symbol, ns: Optional[Namespace] = None) -> Optional[Var]:
1✔
1807
    """Resolve the aliased symbol to a Var from the specified namespace, or the
1808
    current namespace if none is specified."""
1809
    return Var.find(resolve_alias(s, ns))
1✔
1810

1811

1812
#######################
1813
# Namespace Utilities #
1814
#######################
1815

1816

1817
@contextlib.contextmanager
1✔
1818
def bindings(bindings: Optional[Mapping[Var, Any]] = None):
1✔
1819
    """Context manager for temporarily changing the value thread-local value for
1820
    Basilisp dynamic Vars."""
1821
    m = lmap.map(bindings or {})
1✔
1822
    logger.debug(
1✔
1823
        f"Binding thread-local values for Vars: {', '.join(map(str, m.keys()))}"
1824
    )
1825
    try:
1✔
1826
        push_thread_bindings(m)
1✔
1827
        yield
1✔
1828
    finally:
1829
        pop_thread_bindings()
1✔
1830
        logger.debug(
1✔
1831
            f"Reset thread-local bindings for Vars: {', '.join(map(str, m.keys()))}"
1832
        )
1833

1834

1835
@contextlib.contextmanager
1✔
1836
def ns_bindings(
1✔
1837
    ns_name: str, module: Optional[BasilispModule] = None
1838
) -> Iterator[Namespace]:
1839
    """Context manager for temporarily changing the value of basilisp.core/*ns*."""
1840
    symbol = sym.symbol(ns_name)
1✔
1841
    ns = Namespace.get_or_create(symbol, module=module)
1✔
1842
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
1843
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
1844
    )
1845

1846
    with bindings({ns_var: ns}):
1✔
1847
        yield ns_var.value
1✔
1848

1849

1850
@contextlib.contextmanager
1✔
1851
def remove_ns_bindings():
1✔
1852
    """Context manager to pop the most recent bindings for basilisp.core/*ns* after
1853
    completion of the code under management."""
1854
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
1855
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
1856
    )
1857
    try:
1✔
1858
        yield
1✔
1859
    finally:
1860
        ns_var.pop_bindings()
1✔
1861
        logger.debug(f"Reset bindings for {NS_VAR_SYM} to {ns_var.value}")
1✔
1862

1863

1864
def get_current_ns() -> Namespace:
1✔
1865
    """Get the value of the dynamic variable `*ns*` in the current thread."""
1866
    ns: Namespace = (
1✔
1867
        Maybe(Var.find(NS_VAR_SYM))
1868
        .map(lambda v: v.value)
1869
        .or_else_raise(lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!"))
1870
    )
1871
    return ns
1✔
1872

1873

1874
def set_current_ns(
1✔
1875
    ns_name: str,
1876
    module: Optional[BasilispModule] = None,
1877
) -> Var:
1878
    """Set the value of the dynamic variable `*ns*` in the current thread."""
1879
    symbol = sym.symbol(ns_name)
1✔
1880
    ns = Namespace.get_or_create(symbol, module=module)
1✔
1881
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
1882
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
1883
    )
1884
    ns_var.push_bindings(ns)
1✔
1885
    logger.debug(f"Setting {NS_VAR_SYM} to {ns}")
1✔
1886
    return ns_var
1✔
1887

1888

1889
##############################
1890
# Emit Generated Python Code #
1891
##############################
1892

1893

1894
def add_generated_python(
1✔
1895
    generated_python: str,
1896
    which_ns: Optional[Namespace] = None,
1897
) -> None:
1898
    """Add generated Python code to a dynamic variable in which_ns."""
1899
    if which_ns is None:
1✔
1900
        which_ns = get_current_ns()
×
1901
    v = Maybe(which_ns.find(sym.symbol(GENERATED_PYTHON_VAR_NAME))).or_else(
1✔
1902
        lambda: Var.intern(
1903
            which_ns,  # type: ignore
1904
            sym.symbol(GENERATED_PYTHON_VAR_NAME),
1905
            "",
1906
            dynamic=True,
1907
            meta=lmap.map({_PRIVATE_META_KEY: True}),
1908
        )
1909
    )
1910
    # Accessing the Var root via the property uses a lock, which is the
1911
    # desired behavior for Basilisp code, but it introduces additional
1912
    # startup time when there will not realistically be any contention.
1913
    v._root = v._root + generated_python  # type: ignore
1✔
1914

1915

1916
def print_generated_python() -> bool:
1✔
1917
    """Return the value of the `*print-generated-python*` dynamic variable."""
1918
    ns_sym = sym.symbol(PRINT_GENERATED_PY_VAR_NAME, ns=CORE_NS)
1✔
1919
    return (
1✔
1920
        Maybe(Var.find(ns_sym))
1921
        .map(lambda v: v.value)
1922
        .or_else_raise(lambda: RuntimeException(f"Dynamic Var {ns_sym} not bound!"))
1923
    )
1924

1925

1926
#########################
1927
# Bootstrap the Runtime #
1928
#########################
1929

1930

1931
def init_ns_var() -> Var:
1✔
1932
    """Initialize the dynamic `*ns*` variable in the `basilisp.core` Namespace."""
1933
    core_ns = Namespace.get_or_create(CORE_NS_SYM)
1✔
1934
    ns_var = Var.intern(
1✔
1935
        core_ns,
1936
        sym.symbol(NS_VAR_NAME),
1937
        core_ns,
1938
        dynamic=True,
1939
        meta=lmap.map(
1940
            {
1941
                _DOC_META_KEY: (
1942
                    "Pointer to the current namespace.\n\n"
1943
                    "This value is used by both the compiler and runtime to determine where "
1944
                    "newly defined Vars should be bound, so users should not alter or bind "
1945
                    "this Var unless they know what they're doing."
1946
                )
1947
            }
1948
        ),
1949
    )
1950
    logger.debug(f"Created namespace variable {NS_VAR_SYM}")
1✔
1951
    return ns_var
1✔
1952

1953

1954
def bootstrap_core(compiler_opts: CompilerOpts) -> None:
1✔
1955
    """Bootstrap the environment with functions that are either difficult to express
1956
    with the very minimal Lisp environment or which are expected by the compiler."""
1957
    _NS = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
1958
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
1959
    )
1960

1961
    def in_ns(s: sym.Symbol):
1✔
1962
        ns = Namespace.get_or_create(s)
1✔
1963
        _NS.set_value(ns)
1✔
1964
        return ns
1✔
1965

1966
    # Vars used in bootstrapping the runtime
1967
    Var.intern_unbound(
1✔
1968
        CORE_NS_SYM,
1969
        sym.symbol("unquote"),
1970
        meta=lmap.map(
1971
            {
1972
                _DOC_META_KEY: (
1973
                    "Placeholder Var so the compiler does not throw an error while syntax quoting.\n\n"
1974
                    "See :ref:`macros` and :ref:`syntax_quoting` for more details."
1975
                )
1976
            }
1977
        ),
1978
    )
1979
    Var.intern_unbound(
1✔
1980
        CORE_NS_SYM,
1981
        sym.symbol("unquote-splicing"),
1982
        meta=lmap.map(
1983
            {
1984
                _DOC_META_KEY: (
1985
                    "Placeholder Var so the compiler does not throw an error while syntax quoting.\n\n"
1986
                    "See :ref:`macros` and :ref:`syntax_quoting` for more details."
1987
                )
1988
            }
1989
        ),
1990
    )
1991
    Var.intern(
1✔
1992
        CORE_NS_SYM, sym.symbol("in-ns"), in_ns, meta=lmap.map({_REDEF_META_KEY: True})
1993
    )
1994

1995
    # Dynamic Var examined by the compiler when importing new Namespaces
1996
    Var.intern(
1✔
1997
        CORE_NS_SYM,
1998
        sym.symbol(COMPILER_OPTIONS_VAR_NAME),
1999
        compiler_opts,
2000
        dynamic=True,
2001
    )
2002

2003
    # Dynamic Var for introspecting the default reader featureset
2004
    Var.intern(
1✔
2005
        CORE_NS_SYM,
2006
        sym.symbol(DEFAULT_READER_FEATURES_VAR_NAME),
2007
        READER_COND_DEFAULT_FEATURE_SET,
2008
        dynamic=True,
2009
        meta=lmap.map(
2010
            {
2011
                _DOC_META_KEY: (
2012
                    "The set of all currently supported "
2013
                    ":ref:`reader features <reader_conditions>`."
2014
                )
2015
            }
2016
        ),
2017
    )
2018

2019
    # Dynamic Vars examined by the compiler for generating Python code for debugging
2020
    Var.intern(
1✔
2021
        CORE_NS_SYM,
2022
        sym.symbol(PRINT_GENERATED_PY_VAR_NAME),
2023
        False,
2024
        dynamic=True,
2025
        meta=lmap.map({_PRIVATE_META_KEY: True}),
2026
    )
2027
    Var.intern(
1✔
2028
        CORE_NS_SYM,
2029
        sym.symbol(GENERATED_PYTHON_VAR_NAME),
2030
        "",
2031
        dynamic=True,
2032
        meta=lmap.map({_PRIVATE_META_KEY: True}),
2033
    )
2034

2035
    # Dynamic Vars for controlling printing
2036
    Var.intern(
1✔
2037
        CORE_NS_SYM, sym.symbol(PRINT_DUP_VAR_NAME), lobj.PRINT_DUP, dynamic=True
2038
    )
2039
    Var.intern(
1✔
2040
        CORE_NS_SYM, sym.symbol(PRINT_LENGTH_VAR_NAME), lobj.PRINT_LENGTH, dynamic=True
2041
    )
2042
    Var.intern(
1✔
2043
        CORE_NS_SYM, sym.symbol(PRINT_LEVEL_VAR_NAME), lobj.PRINT_LEVEL, dynamic=True
2044
    )
2045
    Var.intern(
1✔
2046
        CORE_NS_SYM, sym.symbol(PRINT_META_VAR_NAME), lobj.PRINT_META, dynamic=True
2047
    )
2048
    Var.intern(
1✔
2049
        CORE_NS_SYM,
2050
        sym.symbol(PRINT_READABLY_VAR_NAME),
2051
        lobj.PRINT_READABLY,
2052
        dynamic=True,
2053
    )
2054

2055
    # Version info
2056
    from basilisp.__version__ import VERSION
1✔
2057

2058
    Var.intern(
1✔
2059
        CORE_NS_SYM,
2060
        sym.symbol(PYTHON_VERSION_VAR_NAME),
2061
        vec.vector(sys.version_info),
2062
        dynamic=True,
2063
        meta=lmap.map(
2064
            {
2065
                _DOC_META_KEY: (
2066
                    "The current Python version as a vector of "
2067
                    "``[major, minor, revision]``."
2068
                )
2069
            }
2070
        ),
2071
    )
2072
    Var.intern(
1✔
2073
        CORE_NS_SYM,
2074
        sym.symbol(BASILISP_VERSION_VAR_NAME),
2075
        vec.vector(VERSION),
2076
        dynamic=True,
2077
        meta=lmap.map(
2078
            {
2079
                _DOC_META_KEY: (
2080
                    "The current Basilisp version as a vector of "
2081
                    "``[major, minor, revision]``."
2082
                )
2083
            }
2084
        ),
2085
    )
2086

2087

2088
def get_compiler_opts() -> CompilerOpts:
1✔
2089
    """Return the current compiler options map."""
2090
    v = Var.find_in_ns(CORE_NS_SYM, sym.symbol(COMPILER_OPTIONS_VAR_NAME))
1✔
2091
    assert v is not None, "*compiler-options* Var not defined"
1✔
2092
    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