Skip to content

Commit 1701536

Browse files
djbluechrisrink10
andauthored
Add tagged-literal support (#1101)
Fixes #1104 This is a first draft at getting tagged literals working. I don't have much experience with python, so I may have made some amateur mistakes. --------- Co-authored-by: Chris Rink <[email protected]>
1 parent 460caff commit 1701536

File tree

7 files changed

+140
-0
lines changed

7 files changed

+140
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88
### Added
99
* Added support for the `:param-tags` reader metadata syntax `^[tag ...]` from Clojure 1.12 (#1111)
10+
* Add support for tagged literals (#1104)
1011

1112
### Changed
1213
* 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)

src/basilisp/core.lpy

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,17 @@
547547
v
548548
(.-name v)))
549549

550+
(defn ^:inline tagged-literal
551+
"Construct a data representation of a tagged literal from a
552+
tag symbol and a form."
553+
[tag form]
554+
(basilisp.lang.tagged/tagged-literal tag form))
555+
556+
(defn ^:inline tagged-literal?
557+
"Return true if the value is the data representation of a tagged literal"
558+
[o]
559+
(instance? basilisp.lang.tagged/TaggedLiteral o))
560+
550561
(defn ^:inline namespace
551562
"Return the namespace of a symbol or keyword, or ``nil`` if no namespace."
552563
[v]

src/basilisp/lang/compiler/generator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,7 @@ def _var_ns_as_python_sym(name: str) -> str:
698698
_SEQ_ALIAS = genname("seq")
699699
_SET_ALIAS = genname("lset")
700700
_SYM_ALIAS = genname("sym")
701+
_TAGGED_ALIAS = genname("tagged")
701702
_VEC_ALIAS = genname("vec")
702703
_VOLATILE_ALIAS = genname("volatile")
703704
_VAR_ALIAS = genname("Var")
@@ -731,6 +732,7 @@ def _var_ns_as_python_sym(name: str) -> str:
731732
"basilisp.lang.seq": _SEQ_ALIAS,
732733
"basilisp.lang.set": _SET_ALIAS,
733734
"basilisp.lang.symbol": _SYM_ALIAS,
735+
"basilisp.lang.tagged": _TAGGED_ALIAS,
734736
"basilisp.lang.vector": _VEC_ALIAS,
735737
"basilisp.lang.volatile": _VOLATILE_ALIAS,
736738
"basilisp.lang.util": _UTIL_ALIAS,

src/basilisp/lang/runtime.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ class Namespace(ReferenceBase):
551551
"basilisp.lang.seq",
552552
"basilisp.lang.set",
553553
"basilisp.lang.symbol",
554+
"basilisp.lang.tagged",
554555
"basilisp.lang.vector",
555556
"basilisp.lang.volatile",
556557
"basilisp.lang.util",

src/basilisp/lang/tagged.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from typing import Optional, TypeVar, Union
2+
3+
from typing_extensions import Unpack
4+
5+
from basilisp.lang.interfaces import ILispObject, ILookup
6+
from basilisp.lang.keyword import keyword
7+
from basilisp.lang.obj import PrintSettings, lrepr
8+
from basilisp.lang.symbol import Symbol
9+
10+
K = TypeVar("K")
11+
V = TypeVar("V")
12+
T = Union[None, V, Symbol]
13+
14+
_TAG_KW = keyword("tag")
15+
_FORM_KW = keyword("form")
16+
17+
18+
class TaggedLiteral(
19+
ILispObject,
20+
ILookup[K, T],
21+
):
22+
"""Basilisp TaggedLiteral. https://clojure.org/reference/reader#tagged_literals"""
23+
24+
__slots__ = ("_tag", "_form", "_hash")
25+
26+
def __init__(self, tag: Symbol, form) -> None:
27+
self._tag = tag
28+
self._form = form
29+
self._hash: Optional[int] = None
30+
31+
@property
32+
def tag(self) -> Symbol:
33+
return self._tag
34+
35+
@property
36+
def form(self):
37+
return self._form
38+
39+
def __bool__(self):
40+
return True
41+
42+
def __eq__(self, other):
43+
if self is other:
44+
return True
45+
if not isinstance(other, TaggedLiteral):
46+
return NotImplemented
47+
return self._tag == other._tag and self._form == other._form
48+
49+
def __hash__(self):
50+
if self._hash is None:
51+
self._hash = hash((self._tag, self._form))
52+
return self._hash
53+
54+
def __getitem__(self, item):
55+
return self.val_at(item)
56+
57+
def val_at(self, k: K, default: Optional[V] = None) -> T:
58+
if k == _TAG_KW:
59+
return self._tag
60+
elif k == _FORM_KW:
61+
return self._form
62+
else:
63+
return default
64+
65+
def _lrepr(self, **kwargs: Unpack[PrintSettings]) -> str:
66+
return f"#{self._tag} {lrepr(self._form, **kwargs)}"
67+
68+
69+
def tagged_literal(tag: Symbol, form):
70+
"""Construct a data representation of a tagged literal from a
71+
tag symbol and a form."""
72+
if not isinstance(tag, Symbol):
73+
raise TypeError(f"tag must be a Symbol, not '{type(tag)}'")
74+
return TaggedLiteral(tag, form)

tests/basilisp/tagged_test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from basilisp.lang.symbol import symbol
2+
from basilisp.lang.tagged import tagged_literal
3+
4+
5+
def test_tagged_literal():
6+
tag = symbol("tag")
7+
form = 1
8+
tagged = tagged_literal(tag, form)
9+
assert tagged.tag == tag
10+
assert tagged.form == form
11+
12+
13+
def test_tagged_literal_str_and_repr():
14+
tag = symbol("tag")
15+
form = 1
16+
tagged = tagged_literal(tag, form)
17+
assert str(tagged) == "#tag 1"
18+
assert repr(tagged) == "#tag 1"

tests/basilisp/test_tagged.lpy

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
(ns tests.basilisp.test-tagged
2+
(:require
3+
[basilisp.test :refer [deftest is testing]]))
4+
5+
(deftest tagged-literal-test
6+
(let [tag 'tag
7+
form 1
8+
tagged (tagged-literal tag form)]
9+
(testing "equality"
10+
(is (= tagged tagged))
11+
(is (= tagged (tagged-literal tag form)))
12+
(is (not= tagged (tagged-literal 'foo form)))
13+
(is (not= tagged (tagged-literal tag 2))))
14+
15+
(testing "accessors"
16+
(is (= tag (:tag tagged)))
17+
(is (= form (:form tagged)))
18+
(is (nil? (:key tagged)))
19+
(is (= ::default (:key tagged ::default))))
20+
21+
(testing "predicate"
22+
(is (true? (tagged-literal? tagged)))
23+
(is (false? (tagged-literal? nil)))
24+
(is (false? (tagged-literal? 0)))
25+
(is (false? (tagged-literal? ::foo))))
26+
27+
(testing "printing"
28+
(is (= "#tag 1" (pr-str tagged)))
29+
(is (= "#js []" (pr-str (tagged-literal 'js []))))
30+
(is (= "#js {}" (pr-str (tagged-literal 'js {})))))
31+
32+
(testing "validation"
33+
(is (thrown? TypeError (tagged-literal 1 1))))))

0 commit comments

Comments
 (0)