Skip to content

Commit 927d5e4

Browse files
authored
Added default reader features for matching Python version ranges (#593)
1 parent 0104685 commit 927d5e4

File tree

5 files changed

+97
-15
lines changed

5 files changed

+97
-15
lines changed

.circleci/config.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ jobs:
3434

3535
test-pypy:
3636
docker:
37-
- image: pypy:3.6-7.2-slim-stretch
37+
- image: pypy:3.6-7-slim-buster
38+
parallelism: 3
3839
steps:
3940
- checkout
4041
- restore_cache:
@@ -46,7 +47,14 @@ jobs:
4647
- run:
4748
name: Run Tests
4849
command: |
49-
tox -e pypy3
50+
# Split tests by timing and print out the test files on each node
51+
CCI_NODE_TESTS=$(circleci tests glob "tests/**/*_test.py" "tests/**/test_*.py" | circleci tests split --split-by=timings)
52+
printf "Test files:\n"
53+
echo "$CCI_NODE_TESTS"
54+
printf "\n"
55+
56+
# Run the tests on the subset defined for this node
57+
tox -e pypy3 -- $CCI_NODE_TESTS
5058
- save_cache:
5159
key: deps9-{{ .Branch }}-{{ checksum "tox.ini" }}-{{ checksum "Pipfile.lock" }}
5260
paths:

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* Added `*basilisp-version*` and `*python-version*` Vars to `basilisp.core` (#584)
1313
* Added support for function decorators to `defn` (#585)
1414
* Added the current Python version (`:lpy36`, `:lpy37`, etc.) as a default reader feature for reader conditionals (#585)
15+
* Added default reader features for matching Python version ranges (`:lpy36+`, `:lpy38-`, etc.) (#593)
1516
* Added `lazy-cat` function for lazily concatenating sequences (#588)
1617

1718
### Changed

src/basilisp/lang/reader.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import functools
55
import io
66
import re
7-
import sys
87
import uuid
98
from datetime import datetime
109
from fractions import Fraction
@@ -50,7 +49,13 @@
5049
IWithMeta,
5150
)
5251
from basilisp.lang.obj import seq_lrepr as _seq_lrepr
53-
from basilisp.lang.runtime import Namespace, Var, get_current_ns, lrepr
52+
from basilisp.lang.runtime import (
53+
READER_COND_DEFAULT_FEATURE_SET,
54+
Namespace,
55+
Var,
56+
get_current_ns,
57+
lrepr,
58+
)
5459
from basilisp.lang.typing import IterableLispForm, LispForm, ReaderForm
5560
from basilisp.lang.util import munge
5661
from basilisp.util import Maybe, partition
@@ -73,16 +78,6 @@
7378
READER_LINE_KW = keyword.keyword("line", ns="basilisp.lang.reader")
7479
READER_COL_KW = keyword.keyword("col", ns="basilisp.lang.reader")
7580

76-
READER_COND_BASILISP_PY_VERSION_FEATURE_KW = keyword.keyword(
77-
f"lpy{sys.version_info.major}{sys.version_info.minor}"
78-
)
79-
READER_COND_BASILISP_FEATURE_KW = keyword.keyword("lpy")
80-
READER_COND_DEFAULT_FEATURE_KW = keyword.keyword("default")
81-
READER_COND_DEFAULT_FEATURE_SET = lset.s(
82-
READER_COND_BASILISP_FEATURE_KW,
83-
READER_COND_DEFAULT_FEATURE_KW,
84-
READER_COND_BASILISP_PY_VERSION_FEATURE_KW,
85-
)
8681
READER_COND_FORM_KW = keyword.keyword("form")
8782
READER_COND_SPLICING_KW = keyword.keyword("splicing?")
8883

src/basilisp/lang/runtime.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@
6666
NS_VAR_SYM = sym.symbol(NS_VAR_NAME, ns=CORE_NS)
6767
NS_VAR_NS = CORE_NS
6868
REPL_DEFAULT_NS = "basilisp.user"
69+
SUPPORTED_PYTHON_VERSIONS = frozenset({(3, 6), (3, 7), (3, 8)})
6970

7071
# Private string constants
7172
_COMPILER_OPTIONS_VAR_NAME = "*compiler-options*"
73+
_DEFAULT_READER_FEATURES_VAR_NAME = "*default-reader-features*"
7274
_GENERATED_PYTHON_VAR_NAME = "*generated-python*"
7375
_PRINT_GENERATED_PY_VAR_NAME = "*print-generated-python*"
7476
_PRINT_DUP_VAR_NAME = "*print-dup*"
@@ -132,6 +134,41 @@
132134
_VAR,
133135
)
134136

137+
138+
# Reader Conditional default features
139+
def _supported_python_versions_features() -> Iterable[kw.Keyword]:
140+
"""Yield successive reader features corresponding to the various Python
141+
`major`.`minor` versions the current Python VM corresponds to amongst the
142+
set of supported Python versions.
143+
144+
For example, for Python 3.6, we emit:
145+
- :lpy36 - to exactly match Basilisp running on Python 3.6
146+
- :lpy36+ - to match Basilisp running on Python 3.6 and later versions
147+
- :lpy36- - to match Basilisp running on Python 3.6 and earlier versions
148+
- :lpy37- - to match Basilisp running on Python 3.7 and earlier versions
149+
- :lpy38- - to match Basilisp running on Python 3.8 and earlier versions"""
150+
feature_kw = lambda major, minor, suffix="": kw.keyword(
151+
f"lpy{major}{minor}{suffix}"
152+
)
153+
154+
yield feature_kw(sys.version_info.major, sys.version_info.minor)
155+
156+
current = (sys.version_info.major, sys.version_info.minor)
157+
for version in SUPPORTED_PYTHON_VERSIONS:
158+
if current <= version:
159+
yield feature_kw(version[0], version[1], suffix="-")
160+
if current >= version:
161+
yield feature_kw(version[0], version[1], suffix="+")
162+
163+
164+
READER_COND_BASILISP_FEATURE_KW = kw.keyword("lpy")
165+
READER_COND_DEFAULT_FEATURE_KW = kw.keyword("default")
166+
READER_COND_DEFAULT_FEATURE_SET = lset.s(
167+
READER_COND_BASILISP_FEATURE_KW,
168+
READER_COND_DEFAULT_FEATURE_KW,
169+
*_supported_python_versions_features(),
170+
)
171+
135172
CompletionMatcher = Callable[[Tuple[sym.Symbol, Any]], bool]
136173
CompletionTrimmer = Callable[[Tuple[sym.Symbol, Any]], str]
137174

@@ -173,7 +210,7 @@ def __repr__(self): # pragma: no cover
173210
return f"Unbound(var={self.var})"
174211

175212
def __eq__(self, other):
176-
return isinstance(other, Unbound) and self.var == other.var
213+
return self is other or (isinstance(other, Unbound) and self.var == other.var)
177214

178215

179216
class Var(IDeref, ReferenceBase):
@@ -1767,6 +1804,14 @@ def in_ns(s: sym.Symbol):
17671804
dynamic=True,
17681805
)
17691806

1807+
# Dynamic Var for introspecting the default reader featureset
1808+
Var.intern(
1809+
CORE_NS_SYM,
1810+
sym.symbol(_DEFAULT_READER_FEATURES_VAR_NAME),
1811+
READER_COND_DEFAULT_FEATURE_SET,
1812+
dynamic=True,
1813+
)
1814+
17701815
# Dynamic Vars examined by the compiler for generating Python code for debugging
17711816
Var.intern(
17721817
CORE_NS_SYM,

tests/basilisp/runtime_test.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from decimal import Decimal
23
from fractions import Fraction
34

@@ -17,6 +18,38 @@
1718
from tests.basilisp.helpers import get_or_create_ns
1819

1920

21+
def test_is_supported_python_version():
22+
v = sys.version_info
23+
assert (v.major, v.minor) in runtime.SUPPORTED_PYTHON_VERSIONS
24+
25+
26+
@pytest.mark.parametrize(
27+
"feature",
28+
{
29+
(3, 6): frozenset(
30+
map(
31+
keyword.keyword,
32+
["lpy36", "default", "lpy", "lpy36-", "lpy36+", "lpy37-", "lpy38-"],
33+
)
34+
),
35+
(3, 7): frozenset(
36+
map(
37+
keyword.keyword,
38+
["lpy37", "default", "lpy", "lpy37-", "lpy37+", "lpy36+", "lpy38-"],
39+
)
40+
),
41+
(3, 8): frozenset(
42+
map(
43+
keyword.keyword,
44+
["lpy38", "default", "lpy", "lpy38-", "lpy38+", "lpy37+", "lpy36+"],
45+
)
46+
),
47+
}[(sys.version_info.major, sys.version_info.minor)],
48+
)
49+
def test_reader_default_featureset(feature):
50+
assert feature in runtime.READER_COND_DEFAULT_FEATURE_SET
51+
52+
2053
def test_first():
2154
assert None is runtime.first(None)
2255
assert None is runtime.first(llist.l())

0 commit comments

Comments
 (0)