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

basilisp-lang / basilisp / 21272825097

23 Jan 2026 02:51AM UTC coverage: 98.601% (-0.02%) from 98.623%
21272825097

Pull #1317

github

web-flow
Merge d9df7f806 into aed95e3d4
Pull Request #1317: Make `apply` lazy

1101 of 1110 branches covered (99.19%)

Branch coverage included in aggregate %.

54 of 61 new or added lines in 3 files covered. (88.52%)

4 existing lines in 1 file now uncovered.

9261 of 9399 relevant lines covered (98.53%)

0.99 hits per line

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

97.87
/src/basilisp/lang/runtime.py
1
# pylint: disable=too-many-lines
2
import builtins
1✔
3
import collections.abc
1✔
4
import contextlib
1✔
5
import decimal
1✔
6
import functools
1✔
7
import graphlib
1✔
8
import importlib.metadata
1✔
9
import inspect
1✔
10
import itertools
1✔
11
import logging
1✔
12
import math
1✔
13
import numbers
1✔
14
import pickle  # nosec B403
1✔
15
import platform
1✔
16
import re
1✔
17
import sys
1✔
18
import threading
1✔
19
import types
1✔
20
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence, Sized
1✔
21
from typing import AbstractSet, Any, NoReturn, Optional, TypeVar, Union, cast
1✔
22

23
import attr
1✔
24

25
from basilisp.lang import keyword as kw
1✔
26
from basilisp.lang import list as llist
1✔
27
from basilisp.lang import map as lmap
1✔
28
from basilisp.lang import obj as lobj
1✔
29
from basilisp.lang import seq as lseq
1✔
30
from basilisp.lang import set as lset
1✔
31
from basilisp.lang import symbol as sym
1✔
32
from basilisp.lang import vector as vec
1✔
33
from basilisp.lang.atom import Atom
1✔
34
from basilisp.lang.interfaces import (
1✔
35
    IAssociative,
36
    IBlockingDeref,
37
    IDeref,
38
    ILookup,
39
    IMapEntry,
40
    IPersistentCollection,
41
    IPersistentList,
42
    IPersistentMap,
43
    IPersistentSet,
44
    IPersistentStack,
45
    IPersistentVector,
46
    IReduce,
47
    ISeq,
48
    ISeqable,
49
    ITransientAssociative,
50
    ITransientSet,
51
    ReduceFunction,
52
)
53
from basilisp.lang.reduced import Reduced
1✔
54
from basilisp.lang.reference import RefBase, ReferenceBase
1✔
55
from basilisp.lang.typing import BasilispFunction, Comparable, CompilerOpts
1✔
56
from basilisp.lang.util import OBJECT_DUNDER_METHODS, demunge, is_abstract, munge
1✔
57
from basilisp.util import Maybe
1✔
58

59
logger = logging.getLogger(__name__)
1✔
60

61
# Public constants
62
CORE_NS = "basilisp.core"
1✔
63
CORE_NS_SYM = sym.symbol(CORE_NS)
1✔
64
NS_VAR_NAME = "*ns*"
1✔
65
NS_VAR_SYM = sym.symbol(NS_VAR_NAME, ns=CORE_NS)
1✔
66
IMPORT_MODULE_VAR_NAME = "*import-module*"
1✔
67
IMPORT_MODULE_VAR_SYM = sym.symbol(IMPORT_MODULE_VAR_NAME, ns=CORE_NS)
1✔
68
NS_VAR_NS = CORE_NS
1✔
69
REPL_DEFAULT_NS = "basilisp.user"
1✔
70
SUPPORTED_PYTHON_VERSIONS = frozenset({(3, 10), (3, 11), (3, 12), (3, 13), (3, 14)})
1✔
71
BASILISP_VERSION_STRING = importlib.metadata.version("basilisp")
1✔
72
BASILISP_VERSION = vec.vector(
1✔
73
    (int(s) if s.isdigit() else s) for s in BASILISP_VERSION_STRING.split(".")
74
)
75

76
# Public basilisp.core symbol names
77
COMPILER_OPTIONS_VAR_NAME = "*compiler-options*"
1✔
78
COMMAND_LINE_ARGS_VAR_NAME = "*command-line-args*"
1✔
79
DEFAULT_READER_FEATURES_VAR_NAME = "*default-reader-features*"
1✔
80
GENERATED_PYTHON_VAR_NAME = "*generated-python*"
1✔
81
PRINT_GENERATED_PY_VAR_NAME = "*print-generated-python*"
1✔
82
MAIN_NS_VAR_NAME = "*main-ns*"
1✔
83
PRINT_DUP_VAR_NAME = "*print-dup*"
1✔
84
PRINT_LENGTH_VAR_NAME = "*print-length*"
1✔
85
PRINT_LEVEL_VAR_NAME = "*print-level*"
1✔
86
PRINT_META_VAR_NAME = "*print-meta*"
1✔
87
PRINT_NAMESPACE_MAPS_VAR_NAME = "*print-namespace-maps*"
1✔
88
PRINT_READABLY_VAR_NAME = "*print-readably*"
1✔
89
PYTHON_VERSION_VAR_NAME = "*python-version*"
1✔
90
BASILISP_VERSION_VAR_NAME = "*basilisp-version*"
1✔
91

92
# Common meta keys
93
_DOC_META_KEY = kw.keyword("doc")
1✔
94
_DYNAMIC_META_KEY = kw.keyword("dynamic")
1✔
95
_PRIVATE_META_KEY = kw.keyword("private")
1✔
96
_REDEF_META_KEY = kw.keyword("redef")
1✔
97

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

148

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

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

165
    yield feature_kw(sys.version_info.major, sys.version_info.minor)
1✔
166

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

174

175
READER_COND_BASILISP_FEATURE_KW = kw.keyword("lpy")
1✔
176
READER_COND_DEFAULT_FEATURE_KW = kw.keyword("default")
1✔
177
READER_COND_PLATFORM = kw.keyword(platform.system().lower())
1✔
178
READER_COND_DEFAULT_FEATURE_SET = lset.s(
1✔
179
    READER_COND_BASILISP_FEATURE_KW,
180
    READER_COND_DEFAULT_FEATURE_KW,
181
    READER_COND_PLATFORM,
182
    *_supported_python_versions_features(),
183
)
184

185
CompletionMatcher = Callable[[tuple[sym.Symbol, Any]], bool]
1✔
186
CompletionTrimmer = Callable[[tuple[sym.Symbol, Any]], str]
1✔
187

188

189
class BasilispModule(types.ModuleType):
1✔
190
    __basilisp_namespace__: "Namespace"
1✔
191
    __basilisp_bootstrapped__: bool = False
1✔
192

193

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

205

206
class RuntimeException(Exception):
1✔
207
    pass
1✔
208

209

210
class _VarBindings(threading.local):
1✔
211
    def __init__(self):
1✔
212
        self.bindings: list = []
1✔
213

214

215
class Unbound:
1✔
216
    __slots__ = ("var",)
1✔
217

218
    def __init__(self, v: "Var"):
1✔
219
        self.var = v
1✔
220

221
    def __repr__(self):  # pragma: no cover
222
        return f"Unbound(var={self.var})"
223

224
    def __eq__(self, other):
1✔
225
        return self is other or (isinstance(other, Unbound) and self.var == other.var)
1✔
226

227

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

242
    def __init__(
1✔
243
        self,
244
        ns: "Namespace",
245
        name: sym.Symbol,
246
        dynamic: bool = False,
247
        meta: IPersistentMap | None = None,
248
    ) -> None:
249
        self._ns = ns
1✔
250
        self._name = name
1✔
251
        self._root = Unbound(self)
1✔
252
        self._dynamic = dynamic
1✔
253
        self._is_bound = False
1✔
254
        self._tl = None
1✔
255
        self._meta = meta
1✔
256
        self._lock = threading.RLock()
1✔
257
        self._watches = lmap.EMPTY
1✔
258
        self._validator = None
1✔
259

260
        if dynamic:
1✔
261
            self._tl = _VarBindings()
1✔
262

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

271
    def __repr__(self):
272
        return f"#'{self.ns.name}/{self.name}"
273

274
    def __call__(self, *args, **kwargs):
1✔
275
        return self.value(*args, *kwargs)  # pylint: disable=not-callable
1✔
276

277
    @property
1✔
278
    def ns(self) -> "Namespace":
1✔
279
        return self._ns
1✔
280

281
    @property
1✔
282
    def name(self) -> sym.Symbol:
1✔
283
        return self._name
1✔
284

285
    @property
1✔
286
    def dynamic(self) -> bool:
1✔
287
        return self._dynamic
1✔
288

289
    def set_dynamic(self, dynamic: bool) -> None:
1✔
290
        with self._lock:
1✔
291
            if dynamic == self._dynamic:
1✔
292
                return
1✔
293

294
            self._dynamic = dynamic
1✔
295
            self._tl = _VarBindings() if dynamic else None
1✔
296

297
    @property
1✔
298
    def is_private(self) -> bool | None:
1✔
299
        if self._meta is not None:
1✔
300
            return self._meta.val_at(_PRIVATE_META_KEY)
1✔
301
        return False
1✔
302

303
    @property
1✔
304
    def is_bound(self) -> bool:
1✔
305
        return self._is_bound or self.is_thread_bound
1✔
306

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

317
    @property
1✔
318
    def root(self):
1✔
319
        with self._lock:
1✔
320
            return self._root
1✔
321

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

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

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

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

345
    def deref(self):
1✔
346
        return self.value
1✔
347

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

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

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

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

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

381
    __UNBOUND_SENTINEL = object()
1✔
382

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

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

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

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

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

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

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

462

463
Frame = IPersistentSet[Var]
1✔
464
FrameStack = IPersistentStack[Frame]
1✔
465

466

467
class _ThreadBindings(threading.local):
1✔
468
    def __init__(self):
1✔
469
        self._bindings: FrameStack = vec.EMPTY
1✔
470

471
    def get_bindings(self) -> FrameStack:
1✔
472
        return self._bindings
1✔
473

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

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

483

484
_THREAD_BINDINGS = _ThreadBindings()
1✔
485

486

487
@attr.frozen
1✔
488
class ImportRefer:
1✔
489
    module_name: sym.Symbol
1✔
490
    value: Any
1✔
491

492

493
AliasMap = lmap.PersistentMap[sym.Symbol, sym.Symbol]
1✔
494
ImportReferMap = lmap.PersistentMap[sym.Symbol, Any]
1✔
495
Module = Union[BasilispModule, types.ModuleType]
1✔
496
ModuleMap = lmap.PersistentMap[sym.Symbol, Module]
1✔
497
NamespaceMap = lmap.PersistentMap[sym.Symbol, "Namespace"]
1✔
498
VarMap = lmap.PersistentMap[sym.Symbol, Var]
1✔
499

500

501
_PRIVATE_NAME_PATTERN = re.compile(r"(_\w*|__\w+__)")
1✔
502

503

504
class Namespace(ReferenceBase):
1✔
505
    """Namespaces serve as organizational units in Basilisp code, just as they do in
506
    Clojure code.
507

508
    Vars are mutable containers for functions and data which may be interned in a
509
    namespace and referred to by a Symbol.
510

511
    Namespaces additionally may have aliases to other namespaces, so code organized in
512
    one namespace may conveniently refer to code or data in other namespaces using that
513
    alias as the Symbol's namespace.
514

515
    Namespaces are constructed def-by-def as Basilisp reads in each form in a file
516
    (which will typically declare a namespace at the top).
517

518
    Namespaces have the following fields of interest:
519

520
    - `aliases` is a mapping between a symbolic alias and another Namespace. The fully
521
      qualified name of a namespace is also an alias for itself.
522

523
    - `imports` is a mapping of names to Python modules imported into the current
524
      namespace.
525

526
    - `import_aliases` is a mapping of aliases for Python modules to the true module
527
      name.
528

529
    - `interns` is a mapping between a symbolic name and a Var. The Var may point to
530
      code, data, or nothing, if it is unbound. Vars in `interns` are interned in
531
      _this_ namespace.
532

533
    - `refers` is a mapping between a symbolic name and a Var. Vars in `refers` are
534
      interned in another namespace and are only referred to without an alias in
535
      this namespace.
536
    """
537

538
    # If this set is updated, be sure to update the following two locations:
539
    # - basilisp.lang.compiler.generator._MODULE_ALIASES
540
    # - the `namespace_imports` section in the documentation
541
    DEFAULT_IMPORTS = lset.set(
1✔
542
        map(
543
            sym.symbol,
544
            [
545
                "attr",
546
                "builtins",
547
                "functools",
548
                "io",
549
                "importlib",
550
                "operator",
551
                "sys",
552
                "basilisp.lang.atom",
553
                "basilisp.lang.compiler",
554
                "basilisp.lang.delay",
555
                "basilisp.lang.exception",
556
                "basilisp.lang.futures",
557
                "basilisp.lang.interfaces",
558
                "basilisp.lang.keyword",
559
                "basilisp.lang.list",
560
                "basilisp.lang.map",
561
                "basilisp.lang.multifn",
562
                "basilisp.lang.numbers",
563
                "basilisp.lang.promise",
564
                "basilisp.lang.queue",
565
                "basilisp.lang.reader",
566
                "basilisp.lang.reduced",
567
                "basilisp.lang.runtime",
568
                "basilisp.lang.seq",
569
                "basilisp.lang.set",
570
                "basilisp.lang.symbol",
571
                "basilisp.lang.tagged",
572
                "basilisp.lang.vector",
573
                "basilisp.lang.volatile",
574
                "basilisp.lang.util",
575
            ],
576
        )
577
    )
578

579
    _NAMESPACES: Atom[NamespaceMap] = Atom(lmap.EMPTY)
1✔
580

581
    __slots__ = (
1✔
582
        "_name",
583
        "_module",
584
        "_meta",
585
        "_lock",
586
        "_interns",
587
        "_refers",
588
        "_aliases",
589
        "_imports",
590
        "_import_aliases",
591
        "_import_refers",
592
    )
593

594
    def __init__(self, name: sym.Symbol, module: BasilispModule | None = None) -> None:
1✔
595
        self._name = name
1✔
596
        self._module = Maybe(module).or_else(lambda: _new_module(name.as_python_sym()))
1✔
597

598
        self._meta: IPersistentMap | None = None
1✔
599
        self._lock = threading.RLock()
1✔
600

601
        self._aliases: NamespaceMap = lmap.EMPTY
1✔
602
        self._imports: ModuleMap = lmap.map(
1✔
603
            dict(
604
                map(
605
                    lambda s: (s, importlib.import_module(s.name)),
606
                    Namespace.DEFAULT_IMPORTS,
607
                )
608
            )
609
        )
610
        self._import_aliases: AliasMap = lmap.EMPTY
1✔
611
        self._import_refers: lmap.PersistentMap[sym.Symbol, ImportRefer] = lmap.EMPTY
1✔
612
        self._interns: VarMap = lmap.EMPTY
1✔
613
        self._refers: VarMap = lmap.EMPTY
1✔
614

615
    @property
1✔
616
    def name(self) -> str:
1✔
617
        return self._name.name
1✔
618

619
    @property
1✔
620
    def module(self) -> BasilispModule:
1✔
621
        return self._module
1✔
622

623
    @module.setter
1✔
624
    def module(self, m: BasilispModule):
1✔
625
        """Override the Python module for this Namespace.
626

627
        ***WARNING**
628
        This should only be done by basilisp.importer code to make sure the
629
        correct module is generated for `basilisp.core`."""
630
        self._module = m
1✔
631

632
    @property
1✔
633
    def aliases(self) -> NamespaceMap:
1✔
634
        """A mapping between a symbolic alias and another Namespace. The
635
        fully qualified name of a namespace is also an alias for itself."""
636
        with self._lock:
1✔
637
            return self._aliases
1✔
638

639
    @property
1✔
640
    def imports(self) -> ModuleMap:
1✔
641
        """A mapping of names to Python modules imported into the current
642
        namespace."""
643
        with self._lock:
1✔
644
            return self._imports
1✔
645

646
    @property
1✔
647
    def import_aliases(self) -> AliasMap:
1✔
648
        """A mapping of a symbolic alias and a Python module name."""
649
        with self._lock:
1✔
650
            return self._import_aliases
1✔
651

652
    @property
1✔
653
    def import_refers(self) -> ImportReferMap:
1✔
654
        """A mapping of a symbolic alias and a Python object from an imported module."""
655
        with self._lock:
1✔
656
            return lmap.map({name: v.value for name, v in self._import_refers.items()})
1✔
657

658
    @property
1✔
659
    def interns(self) -> VarMap:
1✔
660
        """A mapping between a symbolic name and a Var. The Var may point to
661
        code, data, or nothing, if it is unbound. Vars in `interns` are
662
        interned in _this_ namespace."""
663
        with self._lock:
1✔
664
            return self._interns
1✔
665

666
    @property
1✔
667
    def refers(self) -> VarMap:
1✔
668
        """A mapping between a symbolic name and a Var. Vars in refers are
669
        interned in another namespace and are only referred to without an
670
        alias in this namespace."""
671
        with self._lock:
1✔
672
            return self._refers
1✔
673

674
    def __repr__(self):
675
        return f"{self._name}"
676

677
    def __hash__(self):
1✔
678
        return hash(self._name)
1✔
679

680
    def _get_required_namespaces(self) -> vec.PersistentVector["Namespace"]:
1✔
681
        """Return a vector of all required namespaces (loaded via `require`, `use`,
682
        or `refer`).
683

684
        This vector will include `basilisp.core` unless the namespace was created
685
        manually without requiring it."""
686
        ts: graphlib.TopologicalSorter = graphlib.TopologicalSorter()
1✔
687

688
        def add_nodes(ns: Namespace) -> None:
1✔
689
            # basilisp.core does actually create a cycle by requiring namespaces
690
            # that require it, so we cannot add it to the topological sorter here,
691
            # or it will throw a cycle error
692
            if ns.name == CORE_NS:
1✔
693
                return
1✔
694

695
            for aliased_ns in ns.aliases.values():
1✔
696
                ts.add(ns, aliased_ns)
1✔
697
                add_nodes(aliased_ns)
1✔
698

699
            for referred_var in ns.refers.values():
1✔
700
                referred_ns = referred_var.ns
1✔
701
                ts.add(ns, referred_ns)
1✔
702
                add_nodes(referred_ns)
1✔
703

704
        add_nodes(self)
1✔
705
        return vec.vector(ts.static_order())
1✔
706

707
    def reload_all(self) -> "Namespace":
1✔
708
        """Reload all dependency namespaces as by `Namespace.reload()`."""
709
        sorted_reload_order = self._get_required_namespaces()
1✔
710
        logger.debug(f"Computed :reload-all order: {sorted_reload_order}")
1✔
711

712
        for ns in sorted_reload_order:
1✔
713
            ns.reload()
1✔
714

715
        return self
1✔
716

717
    def reload(self) -> "Namespace":
1✔
718
        """Reload code in this namespace by reloading the underlying Python module."""
719
        with self._lock:
1✔
720
            importlib.reload(self.module)
1✔
721
            return self
1✔
722

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

727
        This method is called in code generated for the `require*` special form."""
728
        try:
1✔
729
            ns_module = importlib.import_module(munge(ns_name))
1✔
730
        except ModuleNotFoundError as e:
1✔
731
            # Dynamically created namespaces won't have any on-disk modules to
732
            # import, so loading them via the importer will fail. We can check
733
            # for such a namespace here and return the generated module instead.
734
            if (maybe_ns := self.get(sym.symbol(ns_name))) is not None:
1✔
735
                ns_module = maybe_ns.module
1✔
736
            else:
737
                raise ImportError(
1✔
738
                    f"Basilisp namespace '{ns_name}' not found",
739
                ) from e
740

741
        assert isinstance(ns_module, BasilispModule)
1✔
742
        ns_sym = sym.symbol(ns_name)
1✔
743
        ns = self.get(ns_sym)
1✔
744
        assert ns is not None, "Namespace must exist after being required"
1✔
745
        self.add_alias(ns, ns_sym)
1✔
746
        if aliases:
1✔
747
            self.add_alias(ns, *aliases)
1✔
748
        return ns_module
1✔
749

750
    def add_alias(self, namespace: "Namespace", *aliases: sym.Symbol) -> None:
1✔
751
        """Add Symbol aliases for the given Namespace."""
752
        with self._lock:
1✔
753
            new_m = self._aliases
1✔
754
            for alias in aliases:
1✔
755
                new_m = new_m.assoc(alias, namespace)
1✔
756
            self._aliases = new_m
1✔
757

758
    def get_alias(self, alias: sym.Symbol) -> "Optional[Namespace]":
1✔
759
        """Get the Namespace aliased by Symbol or None if it does not exist."""
760
        with self._lock:
1✔
761
            return self._aliases.val_at(alias, None)
1✔
762

763
    def remove_alias(self, alias: sym.Symbol) -> None:
1✔
764
        """Remove the Namespace aliased by Symbol. Return None."""
765
        with self._lock:
1✔
766
            self._aliases = self._aliases.dissoc(alias)
1✔
767

768
    def intern(self, sym: sym.Symbol, var: Var, force: bool = False) -> Var:
1✔
769
        """Intern the Var given in this namespace mapped by the given Symbol.
770
        If the Symbol already maps to a Var, this method _will not overwrite_
771
        the existing Var mapping unless the force keyword argument is given
772
        and is True."""
773
        with self._lock:
1✔
774
            old_var = self._interns.val_at(sym, None)
1✔
775
            if old_var is None or force:
1✔
776
                self._interns = self._interns.assoc(sym, var)
1✔
777
            return self._interns.val_at(sym)
1✔
778

779
    def unmap(self, sym: sym.Symbol) -> None:
1✔
780
        with self._lock:
1✔
781
            self._interns = self._interns.dissoc(sym)
1✔
782

783
    def find(self, sym: sym.Symbol) -> Var | None:
1✔
784
        """Find Vars mapped by the given Symbol input or None if no Vars are
785
        mapped by that Symbol."""
786
        with self._lock:
1✔
787
            v = self._interns.val_at(sym, None)
1✔
788
            if v is None:
1✔
789
                return self._refers.val_at(sym, None)
1✔
790
            return v
1✔
791

792
    def add_import(
1✔
793
        self,
794
        sym: sym.Symbol,
795
        module: Module,
796
        *aliases: sym.Symbol,
797
        refers: dict[sym.Symbol, Any] | None = None,
798
    ) -> None:
799
        """Add the Symbol as an imported Symbol in this Namespace.
800

801
        If aliases are given, the aliases will be associated to the module as well.
802

803
        If a dictionary of refers is provided, add the referred names as import refers.
804
        """
805
        with self._lock:
1✔
806
            self._imports = self._imports.assoc(sym, module)
1✔
807

808
            if refers:
1✔
809
                final_refers = self._import_refers
1✔
810
                for s, v in refers.items():
1✔
811
                    # Filter out dunder names and private names
812
                    if _PRIVATE_NAME_PATTERN.fullmatch(s.name) is not None:
1✔
813
                        logger.debug(f"Ignoring import refer for {sym} member {s}")
1✔
814
                        continue
1✔
815
                    final_refers = final_refers.assoc(s, ImportRefer(sym, v))
1✔
816
                self._import_refers = final_refers
1✔
817

818
            if aliases:
1✔
819
                m = self._import_aliases
1✔
820
                for alias in aliases:
1✔
821
                    m = m.assoc(alias, sym)
1✔
822
                self._import_aliases = m
1✔
823

824
    def get_import_refer(self, sym: sym.Symbol) -> sym.Symbol | None:
1✔
825
        """Get the Python module member name referred by Symbol or None if it does not
826
        exist."""
827
        with self._lock:
1✔
828
            refer = self._import_refers.val_at(sym, None)
1✔
829
            return refer.module_name if refer is not None else None
1✔
830

831
    def get_import(self, sym: sym.Symbol) -> BasilispModule | None:
1✔
832
        """Return the module if a module named by sym has been imported into
833
        this Namespace, None otherwise.
834

835
        First try to resolve a module directly with the given name. If no module
836
        can be resolved, attempt to resolve the module using import aliases."""
837
        with self._lock:
1✔
838
            mod = self._imports.val_at(sym, None)
1✔
839
            if mod is None:
1✔
840
                alias = self._import_aliases.get(sym, None)
1✔
841
                if alias is None:
1✔
842
                    return None
1✔
843
                return self._imports.val_at(alias, None)
1✔
844
            return mod
1✔
845

846
    def add_refer(self, sym: sym.Symbol, var: Var) -> None:
1✔
847
        """Refer var in this namespace under the name sym."""
848
        if not var.is_private:
1✔
849
            with self._lock:
1✔
850
                self._refers = self._refers.assoc(sym, var)
1✔
851

852
    def get_refer(self, sym: sym.Symbol) -> Var | None:
1✔
853
        """Get the Var referred by Symbol or None if it does not exist."""
854
        with self._lock:
1✔
855
            return self._refers.val_at(sym, None)
1✔
856

857
    def refer_all(self, other_ns: "Namespace") -> None:
1✔
858
        """Refer all the Vars in the other namespace."""
859
        with self._lock:
1✔
860
            final_refers = self._refers
1✔
861
            for s, var in other_ns.interns.items():
1✔
862
                if not var.is_private:
1✔
863
                    final_refers = final_refers.assoc(s, var)
1✔
864
            self._refers = final_refers
1✔
865

866
    @classmethod
1✔
867
    def ns_cache(cls) -> lmap.PersistentMap:
1✔
868
        """Return a snapshot of the Namespace cache."""
869
        return cls._NAMESPACES.deref()
×
870

871
    @staticmethod
1✔
872
    def __get_or_create(
1✔
873
        ns_cache: NamespaceMap,
874
        name: sym.Symbol,
875
        module: BasilispModule | None = None,
876
    ) -> lmap.PersistentMap:
877
        """Private swap function used by `get_or_create` to atomically swap
878
        the new namespace map into the global cache."""
879
        ns = ns_cache.val_at(name, None)
1✔
880
        if ns is not None:
1✔
881
            return ns_cache
1✔
882
        new_ns = Namespace(name, module=module)
1✔
883

884
        # If this is a new namespace and we're given a module (typically from the
885
        # importer), attach the namespace to the module.
886
        if module is not None:
1✔
887
            logger.debug(f"Setting module '{module}' namespace to '{new_ns}'")
1✔
888
            module.__basilisp_namespace__ = new_ns
1✔
889

890
        # The `ns` macro is important for setting up a new namespace, but it becomes
891
        # available only after basilisp.core has been loaded.
892
        ns_var = Var.find_in_ns(CORE_NS_SYM, sym.symbol("ns"))
1✔
893
        if ns_var:
1✔
894
            new_ns.add_refer(sym.symbol("ns"), ns_var)
1✔
895

896
        return ns_cache.assoc(name, new_ns)
1✔
897

898
    @classmethod
1✔
899
    def get_or_create(
1✔
900
        cls, name: sym.Symbol, module: BasilispModule | None = None
901
    ) -> "Namespace":
902
        """Get the namespace bound to the symbol `name` in the global namespace
903
        cache, creating it if it does not exist.
904
        Return the namespace."""
905
        if (
1✔
906
            module is None
907
            and (module_var := Var.find(IMPORT_MODULE_VAR_SYM)) is not None
908
        ):
909
            module = module_var.value
1✔
910
            assert module is None or isinstance(module, BasilispModule)
1✔
911
        return cls._NAMESPACES.swap(Namespace.__get_or_create, name, module=module)[
1✔
912
            name
913
        ]
914

915
    @classmethod
1✔
916
    def get(cls, name: sym.Symbol) -> "Optional[Namespace]":
1✔
917
        """Get the namespace bound to the symbol `name` in the global namespace
918
        cache. Return the namespace if it exists or None otherwise.."""
919
        return cls._NAMESPACES.deref().val_at(name, None)
1✔
920

921
    @classmethod
1✔
922
    def remove(cls, name: sym.Symbol) -> Optional["Namespace"]:
1✔
923
        """Remove the namespace bound to the symbol `name` in the global
924
        namespace cache and return that namespace.
925
        Return None if the namespace did not exist in the cache."""
926
        if name == CORE_NS_SYM:
1✔
927
            raise ValueError("Cannot remove the Basilisp core namespace")
1✔
928
        while True:
1✔
929
            oldval: lmap.PersistentMap = cls._NAMESPACES.deref()
1✔
930
            ns: Namespace | None = oldval.val_at(name, None)
1✔
931
            newval = oldval
1✔
932
            if ns is not None:
1✔
933
                newval = oldval.dissoc(name)
1✔
934
            if cls._NAMESPACES.compare_and_set(oldval, newval):
1✔
935
                return ns
1✔
936

937
    # REPL Completion support
938

939
    @staticmethod
1✔
940
    def __completion_matcher(text: str) -> CompletionMatcher:
1✔
941
        """Return a function which matches any symbol keys from map entries
942
        against the given text."""
943

944
        def is_match(entry: tuple[sym.Symbol, Any]) -> bool:
1✔
945
            return entry[0].name.startswith(text)
1✔
946

947
        return is_match
1✔
948

949
    def __complete_alias(
1✔
950
        self, prefix: str, name_in_ns: str | None = None
951
    ) -> Iterable[str]:
952
        """Return an iterable of possible completions matching the given
953
        prefix from the list of aliased namespaces. If name_in_ns is given,
954
        further attempt to refine the list to matching names in that namespace."""
955
        candidates = filter(
1✔
956
            Namespace.__completion_matcher(prefix),
957
            ((s, n) for s, n in self.aliases.items()),
958
        )
959
        if name_in_ns is not None:
1✔
960
            for _, candidate_ns in candidates:
1✔
961
                for match in candidate_ns.__complete_interns(
1✔
962
                    name_in_ns, include_private_vars=False
963
                ):
964
                    yield f"{prefix}/{match}"
1✔
965
        else:
966
            for alias, _ in candidates:
1✔
967
                yield f"{alias}/"
1✔
968

969
    def __complete_imports_and_aliases(
1✔
970
        self, prefix: str, name_in_module: str | None = None
971
    ) -> Iterable[str]:
972
        """Return an iterable of possible completions matching the given
973
        prefix from the list of imports and aliased imports. If name_in_module
974
        is given, further attempt to refine the list to matching names in that
975
        namespace."""
976
        imports = self.imports
1✔
977
        aliases = lmap.map(
1✔
978
            {
979
                alias: imports.val_at(import_name)
980
                for alias, import_name in self.import_aliases.items()
981
            }
982
        )
983

984
        candidates = filter(
1✔
985
            Namespace.__completion_matcher(prefix),
986
            itertools.chain(
987
                aliases.items(), imports.items(), [(sym.symbol("python"), builtins)]
988
            ),
989
        )
990
        if name_in_module is not None:
1✔
991
            for _, module in candidates:
1✔
992
                for name in module.__dict__:
1✔
993
                    if name.startswith(name_in_module):
1✔
994
                        yield f"{prefix}/{name}"
1✔
995
        else:
996
            for candidate_name, _ in candidates:
1✔
997
                yield f"{candidate_name}/"
1✔
998

999
    def __complete_interns(
1✔
1000
        self, value: str, include_private_vars: bool = True
1001
    ) -> Iterable[str]:
1002
        """Return an iterable of possible completions matching the given
1003
        prefix from the list of interned Vars."""
1004
        if include_private_vars:
1✔
1005
            is_match = Namespace.__completion_matcher(value)
1✔
1006
        else:
1007
            _is_match = Namespace.__completion_matcher(value)
1✔
1008

1009
            def is_match(entry: tuple[sym.Symbol, Var]) -> bool:
1✔
1010
                return _is_match(entry) and not entry[1].is_private
1✔
1011

1012
        return map(
1✔
1013
            lambda entry: f"{entry[0].name}",
1014
            filter(is_match, ((s, v) for s, v in self.interns.items())),
1015
        )
1016

1017
    def __complete_refers(self, value: str) -> Iterable[str]:
1✔
1018
        """Return an iterable of possible completions matching the given prefix from
1019
        the list of referred Vars and referred Python module members."""
1020
        return map(
1✔
1021
            lambda entry: f"{entry[0].name}",
1022
            filter(
1023
                Namespace.__completion_matcher(value),
1024
                itertools.chain(
1025
                    ((s, v) for s, v in self.refers.items()),
1026
                    ((s, v) for s, v in self.import_refers.items()),
1027
                ),
1028
            ),
1029
        )
1030

1031
    def complete(self, text: str) -> Iterable[str]:
1✔
1032
        """Return an iterable of possible completions for the given text in
1033
        this namespace."""
1034
        assert not text.startswith(":")
1✔
1035

1036
        if "/" in text:
1✔
1037
            prefix, suffix = text.split("/", maxsplit=1)
1✔
1038
            results = itertools.chain(
1✔
1039
                self.__complete_alias(prefix, name_in_ns=suffix),
1040
                self.__complete_imports_and_aliases(prefix, name_in_module=suffix),
1041
            )
1042
        else:
1043
            results = itertools.chain(
1✔
1044
                self.__complete_alias(text),
1045
                self.__complete_imports_and_aliases(text),
1046
                self.__complete_interns(text),
1047
                self.__complete_refers(text),
1048
            )
1049

1050
        return results
1✔
1051

1052

1053
def get_thread_bindings() -> IPersistentMap[Var, Any]:
1✔
1054
    """Return the current thread-local bindings."""
1055
    bindings = {}
1✔
1056
    for frame in _THREAD_BINDINGS.get_bindings():
1✔
1057
        bindings.update({var: var.value for var in frame})
1✔
1058
    return lmap.map(bindings)
1✔
1059

1060

1061
def push_thread_bindings(m: IPersistentMap[Var, Any]) -> None:
1✔
1062
    """Push thread local bindings for the Var keys in m using the values."""
1063
    bindings = set()
1✔
1064

1065
    for var, val in m.items():
1✔
1066
        if not var.dynamic:
1✔
1067
            raise RuntimeException(
1✔
1068
                "cannot set thread-local bindings for non-dynamic Var"
1069
            )
1070
        var.push_bindings(val)
1✔
1071
        bindings.add(var)
1✔
1072

1073
    _THREAD_BINDINGS.push_bindings(lset.set(bindings))
1✔
1074

1075

1076
def pop_thread_bindings() -> None:
1✔
1077
    """Pop the thread local bindings set by push_thread_bindings above."""
1078
    try:
1✔
1079
        bindings = _THREAD_BINDINGS.pop_bindings()
1✔
1080
    except IndexError as e:
1✔
1081
        raise RuntimeException(
1✔
1082
            "cannot pop thread-local bindings without prior push"
1083
        ) from e
1084

1085
    for var in bindings:
1✔
1086
        var.pop_bindings()
1✔
1087

1088

1089
###################
1090
# Runtime Support #
1091
###################
1092

1093
T = TypeVar("T")
1✔
1094

1095

1096
@functools.singledispatch
1✔
1097
def to_set(s):
1✔
1098
    return lset.set(s)
1✔
1099

1100

1101
@to_set.register(type(None))
1✔
1102
def _to_set_none(_: None) -> lset.PersistentSet:
1✔
1103
    return lset.EMPTY
1✔
1104

1105

1106
@to_set.register(IPersistentMap)
1✔
1107
def _to_set_map(m: IPersistentMap) -> lset.PersistentSet:
1✔
1108
    if (s := m.seq()) is None:
1✔
1109
        return lset.EMPTY
1✔
1110
    return to_set(s)
1✔
1111

1112

1113
@functools.singledispatch
1✔
1114
def vector(v):
1✔
1115
    return vec.vector(v)
1✔
1116

1117

1118
@vector.register(type(None))
1✔
1119
def _vector_none(_: None) -> vec.PersistentVector:
1✔
1120
    return vec.EMPTY
1✔
1121

1122

1123
@vector.register(IPersistentMap)
1✔
1124
def _vector_map(m: IPersistentMap) -> vec.PersistentVector:
1✔
1125
    if (s := m.seq()) is None:
1✔
1126
        return vec.EMPTY
1✔
1127
    return vector(s)
1✔
1128

1129

1130
def keyword(name: Any, ns: Any = None) -> kw.Keyword:
1✔
1131
    """Return a new keyword with runtime type checks for name and namespace."""
1132
    if not isinstance(name, str):
1✔
1133
        raise TypeError(f"Keyword name must be a string, not '{type(name)}'")
1✔
1134
    if not isinstance(ns, (type(None), str)):
1✔
1135
        raise TypeError(f"Keyword namespace must be a string or nil, not '{type(ns)}")
×
1136
    return kw.keyword(name, ns)
1✔
1137

1138

1139
@functools.singledispatch
1✔
1140
def keyword_from_name(o: Any) -> NoReturn:
1✔
1141
    raise TypeError(f"Cannot create keyword from '{type(o)}'")
1✔
1142

1143

1144
@keyword_from_name.register(type(None))  # type: ignore[arg-type]
1✔
1145
def _keyword_from_name_none(_: None) -> None:
1✔
1146
    return None
1✔
1147

1148

1149
@keyword_from_name.register(kw.Keyword)
1✔
1150
def _keyword_from_name_keyword(o: kw.Keyword) -> kw.Keyword:
1✔
1151
    return o
1✔
1152

1153

1154
@keyword_from_name.register(sym.Symbol)
1✔
1155
def _keyword_from_name_symbol(o: sym.Symbol) -> kw.Keyword:
1✔
1156
    return kw.keyword(o.name, ns=o.ns)
1✔
1157

1158

1159
@keyword_from_name.register(str)
1✔
1160
def _keyword_from_name_str(o: str) -> kw.Keyword:
1✔
1161
    if "/" in o and o != "/":
1✔
1162
        ns, name = o.split("/", maxsplit=1)
1✔
1163
        return kw.keyword(name, ns=ns)
1✔
1164
    return kw.keyword(o)
1✔
1165

1166

1167
def symbol(name: Any, ns: Any = None) -> sym.Symbol:
1✔
1168
    """Return a new symbol with runtime type checks for name and namespace."""
1169
    if not isinstance(name, str):
1✔
1170
        raise TypeError(f"Symbol name must be a string, not '{type(name)}'")
1✔
1171
    if not isinstance(ns, (type(None), str)):
1✔
1172
        raise TypeError(f"Symbol namespace must be a string or nil, not '{type(ns)}")
×
1173
    return sym.symbol(name, ns)
1✔
1174

1175

1176
@functools.singledispatch
1✔
1177
def symbol_from_name(o: Any) -> NoReturn:
1✔
1178
    raise TypeError(f"Cannot create symbol from '{type(o)}'")
1✔
1179

1180

1181
@symbol_from_name.register(kw.Keyword)
1✔
1182
def _symbol_from_name_keyword(o: kw.Keyword) -> sym.Symbol:
1✔
1183
    return sym.symbol(o.name, ns=o.ns)
1✔
1184

1185

1186
@symbol_from_name.register(sym.Symbol)
1✔
1187
def _symbol_from_name_symbol(o: sym.Symbol) -> sym.Symbol:
1✔
1188
    return o
1✔
1189

1190

1191
@symbol_from_name.register(str)
1✔
1192
def _symbol_from_name_str(o: str) -> sym.Symbol:
1✔
1193
    if "/" in o and o != "/":
1✔
1194
        ns, name = o.split("/", maxsplit=1)
1✔
1195
        return sym.symbol(name, ns=ns)
1✔
1196
    return sym.symbol(o)
1✔
1197

1198

1199
@symbol_from_name.register(Var)
1✔
1200
def _symbol_from_name_var(o: Var) -> sym.Symbol:
1✔
1201
    return sym.symbol(o.name.name, ns=o.ns.name)
1✔
1202

1203

1204
@functools.singledispatch
1✔
1205
def first(o):
1✔
1206
    """If o is a ISeq, return the first element from o. If o is None, return
1207
    None. Otherwise, coerces o to a Seq and returns the first."""
1208
    s = to_seq(o)
1✔
1209
    if s is None:
1✔
1210
        return None
1✔
1211
    return s.first
1✔
1212

1213

1214
@first.register(type(None))
1✔
1215
def _first_none(_: None) -> None:
1✔
1216
    return None
1✔
1217

1218

1219
@first.register(ISeq)
1✔
1220
def _first_iseq(o: ISeq[T]) -> T | None:
1✔
1221
    return o.first
1✔
1222

1223

1224
@functools.singledispatch
1✔
1225
def rest(o: Any) -> ISeq:
1✔
1226
    """If o is a ISeq, return the elements after the first in o. If o is None,
1227
    returns an empty seq. Otherwise, coerces o to a seq and returns the rest."""
1228
    n = to_seq(o)
1✔
1229
    if n is None:
1✔
1230
        return lseq.EMPTY
1✔
1231
    return n.rest
1✔
1232

1233

1234
@rest.register(type(None))
1✔
1235
def _rest_none(_: None) -> ISeq:
1✔
1236
    return lseq.EMPTY
1✔
1237

1238

1239
@rest.register(type(ISeq))
1✔
1240
def _rest_iseq(o: ISeq[T]) -> ISeq:
1✔
1241
    s = o.rest
×
1242
    if s is None:
×
1243
        return lseq.EMPTY
×
1244
    return s
×
1245

1246

1247
def nthrest(coll, i: int):
1✔
1248
    """Returns the nth rest sequence of coll, or coll if i is 0."""
1249
    while True:
1✔
1250
        if coll is None:
1✔
1251
            return None
1✔
1252
        if i <= 0:
1✔
1253
            return coll
1✔
1254
        i -= 1
1✔
1255
        coll = rest(coll)
1✔
1256

1257

1258
def next_(o) -> ISeq | None:
1✔
1259
    """Calls rest on o. If o returns an empty sequence or None, returns None.
1260
    Otherwise, returns the elements after the first in o."""
1261
    return to_seq(rest(o))
1✔
1262

1263

1264
def nthnext(coll, i: int) -> ISeq | None:
1✔
1265
    """Returns the nth next sequence of coll."""
1266
    while True:
1✔
1267
        if coll is None:
1✔
1268
            return None
1✔
1269
        if i <= 0:
1✔
1270
            return to_seq(coll)
1✔
1271
        i -= 1
1✔
1272
        coll = next_(coll)
1✔
1273

1274

1275
@functools.singledispatch
1✔
1276
def _cons(seq, o) -> ISeq:
1✔
1277
    return lseq.Cons(o, to_seq(seq))
1✔
1278

1279

1280
@_cons.register(type(None))
1✔
1281
def _cons_none(_: None, o: T) -> ISeq[T]:
1✔
1282
    return llist.l(o)
1✔
1283

1284

1285
@_cons.register(ISeq)
1✔
1286
def _cons_iseq(seq: ISeq, o) -> ISeq:
1✔
1287
    return lseq.Cons(o, seq)
1✔
1288

1289

1290
def cons(o, seq) -> ISeq:
1✔
1291
    """Creates a new sequence where o is the first element and seq is the rest.
1292
    If seq is None, return a list containing o. If seq is not a ISeq, attempt
1293
    to coerce it to a ISeq and then cons o onto the resulting sequence."""
1294
    return _cons(seq, o)
1✔
1295

1296

1297
to_seq = lseq.to_seq
1✔
1298

1299
to_iterator_seq = lseq.iterator_sequence
1✔
1300

1301

1302
def is_reiterable_iterable(x: Any) -> bool:
1✔
1303
    """Return ``true`` if x is a re-iterable Iterable object."""
1304
    return isinstance(x, Iterable) and iter(x) is not x
1✔
1305

1306

1307
T_concat = TypeVar("T_concat")
1✔
1308

1309

1310
def concat_from_seq(seqs: Iterable[Iterable[T_concat] | None] | None) -> ISeq[T_concat]:
1✔
1311
    """Given a seq of seqs, return a flat seq."""
1312
    if seqs is None:
1✔
1313
        return lseq.EMPTY
1✔
1314
    return lseq.iterator_sequence(
1✔
1315
        itertools.chain.from_iterable(filter(None, map(to_seq, seqs)))
1316
    )
1317

1318

1319
def concat(*seqs: Iterable[T_concat] | None) -> ISeq[T_concat]:
1✔
1320
    """Concatenate the sequences given by seqs into a single ISeq."""
1321
    return concat_from_seq(seqs)
1✔
1322

1323

1324
T_reduce_init = TypeVar("T_reduce_init")
1✔
1325

1326

1327
@functools.singledispatch
1✔
1328
def internal_reduce(
1✔
1329
    coll: Any,
1330
    f: ReduceFunction,
1331
    init: T_reduce_init | object = IReduce.REDUCE_SENTINEL,
1332
) -> T_reduce_init:
1333
    raise TypeError(f"Type {type(coll)} cannot be reduced")
1✔
1334

1335

1336
@internal_reduce.register(collections.abc.Iterable)
1✔
1337
@internal_reduce.register(type(None))
1✔
1338
def _internal_reduce_iterable(
1✔
1339
    coll: Iterable[T] | None,
1340
    f: ReduceFunction[T_reduce_init, T],
1341
    init: T_reduce_init | object = IReduce.REDUCE_SENTINEL,
1342
) -> T_reduce_init:
1343
    s = to_seq(coll)
1✔
1344

1345
    if init is IReduce.REDUCE_SENTINEL:
1✔
1346
        if s is None:
1✔
1347
            return f()
1✔
1348

1349
        init, s = s.first, s.rest
1✔
1350
    else:
1351
        if s is None:
1✔
1352
            return cast(T_reduce_init, init)
1✔
1353

1354
    res = cast(T_reduce_init, init)
1✔
1355
    for item in s:
1✔
1356
        res = f(res, item)
1✔
1357
        if isinstance(res, Reduced):
1✔
1358
            return res.deref()
1✔
1359

1360
    return res
1✔
1361

1362

1363
@internal_reduce.register(IReduce)
1✔
1364
def _internal_reduce_ireduce(
1✔
1365
    coll: IReduce,
1366
    f: ReduceFunction[T_reduce_init, T],
1367
    init: T_reduce_init | object = IReduce.REDUCE_SENTINEL,
1368
) -> T_reduce_init:
1369
    if init is IReduce.REDUCE_SENTINEL:
1✔
1370
        return coll.reduce(f)
1✔
1371
    return coll.reduce(f, cast(T_reduce_init, init))
1✔
1372

1373

1374
def apply(f: Callable, args: ISeq):
1✔
1375
    """Apply function f to the arguments provided.
1376
    The last argument must always be coercible to a Seq. Intermediate
1377
    arguments are not modified.
1378
    For example:
1379
        (apply max [1 2 3])   ;=> 3
1380
        (apply max 4 [1 2 3]) ;=> 4"""
1381
    try:
1✔
1382
        *final, last = list(args)
1✔
NEW
1383
    except ValueError:
×
NEW
1384
        final, last = [], None
×
1385

1386
    s = to_seq(last)
1✔
1387
    if s is not None:
1✔
1388
        if getattr(f, "_basilisp_fn", False) and hasattr(f, "apply_to"):
1✔
1389
            return f.apply_to(final, s)
1✔
1390
        else:
1391
            final.extend(s)
1✔
1392

1393
    return f(*final)
1✔
1394

1395

1396
def apply_kw(f: Callable, args: ISeq):
1✔
1397
    """Apply function f to the arguments provided.
1398
    The last argument must always be coercible to a Mapping. Intermediate
1399
    arguments are not modified.
1400
    For example:
1401
        (apply python/dict {:a 1} {:b 2})   ;=> #py {:a 1 :b 2}
1402
        (apply python/dict {:a 1} {:a 2})   ;=> #py {:a 2}"""
1403
    try:
1✔
1404
        *final, last = list(args)
1✔
NEW
1405
    except ValueError:
×
NEW
1406
        final, last = [], None
×
1407

1408
    if last is None:
1✔
1409
        kwargs = {}
1✔
1410
    else:
1411
        kwargs = {
1✔
1412
            to_py(k, lambda kw: munge(kw.name, allow_builtins=True)): v
1413
            for k, v in last.items()
1414
        }
1415

1416
    return f(*final, **kwargs)
1✔
1417

1418

1419
@functools.singledispatch
1✔
1420
def count(coll) -> int:
1✔
1421
    if isinstance(coll, Iterable) and iter(coll) is coll:
1✔
1422
        raise TypeError(
1✔
1423
            "The count function is not supported on single-use iterable objects because "
1424
            "it would exhaust them during counting. Please use iterator-seq to coerce "
1425
            f"them into sequences first. Iterable Object type: {type(coll)}"
1426
        )
1427

1428
    try:
1✔
1429
        return sum(1 for _ in coll)
1✔
1430
    except TypeError as e:
×
1431
        raise TypeError(f"count not supported on object of type {type(coll)}") from e
×
1432

1433

1434
@count.register(type(None))
1✔
1435
def _count_none(_: None) -> int:
1✔
1436
    return 0
1✔
1437

1438

1439
@count.register(Sized)
1✔
1440
def _count_sized(coll: Sized):
1✔
1441
    return len(coll)
1✔
1442

1443

1444
__nth_sentinel = object()
1✔
1445

1446

1447
@functools.singledispatch
1✔
1448
def nth(coll, i: int, notfound=__nth_sentinel):
1✔
1449
    """Returns the ith element of coll (0-indexed), if it exists.
1450
    None otherwise. If i is out of bounds, throws an IndexError unless
1451
    notfound is specified."""
1452
    raise TypeError(f"nth not supported on object of type {type(coll)}")
1✔
1453

1454

1455
@nth.register(type(None))
1✔
1456
def _nth_none(_: None, i: int, notfound=__nth_sentinel) -> None:
1✔
1457
    return notfound if notfound is not __nth_sentinel else None  # type: ignore[return-value]
1✔
1458

1459

1460
@nth.register(Sequence)
1✔
1461
def _nth_sequence(coll: Sequence, i: int, notfound=__nth_sentinel):
1✔
1462
    try:
1✔
1463
        return coll[i]
1✔
1464
    except IndexError as ex:
1✔
1465
        if notfound is not __nth_sentinel:
1✔
1466
            return notfound
1✔
1467
        raise ex
1✔
1468

1469

1470
@nth.register(ISeq)
1✔
1471
def _nth_iseq(coll: ISeq, i: int, notfound=__nth_sentinel):
1✔
1472
    for j, e in enumerate(coll):
1✔
1473
        if i == j:
1✔
1474
            return e
1✔
1475

1476
    if notfound is not __nth_sentinel:
1✔
1477
        return notfound
1✔
1478

1479
    raise IndexError(f"Index {i} out of bounds")
1✔
1480

1481

1482
@functools.singledispatch
1✔
1483
def contains(coll, k) -> bool:
1✔
1484
    """Return true if o contains the key k."""
1485
    return k in coll
1✔
1486

1487

1488
@contains.register(type(None))
1✔
1489
def _contains_none(_, __) -> bool:
1✔
1490
    return False
1✔
1491

1492

1493
@contains.register(str)
1✔
1494
def _contains_str(s: str, k: Any) -> bool:
1✔
1495
    if isinstance(k, int):
1✔
1496
        return 0 <= k < len(s)
1✔
1497
    elif isinstance(k, str):
1✔
1498
        return k in s
1✔
1499
    else:
1500
        raise TypeError(f"contains? key must be a string; got '{type(k)}'")
1✔
1501

1502

1503
@contains.register(IAssociative)
1✔
1504
def _contains_iassociative(coll: IAssociative, k) -> bool:
1✔
1505
    return coll.contains(k)
1✔
1506

1507

1508
@contains.register(ITransientAssociative)
1✔
1509
def _contains_itransientassociative(coll: ITransientAssociative, k) -> bool:
1✔
1510
    return coll.contains_transient(k)
1✔
1511

1512

1513
@functools.singledispatch
1✔
1514
def get(m, k, default=None):  # pylint: disable=unused-argument
1✔
1515
    """Return the value of k in m. Return default if k not found in m."""
1516
    return default
1✔
1517

1518

1519
@get.register(bytes)
1✔
1520
@get.register(dict)
1✔
1521
@get.register(list)
1✔
1522
@get.register(str)
1✔
1523
@get.register(bytes)
1✔
1524
def _get_others(m, k, default=None):
1✔
1525
    try:
1✔
1526
        return m[k]
1✔
1527
    except (KeyError, IndexError, TypeError):
1✔
1528
        return default
1✔
1529

1530

1531
@get.register(IPersistentSet)
1✔
1532
@get.register(ITransientSet)
1✔
1533
@get.register(frozenset)
1✔
1534
@get.register(set)
1✔
1535
def _get_settypes(m, k, default=None):
1✔
1536
    if k in m:
1✔
1537
        return k
1✔
1538
    return default
1✔
1539

1540

1541
@get.register(ILookup)
1✔
1542
def _get_ilookup(m, k, default=None):
1✔
1543
    return m.val_at(k, default)
1✔
1544

1545

1546
@functools.singledispatch
1✔
1547
def assoc(m, *kvs):
1✔
1548
    """Associate keys to values in associative data structure m. If m is None,
1549
    returns a new Map with key-values kvs."""
1550
    raise TypeError(
1✔
1551
        f"Object of type {type(m)} does not implement IAssociative interface"
1552
    )
1553

1554

1555
@assoc.register(type(None))
1✔
1556
def _assoc_none(_: None, *kvs) -> lmap.PersistentMap:
1✔
1557
    return lmap.EMPTY.assoc(*kvs)
1✔
1558

1559

1560
@assoc.register(IAssociative)
1✔
1561
def _assoc_iassociative(m: IAssociative, *kvs):
1✔
1562
    return m.assoc(*kvs)
1✔
1563

1564

1565
@functools.singledispatch
1✔
1566
def update(m, k, f, *args):
1✔
1567
    """Updates the value for key k in associative data structure m with the return value from
1568
    calling f(old_v, *args). If m is None, use an empty map. If k is not in m, old_v will be
1569
    None."""
1570
    raise TypeError(
1✔
1571
        f"Object of type {type(m)} does not implement IAssociative interface"
1572
    )
1573

1574

1575
@update.register(type(None))
1✔
1576
def _update_none(_: None, k, f, *args) -> lmap.PersistentMap:
1✔
1577
    return lmap.EMPTY.assoc(k, f(None, *args))
1✔
1578

1579

1580
@update.register(IAssociative)
1✔
1581
def _update_iassociative(m: IAssociative, k, f, *args):
1✔
1582
    old_v = m.val_at(k)
1✔
1583
    new_v = f(old_v, *args)
1✔
1584
    return m.assoc(k, new_v)
1✔
1585

1586

1587
@functools.singledispatch
1✔
1588
def keys(o):
1✔
1589
    raise TypeError(f"Object of type {type(o)} cannot be coerced to a key sequence")
1✔
1590

1591

1592
@keys.register(type(None))
1✔
1593
def _keys_none(_: None) -> None:
1✔
1594
    return None
1✔
1595

1596

1597
@keys.register(collections.abc.Iterable)
1✔
1598
@keys.register(ISeqable)
1✔
1599
def _keys_iterable(o: ISeqable | Iterable) -> ISeq | None:
1✔
1600
    return keys(to_seq(o))
1✔
1601

1602

1603
@keys.register(ISeq)
1✔
1604
def _keys_iseq(o: ISeq) -> ISeq | None:
1✔
1605
    if to_seq(o) is None:
1✔
1606
        return None
1✔
1607

1608
    def _key_seq(s: ISeq) -> ISeq | None:
1✔
1609
        if to_seq(s) is not None:
1✔
1610
            e = s.first
1✔
1611
            if not isinstance(e, IMapEntry):
1✔
1612
                raise TypeError(
1✔
1613
                    f"Object of type {type(e)} cannot be coerced to a map entry"
1614
                )
1615
            return lseq.Cons(e.key, lseq.LazySeq(lambda: _key_seq(s.rest)))
1✔
1616
        return None
1✔
1617

1618
    return lseq.LazySeq(lambda: _key_seq(o))
1✔
1619

1620

1621
@keys.register(collections.abc.Mapping)
1✔
1622
def _keys_mapping(o: Mapping) -> ISeq | None:
1✔
1623
    return to_seq(o.keys())
1✔
1624

1625

1626
@functools.singledispatch
1✔
1627
def vals(o):
1✔
1628
    raise TypeError(f"Object of type {type(o)} cannot be coerced to a value sequence")
1✔
1629

1630

1631
@vals.register(type(None))
1✔
1632
def _vals_none(_: None) -> None:
1✔
1633
    return None
1✔
1634

1635

1636
@vals.register(str)
1✔
1637
def _vals_str(o: str) -> None:
1✔
1638
    if to_seq(o) is None:
1✔
1639
        return None
1✔
1640
    raise TypeError(f"Object of type {type(o)} cannot be coerced to a value sequence")
1✔
1641

1642

1643
@keys.register(collections.abc.Iterable)
1✔
1644
@vals.register(ISeqable)
1✔
1645
def _vals_iterable(o: ISeqable | Iterable) -> ISeq | None:
1✔
1646
    return vals(to_seq(o))
1✔
1647

1648

1649
@vals.register(ISeq)
1✔
1650
def _vals_iseq(o: ISeq) -> ISeq | None:
1✔
1651
    if to_seq(o) is None:
1✔
1652
        return None
1✔
1653

1654
    def _val_seq(s: ISeq) -> ISeq | None:
1✔
1655
        if to_seq(s) is not None:
1✔
1656
            e = s.first
1✔
1657
            if not isinstance(e, IMapEntry):
1✔
1658
                raise TypeError(
1✔
1659
                    f"Object of type {type(e)} cannot be coerced to a map entry"
1660
                )
1661
            return lseq.Cons(e.value, lseq.LazySeq(lambda: _val_seq(s.rest)))
1✔
1662
        return None
1✔
1663

1664
    return lseq.LazySeq(lambda: _val_seq(o))
1✔
1665

1666

1667
@vals.register(collections.abc.Mapping)
1✔
1668
def _vals_mapping(o: Mapping) -> ISeq | None:
1✔
1669
    return to_seq(o.values())
1✔
1670

1671

1672
@functools.singledispatch
1✔
1673
def conj(coll, *xs):
1✔
1674
    """Conjoin xs to collection. New elements may be added in different positions
1675
    depending on the type of coll. conj returns the same type as coll. If coll
1676
    is None, return a list with xs conjoined."""
1677
    raise TypeError(
1✔
1678
        f"Object of type {type(coll)} does not implement "
1679
        "IPersistentCollection interface"
1680
    )
1681

1682

1683
@conj.register(type(None))
1✔
1684
def _conj_none(_: None, *xs):
1✔
1685
    l = llist.EMPTY
1✔
1686
    return l.cons(*xs)
1✔
1687

1688

1689
@conj.register(IPersistentCollection)
1✔
1690
def _conj_ipersistentcollection(coll: IPersistentCollection, *xs):
1✔
1691
    return coll.cons(*xs)
1✔
1692

1693

1694
def _update_signature_for_partial(f: BasilispFunction, num_args: int) -> None:
1✔
1695
    """Update the various properties of a Basilisp function for wrapped partials.
1696

1697
    Partial applications change the number of arities a function appears to have.
1698
    This function computes the new `arities` set for the partial function by removing
1699
    any now-invalid fixed arities from the original function's set.
1700

1701
    Additionally, partials do not take the meta from the wrapped function, so that
1702
    value should be cleared and the `with-meta` method should be replaced with a
1703
    new method."""
1704
    existing_arities: IPersistentSet[kw.Keyword | int] = f.arities
1✔
1705
    new_arities: set[kw.Keyword | int] = set()
1✔
1706
    for arity in existing_arities:
1✔
1707
        if isinstance(arity, kw.Keyword):
1✔
1708
            new_arities.add(arity)
1✔
1709
        elif arity > num_args:
1✔
1710
            new_arities.add(arity - num_args)
1✔
1711
    if not new_arities:
1✔
1712
        if num_args in existing_arities:
1✔
1713
            new_arities.add(0)
1✔
1714
        else:
1715
            logger.warning(
1✔
1716
                f"invalid partial function application of '{f.__name__}' detected: "  # type: ignore[attr-defined]
1717
                f"{num_args} arguments given; expected any of: "
1718
                f"{', '.join(sorted(map(str, existing_arities)))}"
1719
            )
1720
    f.apply_to = _fn_apply_to(  # type: ignore[method-assign, assignment]
1✔
1721
        f,
1722
        tuple(new_arities),
1723
        max_fixed_arity=max(
1724
            (arity for arity in new_arities if isinstance(arity, int)), default=None
1725
        ),
1726
    )
1727
    f.arities = lset.set(new_arities)
1✔
1728
    f.meta = None
1✔
1729
    f.with_meta = partial(_fn_with_meta, f)  # type: ignore[method-assign]
1✔
1730

1731

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

1735
    @functools.wraps(f)
1✔
1736
    def partial_f(*inner_args, **inner_kwargs):
1✔
1737
        return f(*args, *inner_args, **{**kwargs, **inner_kwargs})
1✔
1738

1739
    if hasattr(partial_f, "_basilisp_fn"):
1✔
1740
        _update_signature_for_partial(cast(BasilispFunction, partial_f), len(args))
1✔
1741

1742
    return partial_f
1✔
1743

1744

1745
@functools.singledispatch
1✔
1746
def deref(o, timeout_ms=None, timeout_val=None):
1✔
1747
    """Dereference a Deref object and return its contents.
1748

1749
    If o is an object implementing IBlockingDeref and timeout_ms and
1750
    timeout_val are supplied, deref will wait at most timeout_ms milliseconds,
1751
    returning timeout_val if timeout_ms milliseconds elapse and o has not
1752
    returned."""
1753
    raise TypeError(f"Object of type {type(o)} cannot be dereferenced")
1✔
1754

1755

1756
@deref.register(IBlockingDeref)
1✔
1757
def _deref_blocking(o: IBlockingDeref, timeout_ms: int | None = None, timeout_val=None):
1✔
1758
    timeout_s = None
1✔
1759
    if timeout_ms is not None:
1✔
1760
        timeout_s = timeout_ms / 1000 if timeout_ms != 0 else 0
1✔
1761
    return o.deref(timeout_s, timeout_val)
1✔
1762

1763

1764
@deref.register(IDeref)
1✔
1765
def _deref(o: IDeref):
1✔
1766
    return o.deref()
1✔
1767

1768

1769
def equals(v1, v2) -> bool:
1✔
1770
    """Compare two objects by value. Unlike the standard Python equality operator,
1771
    this function does not consider 1 == True or 0 == False. All other equality
1772
    operations are the same and performed using Python's equality operator."""
1773
    if isinstance(v1, (bool, type(None))) or isinstance(v2, (bool, type(None))):
1✔
1774
        return v1 is v2
1✔
1775
    return v1 == v2
1✔
1776

1777

1778
T_comparable = TypeVar("T_comparable", bound=Comparable)
1✔
1779

1780

1781
def max_of(it: Iterable[T_comparable]) -> T_comparable:
1✔
1782
    """Return the max of an iterable of arguments.
1783

1784
    Unlike in Python, if any value is NaN return NaN."""
1785
    for v in it:
1✔
1786
        if isinstance(v, numbers.Real) and math.isnan(v):
1✔
1787
            return v
1✔
1788
    return max(it)
1✔
1789

1790

1791
def min_of(it: Iterable[T_comparable]) -> T_comparable:
1✔
1792
    """Return the min of an iterable of arguments.
1793

1794
    Unlike in Python, if any value is NaN return NaN."""
1795
    for v in it:
1✔
1796
        if isinstance(v, numbers.Real) and math.isnan(v):
1✔
1797
            return v
1✔
1798
    return min(it)
1✔
1799

1800

1801
@functools.singledispatch
1✔
1802
def compare(x, y) -> int:
1✔
1803
    """Return either -1, 0, or 1 to indicate the relationship between x and y.
1804

1805
    This is a 3-way comparator commonly used in Java-derived systems. Python does not
1806
    typically use 3-way comparators, so this function convert's Python's `__lt__` and
1807
    `__gt__` method returns into one of the 3-way comparator return values."""
1808
    if y is None:
1✔
1809
        assert x is not None, "x cannot be nil"
1✔
1810
        return 1
1✔
1811
    return (x > y) - (x < y)
1✔
1812

1813

1814
@compare.register(type(None))
1✔
1815
def _compare_nil(_: None, y) -> int:
1✔
1816
    # nil is less than all values, except itself.
1817
    return 0 if y is None else -1
1✔
1818

1819

1820
@compare.register(decimal.Decimal)
1✔
1821
def _compare_decimal(x: decimal.Decimal, y) -> int:
1✔
1822
    # Decimal instances will not compare with float("nan"), so we need a special case
1823
    if isinstance(y, float):
1✔
1824
        return -compare(y, x)  # pylint: disable=arguments-out-of-order
1✔
1825
    return (x > y) - (x < y)
1✔
1826

1827

1828
@compare.register(float)
1✔
1829
def _compare_float(x, y) -> int:
1✔
1830
    if y is None:
1✔
1831
        return 1
1✔
1832
    if math.isnan(x):
1✔
1833
        return 0
1✔
1834
    return (x > y) - (x < y)
1✔
1835

1836

1837
@compare.register(IPersistentSet)
1✔
1838
def _compare_sets(x: IPersistentSet, y) -> int:
1✔
1839
    # Sets are not comparable (because there is no total ordering between sets).
1840
    # However, in Python comparison is done using __lt__ and __gt__, which AbstractSet
1841
    # inconveniently also uses as part of it's API for comparing sets with subset and
1842
    # superset relationships. To "break" that, we just override the comparison method.
1843
    # One consequence of this is that it may be possible to sort a collection of sets,
1844
    # since `compare` isn't actually consulted in sorting.
1845
    raise TypeError(
1✔
1846
        f"cannot compare instances of '{type(x).__name__}' and '{type(y).__name__}'"
1847
    )
1848

1849

1850
def _fn_to_comparator(f):
1✔
1851
    """Coerce F comparator fn to a 3 way comparator fn."""
1852

1853
    if f == compare:  # pylint: disable=comparison-with-callable
1✔
1854
        return f
1✔
1855

1856
    def cmp(x, y):
1✔
1857
        r = f(x, y)
1✔
1858
        if not isinstance(r, bool) and isinstance(r, numbers.Number):
1✔
1859
            return r
1✔
1860
        elif r:
1✔
1861
            return -1
1✔
1862
        elif f(y, x):
1✔
1863
            return 1
1✔
1864
        else:
1865
            return 0
1✔
1866

1867
    return cmp
1✔
1868

1869

1870
def sort(coll, f=compare) -> ISeq | None:
1✔
1871
    """Return a sorted sequence of the elements in coll. If a
1872
    comparator function f is provided, compare elements in coll
1873
    using f or use the `compare` fn if not.
1874

1875
    The comparator fn can be either a boolean or 3-way comparison fn."""
1876
    seq = lseq.to_seq(coll)
1✔
1877
    if seq:
1✔
1878
        if isinstance(coll, IPersistentMap):
1✔
1879
            coll = seq
1✔
1880

1881
        comparator = _fn_to_comparator(f)
1✔
1882

1883
        class key:
1✔
1884
            __slots__ = ("obj",)
1✔
1885

1886
            def __init__(self, obj):
1✔
1887
                self.obj = obj
1✔
1888

1889
            def __lt__(self, other):
1✔
1890
                return comparator(self.obj, other.obj) < 0
1✔
1891

1892
            def __gt__(self, other):
1✔
1893
                return comparator(self.obj, other.obj) > 0
×
1894

1895
            def __eq__(self, other):
1✔
1896
                return comparator(self.obj, other.obj) == 0
×
1897

1898
            def __le__(self, other):
1✔
1899
                return comparator(self.obj, other.obj) <= 0
×
1900

1901
            def __ge__(self, other):
1✔
1902
                return comparator(self.obj, other.obj) >= 0
×
1903

1904
            __hash__ = None  # type: ignore
1✔
1905

1906
        return lseq.sequence(sorted(coll, key=key))
1✔
1907
    else:
1908
        return llist.EMPTY
1✔
1909

1910

1911
def sort_by(keyfn, coll, cmp=compare) -> ISeq | None:
1✔
1912
    """Return a sorted sequence of the elements in coll. If a
1913
    comparator function cmp is provided, compare elements in coll
1914
    using cmp or use the `compare` fn if not.
1915

1916
    The comparator fn can be either a boolean or 3-way comparison fn."""
1917
    seq = lseq.to_seq(coll)
1✔
1918
    if seq:
1✔
1919
        if isinstance(coll, IPersistentMap):
1✔
1920
            coll = seq
1✔
1921

1922
        comparator = _fn_to_comparator(cmp)
1✔
1923

1924
        class key:
1✔
1925
            __slots__ = ("obj",)
1✔
1926

1927
            def __init__(self, obj):
1✔
1928
                self.obj = obj
1✔
1929

1930
            def __lt__(self, other):
1✔
1931
                return comparator(keyfn(self.obj), keyfn(other.obj)) < 0
1✔
1932

1933
            def __gt__(self, other):
1✔
1934
                return comparator(keyfn(self.obj), keyfn(other.obj)) > 0
×
1935

1936
            def __eq__(self, other):
1✔
1937
                return comparator(keyfn(self.obj), keyfn(other.obj)) == 0
×
1938

1939
            def __le__(self, other):
1✔
1940
                return comparator(keyfn(self.obj), keyfn(other.obj)) <= 0
×
1941

1942
            def __ge__(self, other):
1✔
1943
                return comparator(keyfn(self.obj), keyfn(other.obj)) >= 0
×
1944

1945
            __hash__ = None  # type: ignore
1✔
1946

1947
        return lseq.sequence(sorted(coll, key=key))
1✔
1948
    else:
1949
        return llist.EMPTY
1✔
1950

1951

1952
def is_special_form(s: sym.Symbol) -> bool:
1✔
1953
    """Return True if s names a special form."""
1954
    return s in _SPECIAL_FORMS
1✔
1955

1956

1957
@functools.singledispatch
1✔
1958
def to_lisp(o, keywordize_keys: bool = True):  # pylint: disable=unused-argument
1✔
1959
    """Recursively convert Python collections into Lisp collections."""
1960
    return o
1✔
1961

1962

1963
@to_lisp.register(list)
1✔
1964
@to_lisp.register(tuple)
1✔
1965
def _to_lisp_vec(o: Iterable, keywordize_keys: bool = True) -> vec.PersistentVector:
1✔
1966
    return vec.vector(
1✔
1967
        map(functools.partial(to_lisp, keywordize_keys=keywordize_keys), o)
1968
    )
1969

1970

1971
@functools.singledispatch
1✔
1972
def _keywordize_keys(k, keywordize_keys: bool = True):
1✔
1973
    return to_lisp(k, keywordize_keys=keywordize_keys)
1✔
1974

1975

1976
@_keywordize_keys.register(str)
1✔
1977
def _keywordize_keys_str(k: str, keywordize_keys: bool = True):
1✔
1978
    return keyword_from_name(k)
1✔
1979

1980

1981
@to_lisp.register(dict)
1✔
1982
def _to_lisp_map(o: Mapping, keywordize_keys: bool = True) -> lmap.PersistentMap:
1✔
1983
    process_key = _keywordize_keys if keywordize_keys else to_lisp
1✔
1984
    return lmap.map(
1✔
1985
        {
1986
            process_key(k, keywordize_keys=keywordize_keys): to_lisp(
1987
                v, keywordize_keys=keywordize_keys
1988
            )
1989
            for k, v in o.items()
1990
        }
1991
    )
1992

1993

1994
@to_lisp.register(frozenset)
1✔
1995
@to_lisp.register(set)
1✔
1996
def _to_lisp_set(o: AbstractSet, keywordize_keys: bool = True) -> lset.PersistentSet:
1✔
1997
    return lset.set(map(functools.partial(to_lisp, keywordize_keys=keywordize_keys), o))
1✔
1998

1999

2000
def _kw_name(kw: kw.Keyword) -> str:
1✔
2001
    return kw.name
1✔
2002

2003

2004
@functools.singledispatch
1✔
2005
def to_py(
1✔
2006
    o, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
2007
):  # pylint: disable=unused-argument
2008
    """Recursively convert Lisp collections into Python collections."""
2009
    return o
1✔
2010

2011

2012
@to_py.register(kw.Keyword)
1✔
2013
def _to_py_kw(o: kw.Keyword, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name) -> Any:
1✔
2014
    return keyword_fn(o)
1✔
2015

2016

2017
@to_py.register(IPersistentList)
1✔
2018
@to_py.register(ISeq)
1✔
2019
@to_py.register(IPersistentVector)
1✔
2020
def _to_py_list(
1✔
2021
    o: IPersistentList | ISeq | IPersistentVector,
2022
    keyword_fn: Callable[[kw.Keyword], Any] = _kw_name,
2023
) -> list:
2024
    return list(map(functools.partial(to_py, keyword_fn=keyword_fn), o))
1✔
2025

2026

2027
@to_py.register(IPersistentMap)
1✔
2028
def _to_py_map(
1✔
2029
    o: IPersistentMap, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
2030
) -> dict:
2031
    return {
1✔
2032
        to_py(key, keyword_fn=keyword_fn): to_py(value, keyword_fn=keyword_fn)
2033
        for key, value in o.items()
2034
    }
2035

2036

2037
@to_py.register(IPersistentSet)
1✔
2038
def _to_py_set(
1✔
2039
    o: IPersistentSet, keyword_fn: Callable[[kw.Keyword], Any] = _kw_name
2040
) -> set:
2041
    return {to_py(e, keyword_fn=keyword_fn) for e in o}
1✔
2042

2043

2044
def lrepr(o, human_readable: bool = False) -> str:
1✔
2045
    """Produce a string representation of an object. If human_readable is False,
2046
    the string representation of Lisp objects is something that can be read back
2047
    in by the reader as the same object."""
2048
    core_ns = Namespace.get(CORE_NS_SYM)
1✔
2049
    assert core_ns is not None
1✔
2050
    return lobj.lrepr(
1✔
2051
        o,
2052
        human_readable=human_readable,
2053
        print_dup=core_ns.find(sym.symbol(PRINT_DUP_VAR_NAME)).value,  # type: ignore
2054
        print_length=core_ns.find(  # type: ignore
2055
            sym.symbol(PRINT_LENGTH_VAR_NAME)
2056
        ).value,
2057
        print_level=core_ns.find(  # type: ignore
2058
            sym.symbol(PRINT_LEVEL_VAR_NAME)
2059
        ).value,
2060
        print_meta=core_ns.find(sym.symbol(PRINT_META_VAR_NAME)).value,  # type: ignore
2061
        print_namespace_maps=core_ns.find(sym.symbol(PRINT_NAMESPACE_MAPS_VAR_NAME)).value,  # type: ignore
2062
        print_readably=core_ns.find(  # type: ignore
2063
            sym.symbol(PRINT_READABLY_VAR_NAME)
2064
        ).value,
2065
    )
2066

2067

2068
def lstr(o) -> str:
1✔
2069
    """Produce a human-readable string representation of an object."""
2070
    return lobj.lstr(o)
1✔
2071

2072

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

2075

2076
def repl_completions(text: str) -> Iterable[str]:
1✔
2077
    """Return an optional iterable of REPL completions."""
2078
    # Can't complete Keywords, Numerals
2079
    if __NOT_COMPLETEABLE.match(text):
1✔
2080
        return ()
×
2081
    elif text.startswith(":"):
1✔
2082
        return kw.complete(text)
×
2083
    else:
2084
        ns = get_current_ns()
1✔
2085
        return ns.complete(text)
1✔
2086

2087

2088
####################
2089
# Compiler Support #
2090
####################
2091

2092

2093
class _TrampolineArgs:
1✔
2094
    __slots__ = ("_has_varargs", "_args", "_kwargs")
1✔
2095

2096
    def __init__(self, has_varargs: bool, *args, **kwargs) -> None:
1✔
2097
        self._has_varargs = has_varargs
1✔
2098
        self._args = args
1✔
2099
        self._kwargs = kwargs
1✔
2100

2101
    @property
1✔
2102
    def args(self) -> tuple:
1✔
2103
        """Return the arguments for a trampolined function. If the function
2104
        that is being trampolined has varargs, unroll the final argument if
2105
        it is a sequence."""
2106
        if not self._has_varargs:
1✔
2107
            return self._args
1✔
2108

2109
        try:
1✔
2110
            final = self._args[-1]
1✔
2111
            if isinstance(final, ISeq):
1✔
2112
                inits = self._args[:-1]
1✔
2113
                return tuple(itertools.chain(inits, final))
1✔
2114
            return self._args
1✔
2115
        except IndexError:
1✔
2116
            return ()
1✔
2117

2118
    @property
1✔
2119
    def kwargs(self) -> dict:
1✔
2120
        return self._kwargs
1✔
2121

2122

2123
def _trampoline(f):
1✔
2124
    """Trampoline a function repeatedly until it is finished recurring to help
2125
    avoid stack growth."""
2126

2127
    @functools.wraps(f)
1✔
2128
    def trampoline(*args, **kwargs):
1✔
2129
        while True:
1✔
2130
            ret = f(*args, **kwargs)
1✔
2131
            if isinstance(ret, _TrampolineArgs):
1✔
2132
                args = ret.args
1✔
2133
                kwargs = ret.kwargs
1✔
2134
                continue
1✔
2135
            return ret
1✔
2136

2137
    return trampoline
1✔
2138

2139

2140
def _lisp_fn_apply_kwargs(f):
1✔
2141
    """Convert a Python function into a Lisp function.
2142

2143
    Python keyword arguments will be converted into Lisp keyword/argument pairs
2144
    that can be easily understood by Basilisp.
2145

2146
    Lisp functions annotated with the `:apply` value for the `:kwargs` metadata key
2147
    will be wrapped with this decorator by the compiler."""
2148

2149
    @functools.wraps(f)
1✔
2150
    def wrapped_f(*args, **kwargs):
1✔
2151
        return f(
1✔
2152
            *args,
2153
            *itertools.chain.from_iterable(
2154
                (kw.keyword(demunge(k)), v) for k, v in kwargs.items()
2155
            ),
2156
        )
2157

2158
    return wrapped_f
1✔
2159

2160

2161
def _lisp_fn_collect_kwargs(f):
1✔
2162
    """Convert a Python function into a Lisp function.
2163

2164
    Python keyword arguments will be collected into a single map, which is supplied
2165
    as the final positional argument.
2166

2167
    Lisp functions annotated with the `:collect` value for the `:kwargs` metadata key
2168
    will be wrapped with this decorator by the compiler."""
2169

2170
    @functools.wraps(f)
1✔
2171
    def wrapped_f(*args, **kwargs):
1✔
2172
        return f(
1✔
2173
            *args,
2174
            lmap.map({kw.keyword(demunge(k)): v for k, v in kwargs.items()}),
2175
        )
2176

2177
    return wrapped_f
1✔
2178

2179

2180
def _with_attrs(**kwargs):
1✔
2181
    """Decorator to set attributes on a function. Returns the original
2182
    function after setting the attributes named by the keyword arguments."""
2183

2184
    def decorator(f):
1✔
2185
        for k, v in kwargs.items():
1✔
2186
            setattr(f, k, v)
1✔
2187
        return f
1✔
2188

2189
    return decorator
1✔
2190

2191

2192
def _fn_with_meta(f, meta: lmap.PersistentMap | None):
1✔
2193
    """Return a new function with the given meta. If the function f already
2194
    has a meta map, then merge the new meta with the existing meta."""
2195

2196
    if not isinstance(meta, lmap.PersistentMap):
1✔
2197
        raise TypeError("meta must be a map")
1✔
2198

2199
    if inspect.iscoroutinefunction(f):
1✔
2200

2201
        @functools.wraps(f)
1✔
2202
        async def wrapped_f(*args, **kwargs):
1✔
2203
            return await f(*args, **kwargs)
1✔
2204

2205
    else:
2206

2207
        @functools.wraps(f)
1✔
2208
        def wrapped_f(*args, **kwargs):
1✔
2209
            return f(*args, **kwargs)
1✔
2210

2211
    wrapped_f.meta = (  # type: ignore
1✔
2212
        f.meta.update(meta)
2213
        if hasattr(f, "meta") and isinstance(f.meta, lmap.PersistentMap)
2214
        else meta
2215
    )
2216
    wrapped_f.with_meta = partial(_fn_with_meta, wrapped_f)  # type: ignore
1✔
2217
    return wrapped_f
1✔
2218

2219

2220
class _WrappedRestArgs:
1✔
2221
    """Sentinel wrapper type for a potentially infinite sequence of arguments
2222
    provided by `apply`.
2223

2224
    `apply` calls `BasilispFunction.apply_to` (a method on all Basilisp functions
2225
    added by the compiler via `_basilisp_fn` decorator) with the fixed set of arguments
2226
    and the remaining (rest) arguments as an ISeq. `BasilispFunction.apply_to` then
2227
    calls the wrapped function with the fixed set of arguments and the remaining args
2228
    as an ISeq wrapped in a `_WrappedRestArgs` object.
2229

2230
    The compiler generates code which calls `_unwrap_rest_args` on Python varargs
2231
    to convert it into a flattened ISeq within the receiving function. This allows
2232
    applying infinite sequences without attempting to eagerly consume them."""
2233

2234
    __slots__ = ("rest",)
1✔
2235

2236
    def __init__(self, rest: ISeq | None):
1✔
2237
        self.rest = rest
1✔
2238

2239

2240
def _unwrap_rest_args(args: tuple) -> ISeq:
1✔
2241
    """Runtime support function added by the compiler for generated variadic Python
2242
    functions to support infinite sequences of arguments with `apply`.
2243

2244
    Unwraps any `_WrappedRestArgs` objects in the final position of the Python vararg
2245
    on a variadic arity function, returning an ISeq which can be consumed lazily."""
2246
    try:
1✔
2247
        *final, last = args
1✔
NEW
2248
    except ValueError:
×
NEW
2249
        return lseq.EMPTY
×
2250
    else:
2251
        if isinstance(last, _WrappedRestArgs):
1✔
2252
            return concat(final, last.rest)
1✔
2253
        return concat(final, [last])
1✔
2254

2255

2256
_REST_KW = kw.keyword("rest")
1✔
2257

2258

2259
def _fn_apply_to(
1✔
2260
    f, arities: tuple[int | kw.Keyword, ...], max_fixed_arity: int | None = None
2261
) -> Callable[[list, ISeq | None], Any]:
2262
    """Return a new `apply_to` method implementation for the supplied function."""
2263

2264
    if _REST_KW in arities:
1✔
2265
        if max_fixed_arity is None:
1✔
2266

2267
            # If there is a rest arity, we pass the non-seq args to the function
2268
            # using Python's `*` operator and then wrap the final seq args in
2269
            # _WrappedRestArgs.
2270

2271
            @functools.wraps(f)
1✔
2272
            def apply_to(args: list, rest: ISeq | None):
1✔
NEW
2273
                return f(*args, _WrappedRestArgs(rest))
×
2274

2275
        else:
2276

2277
            # If there is a maximum fixed arity, we need to make sure we pass at
2278
            # least that many arguments as standard arguments before wrapping the
2279
            # seq in a _WrappedRestArgs otherwise we'll get a TypeError.
2280

2281
            @functools.wraps(f)
1✔
2282
            def apply_to(args: list, rest: ISeq | None):
1✔
2283
                num_missing_args = max_fixed_arity - len(args)
1✔
2284
                if num_missing_args > 0:
1✔
2285
                    remaining = []
1✔
2286
                    while num_missing_args > 0 and to_seq(rest):
1✔
2287
                        assert rest is not None
1✔
2288
                        e, rest = rest.first, rest.rest
1✔
2289
                        remaining.append(e)
1✔
2290
                        num_missing_args -= 1
1✔
2291
                    if to_seq(rest):
1✔
2292
                        return f(*args, *remaining, _WrappedRestArgs(rest))
1✔
2293
                    else:
2294
                        return f(*args, *remaining)
1✔
2295
                return f(*args, _WrappedRestArgs(rest))
1✔
2296

2297
    else:
2298

2299
        # If there is no rest arity on this function, consume the entire argument
2300
        # list eagerly.
2301

2302
        @functools.wraps(f)
1✔
2303
        def apply_to(args: list, rest: ISeq | None):
1✔
2304
            return f(*concat(args, rest))
1✔
2305

2306
    return apply_to
1✔
2307

2308

2309
def _basilisp_fn(
1✔
2310
    arities: tuple[int | kw.Keyword, ...],
2311
    max_fixed_arity: int | None = None,
2312
) -> Callable[..., BasilispFunction]:
2313
    """Decorator applied to Basilisp functions by the compiler.
2314

2315
    This decorator is responsible for setting default properties and generating methods
2316
    all Basilisp functions must have."""
2317

2318
    # Be sure to update _update_signature_for_partial when new attributes are added here.
2319
    def wrap_fn(f) -> BasilispFunction:
1✔
2320
        assert not hasattr(f, "meta")
1✔
2321
        f._basilisp_fn = True
1✔
2322
        f.apply_to = _fn_apply_to(f, arities, max_fixed_arity=max_fixed_arity)
1✔
2323
        f.arities = lset.set(arities)
1✔
2324
        f.meta = None
1✔
2325
        f.with_meta = partial(_fn_with_meta, f)
1✔
2326
        return f
1✔
2327

2328
    return wrap_fn
1✔
2329

2330

2331
def _basilisp_type(
1✔
2332
    fields: Iterable[str],
2333
    interfaces: Iterable[type],
2334
    artificially_abstract_bases: AbstractSet[type],
2335
    members: Iterable[str],
2336
):
2337
    """Check that a Basilisp type (defined by `deftype*`) only declares abstract
2338
    super-types and that all abstract methods are implemented."""
2339

2340
    def wrap_class(cls: type):
1✔
2341
        field_names = frozenset(fields)
1✔
2342
        member_names = frozenset(members)
1✔
2343
        artificially_abstract_base_members: set[str] = set()
1✔
2344
        all_member_names = field_names.union(member_names)
1✔
2345
        all_interface_methods: set[str] = set()
1✔
2346
        for interface in interfaces:
1✔
2347
            if interface is object:
1✔
UNCOV
2348
                continue
×
2349

2350
            if is_abstract(interface):
1✔
2351
                interface_names: frozenset[str] = interface.__abstractmethods__  # type: ignore[attr-defined]
1✔
2352
                interface_property_names: frozenset[str] = frozenset(
1✔
2353
                    method
2354
                    for method in interface_names
2355
                    if isinstance(getattr(interface, method), property)
2356
                )
2357
                interface_method_names = interface_names - interface_property_names
1✔
2358
                if not interface_method_names.issubset(member_names):
1✔
2359
                    missing_methods = ", ".join(interface_method_names - member_names)
1✔
2360
                    raise RuntimeException(
1✔
2361
                        "deftype* definition missing interface members for interface "
2362
                        f"{interface}: {missing_methods}",
2363
                    )
2364
                elif not interface_property_names.issubset(all_member_names):
1✔
2365
                    missing_fields = ", ".join(interface_property_names - field_names)
1✔
2366
                    raise RuntimeException(
1✔
2367
                        "deftype* definition missing interface properties for interface "
2368
                        f"{interface}: {missing_fields}",
2369
                    )
2370

2371
                all_interface_methods.update(interface_names)
1✔
2372
            elif interface in artificially_abstract_bases:
1✔
2373
                artificially_abstract_base_members.update(
1✔
2374
                    map(
2375
                        lambda v: v[0],
2376
                        inspect.getmembers(
2377
                            interface,
2378
                            predicate=lambda v: inspect.isfunction(v)
2379
                            or isinstance(v, (property, staticmethod))
2380
                            or inspect.ismethod(v),
2381
                        ),
2382
                    )
2383
                )
2384
            else:
2385
                raise RuntimeException(
1✔
2386
                    "deftype* interface must be Python abstract class or object",
2387
                )
2388

2389
        extra_methods = member_names - all_interface_methods - OBJECT_DUNDER_METHODS
1✔
2390
        if extra_methods and not extra_methods.issubset(
1✔
2391
            artificially_abstract_base_members
2392
        ):
2393
            extra_method_str = ", ".join(extra_methods)
1✔
2394
            raise RuntimeException(
1✔
2395
                "deftype* definition for interface includes members not part of "
2396
                f"defined interfaces: {extra_method_str}"
2397
            )
2398

UNCOV
2399
        return cls
×
2400

2401
    return wrap_class
1✔
2402

2403

2404
def _load_constant(s: bytes) -> Any:
1✔
2405
    """Load a compiler "constant" stored as a byte string as by Python's `pickle`
2406
    module.
2407

2408
    Constant types without special handling are emitted to bytecode as a byte string
2409
    produced by `pickle.dumps`."""
2410
    try:
1✔
2411
        return pickle.loads(s)  # nosec B301
1✔
UNCOV
2412
    except pickle.UnpicklingError as e:
×
UNCOV
2413
        raise RuntimeException("Unable to load constant value") from e
×
2414

2415

2416
###############################
2417
# Symbol and Alias Resolution #
2418
###############################
2419

2420

2421
def resolve_alias(s: sym.Symbol, ns: Namespace | None = None) -> sym.Symbol:
1✔
2422
    """Resolve the aliased symbol in the current namespace."""
2423
    if s in _SPECIAL_FORMS:
1✔
2424
        return s
1✔
2425

2426
    ns = Maybe(ns).or_else(get_current_ns)
1✔
2427
    if s.ns is not None:
1✔
2428
        aliased_ns = ns.get_alias(sym.symbol(s.ns))
1✔
2429
        if aliased_ns is not None:
1✔
2430
            return sym.symbol(s.name, aliased_ns.name)
1✔
2431
        else:
2432
            return s
1✔
2433
    else:
2434
        which_var = ns.find(sym.symbol(s.name))
1✔
2435
        if which_var is not None:
1✔
2436
            return sym.symbol(which_var.name.name, which_var.ns.name)
1✔
2437
        else:
2438
            return sym.symbol(s.name, ns=ns.name)
1✔
2439

2440

2441
def resolve_var(s: sym.Symbol, ns: Namespace | None = None) -> Var | None:
1✔
2442
    """Resolve the aliased symbol to a Var from the specified namespace, or the
2443
    current namespace if none is specified."""
2444
    ns_qualified_sym = resolve_alias(s, ns)
1✔
2445
    return Var.find(resolve_alias(s, ns)) if ns_qualified_sym.ns else None
1✔
2446

2447

2448
#######################
2449
# Namespace Utilities #
2450
#######################
2451

2452

2453
@contextlib.contextmanager
1✔
2454
def bindings(bindings: Mapping[Var, Any] | None = None):
1✔
2455
    """Context manager for temporarily changing the value thread-local value for
2456
    Basilisp dynamic Vars."""
2457
    m = lmap.map(bindings or {})
1✔
2458
    logger.debug(
1✔
2459
        f"Binding thread-local values for Vars: {', '.join(map(str, m.keys()))}"
2460
    )
2461
    try:
1✔
2462
        push_thread_bindings(m)
1✔
2463
        yield
1✔
2464
    finally:
2465
        pop_thread_bindings()
1✔
2466
        logger.debug(
1✔
2467
            f"Reset thread-local bindings for Vars: {', '.join(map(str, m.keys()))}"
2468
        )
2469

2470

2471
@contextlib.contextmanager
1✔
2472
def ns_bindings(
1✔
2473
    ns_name: str, module: BasilispModule | None = None
2474
) -> Iterator[Namespace]:
2475
    """Context manager for temporarily changing the value of basilisp.core/*ns*."""
2476
    symbol = sym.symbol(ns_name)
1✔
2477
    ns = Namespace.get_or_create(symbol, module=module)
1✔
2478
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
2479
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
2480
    )
2481

2482
    with bindings({ns_var: ns}):
1✔
2483
        yield ns_var.value
1✔
2484

2485

2486
@contextlib.contextmanager
1✔
2487
def remove_ns_bindings():
1✔
2488
    """Context manager to pop the most recent bindings for basilisp.core/*ns* after
2489
    completion of the code under management."""
2490
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
2491
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
2492
    )
2493
    try:
1✔
2494
        yield
1✔
2495
    finally:
2496
        ns_var.pop_bindings()
1✔
2497
        logger.debug(f"Reset bindings for {NS_VAR_SYM} to {ns_var.value}")
1✔
2498

2499

2500
def get_current_ns() -> Namespace:
1✔
2501
    """Get the value of the dynamic variable `*ns*` in the current thread."""
2502
    ns: Namespace = (
1✔
2503
        Maybe(Var.find(NS_VAR_SYM))
2504
        .map(lambda v: v.value)
2505
        .or_else_raise(lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!"))
2506
    )
2507
    return ns
1✔
2508

2509

2510
def set_current_ns(
1✔
2511
    ns_name: str,
2512
    module: BasilispModule | None = None,
2513
) -> Var:
2514
    """Set the value of the dynamic variable `*ns*` in the current thread."""
2515
    symbol = sym.symbol(ns_name)
1✔
2516
    ns = Namespace.get_or_create(symbol, module=module)
1✔
2517
    ns_var = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
2518
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
2519
    )
2520
    ns_var.push_bindings(ns)
1✔
2521
    logger.debug(f"Setting {NS_VAR_SYM} to {ns}")
1✔
2522
    return ns_var
1✔
2523

2524

2525
##############################
2526
# Emit Generated Python Code #
2527
##############################
2528

2529

2530
def add_generated_python(
1✔
2531
    generated_python: str,
2532
    which_ns: Namespace | None = None,
2533
) -> None:
2534
    """Add generated Python code to a dynamic variable in which_ns."""
2535
    if which_ns is None:
1✔
2536
        which_ns = get_current_ns()
1✔
2537
    v = Maybe(which_ns.find(sym.symbol(GENERATED_PYTHON_VAR_NAME))).or_else(
1✔
2538
        lambda: Var.intern(
2539
            which_ns,  # type: ignore[arg-type, unused-ignore]
2540
            sym.symbol(GENERATED_PYTHON_VAR_NAME),
2541
            "",
2542
            dynamic=True,
2543
            meta=lmap.map({_PRIVATE_META_KEY: True}),
2544
        )
2545
    )
2546
    # Accessing the Var root via the property uses a lock, which is the
2547
    # desired behavior for Basilisp code, but it introduces additional
2548
    # startup time when there will not realistically be any contention.
2549
    v._root = v._root + generated_python  # type: ignore
1✔
2550

2551

2552
def print_generated_python() -> bool:
1✔
2553
    """Return the value of the `*print-generated-python*` dynamic variable."""
2554
    ns_sym = sym.symbol(PRINT_GENERATED_PY_VAR_NAME, ns=CORE_NS)
1✔
2555
    return (
1✔
2556
        Maybe(Var.find(ns_sym))
2557
        .map(lambda v: v.value)
2558
        .or_else_raise(lambda: RuntimeException(f"Dynamic Var {ns_sym} not bound!"))
2559
    )
2560

2561

2562
#########################
2563
# Bootstrap the Runtime #
2564
#########################
2565

2566

2567
def init_ns_var() -> Var:
1✔
2568
    """Initialize the dynamic `*ns*` variable in the `basilisp.core` Namespace."""
2569
    core_ns = Namespace.get_or_create(CORE_NS_SYM)
1✔
2570
    ns_var = Var.intern(
1✔
2571
        core_ns,
2572
        sym.symbol(NS_VAR_NAME),
2573
        core_ns,
2574
        dynamic=True,
2575
        meta=lmap.map(
2576
            {
2577
                _DOC_META_KEY: (
2578
                    "Pointer to the current namespace.\n\n"
2579
                    "This value is used by both the compiler and runtime to determine where "
2580
                    "newly defined Vars should be bound, so users should not alter or bind "
2581
                    "this Var unless they know what they're doing."
2582
                )
2583
            }
2584
        ),
2585
    )
2586
    logger.debug(f"Created namespace variable {NS_VAR_SYM}")
1✔
2587
    return ns_var
1✔
2588

2589

2590
def bootstrap_core(compiler_opts: CompilerOpts) -> None:
1✔
2591
    """Bootstrap the environment with functions that are either difficult to express
2592
    with the very minimal Lisp environment or which are expected by the compiler."""
2593
    _NS = Maybe(Var.find(NS_VAR_SYM)).or_else_raise(
1✔
2594
        lambda: RuntimeException(f"Dynamic Var {NS_VAR_SYM} not bound!")
2595
    )
2596

2597
    def in_ns(s: sym.Symbol):
1✔
2598
        ns = Namespace.get_or_create(s)
1✔
2599
        _NS.set_value(ns)
1✔
2600
        return ns
1✔
2601

2602
    # Vars used in bootstrapping the runtime
2603
    Var.intern_unbound(
1✔
2604
        CORE_NS_SYM,
2605
        sym.symbol("unquote"),
2606
        meta=lmap.map(
2607
            {
2608
                _DOC_META_KEY: (
2609
                    "Placeholder Var so the compiler does not throw an error while syntax quoting.\n\n"
2610
                    "See :ref:`macros` and :ref:`syntax_quoting` for more details."
2611
                )
2612
            }
2613
        ),
2614
    )
2615
    Var.intern_unbound(
1✔
2616
        CORE_NS_SYM,
2617
        sym.symbol("unquote-splicing"),
2618
        meta=lmap.map(
2619
            {
2620
                _DOC_META_KEY: (
2621
                    "Placeholder Var so the compiler does not throw an error while syntax quoting.\n\n"
2622
                    "See :ref:`macros` and :ref:`syntax_quoting` for more details."
2623
                )
2624
            }
2625
        ),
2626
    )
2627
    Var.intern(
1✔
2628
        CORE_NS_SYM, sym.symbol("in-ns"), in_ns, meta=lmap.map({_REDEF_META_KEY: True})
2629
    )
2630
    Var.intern(
1✔
2631
        CORE_NS_SYM,
2632
        sym.symbol(IMPORT_MODULE_VAR_NAME),
2633
        None,
2634
        dynamic=True,
2635
        meta=lmap.map(
2636
            {
2637
                _DOC_META_KEY: "If not ``nil``, corresponds to the module which is currently being imported."
2638
            }
2639
        ),
2640
    )
2641

2642
    # Dynamic Var examined by the compiler when importing new Namespaces
2643
    Var.intern(
1✔
2644
        CORE_NS_SYM,
2645
        sym.symbol(COMPILER_OPTIONS_VAR_NAME),
2646
        compiler_opts,
2647
        dynamic=True,
2648
    )
2649

2650
    # Dynamic Var containing command line arguments passed via `basilisp run`
2651
    Var.intern(
1✔
2652
        CORE_NS_SYM,
2653
        sym.symbol(COMMAND_LINE_ARGS_VAR_NAME),
2654
        None,
2655
        dynamic=True,
2656
        meta=lmap.map(
2657
            {
2658
                _DOC_META_KEY: (
2659
                    "A vector of command line arguments if this process was started "
2660
                    "with command line arguments as by ``basilisp run {file_or_code}`` "
2661
                    "or ``nil`` otherwise.\n\n"
2662
                    "Note that this value will differ from ``sys.argv`` since it will "
2663
                    "not include the command line arguments consumed by Basilisp's "
2664
                    "own CLI."
2665
                )
2666
            }
2667
        ),
2668
    )
2669

2670
    # Dynamic Var containing command line arguments passed via `basilisp run`
2671
    Var.intern(
2672
        CORE_NS_SYM,
2673
        sym.symbol(MAIN_NS_VAR_NAME),
2674
        None,
2675
        dynamic=True,
2676
        meta=lmap.map(
2677
            {
2678
                _DOC_META_KEY: (
2679
                    "The name of the main namespace as a symbol if this process was "
2680
                    "executed as ``basilisp run -n {namespace}`` or "
2681
                    "``python -m {namespace}`` or ``nil`` otherwise.\n\n"
2682
                    "This can be useful for detecting scripts similarly to how Python "
2683
                    'scripts use the idiom ``if __name__ == "__main__":``.'
2684
                )
2685
            }
2686
        ),
2687
    )
2688

2689
    # Dynamic Var for introspecting the default reader featureset
2690
    Var.intern(
1✔
2691
        CORE_NS_SYM,
2692
        sym.symbol(DEFAULT_READER_FEATURES_VAR_NAME),
2693
        READER_COND_DEFAULT_FEATURE_SET,
2694
        dynamic=True,
2695
        meta=lmap.map(
2696
            {
2697
                _DOC_META_KEY: (
2698
                    "The set of all currently supported "
2699
                    ":ref:`reader features <reader_conditionals>`."
2700
                )
2701
            }
2702
        ),
2703
    )
2704

2705
    # Dynamic Vars examined by the compiler for generating Python code for debugging
2706
    Var.intern(
1✔
2707
        CORE_NS_SYM,
2708
        sym.symbol(PRINT_GENERATED_PY_VAR_NAME),
2709
        False,
2710
        dynamic=True,
2711
        meta=lmap.map({_PRIVATE_META_KEY: True}),
2712
    )
2713
    Var.intern(
1✔
2714
        CORE_NS_SYM,
2715
        sym.symbol(GENERATED_PYTHON_VAR_NAME),
2716
        "",
2717
        dynamic=True,
2718
        meta=lmap.map({_PRIVATE_META_KEY: True}),
2719
    )
2720

2721
    # Dynamic Vars for controlling printing
2722
    Var.intern(
1✔
2723
        CORE_NS_SYM, sym.symbol(PRINT_DUP_VAR_NAME), lobj.PRINT_DUP, dynamic=True
2724
    )
2725
    Var.intern(
1✔
2726
        CORE_NS_SYM,
2727
        sym.symbol(PRINT_LENGTH_VAR_NAME),
2728
        lobj.PRINT_LENGTH,
2729
        dynamic=True,
2730
        meta=lmap.map(
2731
            {
2732
                _DOC_META_KEY: (
2733
                    "Limits the number of items printed per collection. If falsy, all items are shown."
2734
                    " If set to an integer, only that many items are printed,"
2735
                    " with ``...`` indicating more. By default, it is ``nil``, meaning no limit."
2736
                )
2737
            }
2738
        ),
2739
    )
2740
    Var.intern(
1✔
2741
        CORE_NS_SYM, sym.symbol(PRINT_LEVEL_VAR_NAME), lobj.PRINT_LEVEL, dynamic=True
2742
    )
2743
    Var.intern(
1✔
2744
        CORE_NS_SYM, sym.symbol(PRINT_META_VAR_NAME), lobj.PRINT_META, dynamic=True
2745
    )
2746
    Var.intern(
1✔
2747
        CORE_NS_SYM,
2748
        sym.symbol(PRINT_NAMESPACE_MAPS_VAR_NAME),
2749
        lobj.PRINT_NAMESPACE_MAPS,
2750
        dynamic=True,
2751
        meta=lmap.map(
2752
            {
2753
                _DOC_META_KEY: (
2754
                    "Indicates to print the namespace of keys in a map belonging to the same"
2755
                    " namespace, at the beginning of the map instead of beside the keys."
2756
                    " Defaults to false."
2757
                )
2758
            }
2759
        ),
2760
    )
2761
    Var.intern(
1✔
2762
        CORE_NS_SYM,
2763
        sym.symbol(PRINT_READABLY_VAR_NAME),
2764
        lobj.PRINT_READABLY,
2765
        dynamic=True,
2766
    )
2767

2768
    # Version info
2769
    Var.intern(
1✔
2770
        CORE_NS_SYM,
2771
        sym.symbol(PYTHON_VERSION_VAR_NAME),
2772
        vec.vector(sys.version_info),
2773
        dynamic=True,
2774
        meta=lmap.map(
2775
            {
2776
                _DOC_META_KEY: (
2777
                    "The current Python version as a vector of "
2778
                    "``[major, minor, revision]``."
2779
                )
2780
            }
2781
        ),
2782
    )
2783
    Var.intern(
1✔
2784
        CORE_NS_SYM,
2785
        sym.symbol(BASILISP_VERSION_VAR_NAME),
2786
        BASILISP_VERSION,
2787
        dynamic=True,
2788
        meta=lmap.map(
2789
            {
2790
                _DOC_META_KEY: (
2791
                    "The current Basilisp version as a vector of "
2792
                    "``[major, minor, revision]``."
2793
                )
2794
            }
2795
        ),
2796
    )
2797

2798

2799
def get_compiler_opts() -> CompilerOpts:
1✔
2800
    """Return the current compiler options map."""
2801
    v = Var.find_in_ns(CORE_NS_SYM, sym.symbol(COMPILER_OPTIONS_VAR_NAME))
1✔
2802
    assert v is not None, "*compiler-options* Var not defined"
1✔
2803
    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

© 2026 Coveralls, Inc