Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
* Added support for the `:param-tags` reader metadata syntax `^[tag ...]` from Clojure 1.12 (#1111)
* Add support for tagged literals (#1104)

### Changed
* Types generated by `reify` may optionally be marked as `^:mutable` now to prevent `attrs.exceptions.FrozenInstanceError`s being thrown when mutating methods inherited from the supertype(s) are called (#1088)
Expand Down
11 changes: 11 additions & 0 deletions src/basilisp/core.lpy
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,17 @@
v
(.-name v)))

(defn ^:inline tagged-literal
"Construct a data representation of a tagged literal from a
tag symbol and a form."
[tag form]
(basilisp.lang.tagged/tagged-literal tag form))

(defn ^:inline tagged-literal?
"Return true if the value is the data representation of a tagged literal"
[o]
(instance? basilisp.lang.tagged/TaggedLiteral o))

(defn ^:inline namespace
"Return the namespace of a symbol or keyword, or ``nil`` if no namespace."
[v]
Expand Down
2 changes: 2 additions & 0 deletions src/basilisp/lang/compiler/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,7 @@ def _var_ns_as_python_sym(name: str) -> str:
_SEQ_ALIAS = genname("seq")
_SET_ALIAS = genname("lset")
_SYM_ALIAS = genname("sym")
_TAGGED_ALIAS = genname("tagged")
_VEC_ALIAS = genname("vec")
_VOLATILE_ALIAS = genname("volatile")
_VAR_ALIAS = genname("Var")
Expand Down Expand Up @@ -731,6 +732,7 @@ def _var_ns_as_python_sym(name: str) -> str:
"basilisp.lang.seq": _SEQ_ALIAS,
"basilisp.lang.set": _SET_ALIAS,
"basilisp.lang.symbol": _SYM_ALIAS,
"basilisp.lang.tagged": _TAGGED_ALIAS,
"basilisp.lang.vector": _VEC_ALIAS,
"basilisp.lang.volatile": _VOLATILE_ALIAS,
"basilisp.lang.util": _UTIL_ALIAS,
Expand Down
1 change: 1 addition & 0 deletions src/basilisp/lang/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ class Namespace(ReferenceBase):
"basilisp.lang.seq",
"basilisp.lang.set",
"basilisp.lang.symbol",
"basilisp.lang.tagged",
"basilisp.lang.vector",
"basilisp.lang.volatile",
"basilisp.lang.util",
Expand Down
74 changes: 74 additions & 0 deletions src/basilisp/lang/tagged.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from typing import Optional, TypeVar, Union

from typing_extensions import Unpack

from basilisp.lang.interfaces import ILispObject, ILookup
from basilisp.lang.keyword import keyword
from basilisp.lang.obj import PrintSettings, lrepr
from basilisp.lang.symbol import Symbol

K = TypeVar("K")
V = TypeVar("V")
T = Union[None, V, Symbol]

_TAG_KW = keyword("tag")
_FORM_KW = keyword("form")


class TaggedLiteral(
ILispObject,
ILookup[K, T],
):
"""Basilisp TaggedLiteral. https://clojure.org/reference/reader#tagged_literals"""

__slots__ = ("_tag", "_form", "_hash")

def __init__(self, tag: Symbol, form) -> None:
self._tag = tag
self._form = form
self._hash: Optional[int] = None

@property
def tag(self) -> Symbol:
return self._tag

@property
def form(self):
return self._form

def __bool__(self):
return True

def __eq__(self, other):
if self is other:
return True
if not isinstance(other, TaggedLiteral):
return NotImplemented
return self._tag == other._tag and self._form == other._form

def __hash__(self):
if self._hash is None:
self._hash = hash((self._tag, self._form))
return self._hash

def __getitem__(self, item):
return self.val_at(item)

def val_at(self, k: K, default: Optional[V] = None) -> T:
if k == _TAG_KW:
return self._tag
elif k == _FORM_KW:
return self._form
else:
return default

def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
return f"#{self._tag} {lrepr(self._form, **kwargs)}"


def tagged_literal(tag: Symbol, form):
"""Construct a data representation of a tagged literal from a
tag symbol and a form."""
if not isinstance(tag, Symbol):
raise TypeError(f"tag must be a Symbol, not '{type(tag)}'")
return TaggedLiteral(tag, form)
18 changes: 18 additions & 0 deletions tests/basilisp/tagged_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from basilisp.lang.symbol import symbol
from basilisp.lang.tagged import tagged_literal


def test_tagged_literal():
tag = symbol("tag")
form = 1
tagged = tagged_literal(tag, form)
assert tagged.tag == tag
assert tagged.form == form


def test_tagged_literal_str_and_repr():
tag = symbol("tag")
form = 1
tagged = tagged_literal(tag, form)
assert str(tagged) == "#tag 1"
assert repr(tagged) == "#tag 1"
33 changes: 33 additions & 0 deletions tests/basilisp/test_tagged.lpy
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
(ns tests.basilisp.test-tagged
(:require
[basilisp.test :refer [deftest is testing]]))

(deftest tagged-literal-test
(let [tag 'tag
form 1
tagged (tagged-literal tag form)]
(testing "equality"
(is (= tagged tagged))
(is (= tagged (tagged-literal tag form)))
(is (not= tagged (tagged-literal 'foo form)))
(is (not= tagged (tagged-literal tag 2))))

(testing "accessors"
(is (= tag (:tag tagged)))
(is (= form (:form tagged)))
(is (nil? (:key tagged)))
(is (= ::default (:key tagged ::default))))

(testing "predicate"
(is (true? (tagged-literal? tagged)))
(is (false? (tagged-literal? nil)))
(is (false? (tagged-literal? 0)))
(is (false? (tagged-literal? ::foo))))

(testing "printing"
(is (= "#tag 1" (pr-str tagged)))
(is (= "#js []" (pr-str (tagged-literal 'js []))))
(is (= "#js {}" (pr-str (tagged-literal 'js {})))))

(testing "validation"
(is (thrown? TypeError (tagged-literal 1 1))))))
Loading