Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
85 changes: 85 additions & 0 deletions src/basilisp/lang/tagged.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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)
16 changes: 16 additions & 0 deletions tests/basilisp/tagged_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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