Skip to content

Commit

Permalink
Merge pull request #2616 from Kodiologist/pragma-hy
Browse files Browse the repository at this point in the history
Add the `:hy` pragma
  • Loading branch information
Kodiologist authored Oct 28, 2024
2 parents 5828ce0 + d2e2885 commit 7bdc5a8
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 27 deletions.
File renamed without changes.
16 changes: 0 additions & 16 deletions .readthedocs.yaml

This file was deleted.

4 changes: 4 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Unreleased

Supports Python 3.9 – Python 3.x

New Features
------------------------------
* New pragma `hy`.

1.0.0 ("Afternoon Review", released 2024-09-22)
======================================================================

Expand Down
12 changes: 10 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,17 @@ Fundamentals
The effect of each pragma is locally scoped to its containing function,
class, or comprehension form (other than ``for``), if there is one.

Only one pragma is currently implemented:
These pragmata are currently implemented:

.. _warn-on-core-shadow:
- ``:hy``: Set this to a string giving a Hy version number or prefix thereof,
such as "1.1.0" or "1", to raise a compile-time error if the currently
executing version of Hy isn't at least this new. If you're writing a
package, you should still declare the required version of Hy in ``setup.py``
or ``pyproject.toml`` or whatever, because ``pip`` won't look for ``(pragma
:hy …)`` calls. In the future, this pragma may also switch on features of Hy
that were introduced in or before the given version.

.. _warn-on-core-shadow:

- ``:warn-on-core-shadow``: If true (the default), :hy:func:`defmacro` and
:hy:func:`require` will raise a warning at compile-time if you define a macro
Expand Down
2 changes: 1 addition & 1 deletion docs/versioning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ Versioning and compatibility

Starting with Hy 1.0.0, Hy is `semantically versioned <https://semver.org>`_. Refer to `the NEWS file <https://github.com/hylang/hy/blob/master/NEWS.rst>`_ for a summary of user-visible changes brought on by each version, and how to update your code in case of breaking changes. Be sure you're reading the version of this manual (shown at the top of each page) that matches the version of Hy you're running.

Hy is tested on `all released and currently maintained versions of CPython <https://devguide.python.org/versions>`_ (on Linux, Windows, and Mac OS), and on recent versions of PyPy and Pyodide. We usually find that for Hy, unlike most Python packages, we need to change things to fully support each new 3.x release of Python. We may drop compatibility with a version of Python after the CPython guys cease maintaining it. Note that we construe such a change as non-breaking, so we won't bump Hy's major version for it. But we will at least bump the minor version, and ``python_requires`` in Hy's ``setup.py`` should prevent you from installing a Hy version that won't work with your Python version.
Hy is tested on `all released and currently maintained versions of CPython <https://devguide.python.org/versions>`_ (on Linux, Windows, and Mac OS), and on recent versions of `PyPy <https://pypy.org>`_ and `Pyodide <https://pyodide.org>`_. We usually find that for Hy, unlike most Python packages, we need to change things to fully support each new 3.x release of Python. We may drop compatibility with a version of Python after the CPython guys cease maintaining it. Note that we construe such a change as non-breaking, so we won't bump Hy's major version for it. But we will at least bump the minor version, and ``python_requires`` in Hy's ``setup.py`` should prevent you from installing a Hy version that won't work with your Python version.

Starting with Hy 1.0.0, each version of Hy also has a nickname, such as "Afternoon Review". Nicknames are used in alphabetical order, with a nickname starting with "Z" then wrapping around to "A". Nicknames are provided mostly for the amusement of the maintainer, but can be useful as a conspicuous sign that you're not using the version you expected. In code, you can get the current nickname as a string (or ``None``, for unreleased commits of Hy) with ``hy.nickname``.
4 changes: 4 additions & 0 deletions hy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
__version__ = 'unreleased'
nickname = None
last_version = '1.0.0'
# This is used by `(pragma :hy …)` to guess whether an unreleased
# version of Hy is new enough. In a released version, it's simply
# equal to `__version__`.


def _initialize_env_var(env_var, default_val):
Expand Down
34 changes: 27 additions & 7 deletions hy/core/result_macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
import ast
import textwrap
from contextlib import nullcontext
from itertools import dropwhile
from itertools import dropwhile, zip_longest

from funcparserlib.parser import finished, forward_decl, many, maybe, oneplus, some

from hy import last_version
from hy.compat import PY3_11, PY3_12
from hy.compiler import Result, asty, mkexpr
from hy.errors import HyEvalError, HyInternalError, HyTypeError
Expand Down Expand Up @@ -165,11 +166,31 @@ def compile_inline_python(compiler, expr, root, code):
@pattern_macro("pragma", [many(KEYWORD + FORM)])
def compile_pragma(compiler, expr, root, kwargs):
for kw, value in kwargs:
if kw == Keyword("warn-on-core-shadow"):

if kw == Keyword("hy"):
min_version = compiler.eval(value)
if not isinstance(min_version, str):
raise compiler._syntax_error(value, "The version given to the pragma `:hy` must be a string")
parts = min_version.split('.')
if not all(p.isdigit() for p in parts):
raise compiler._syntax_error(value, "The string given to the pragma `:hy` must be a dot-separated sequence of integers")
for have, need in zip_longest(
map(int, last_version.split('.')),
map(int, parts)):
if need is None:
break
if have is None or have < need:
raise compiler._syntax_error(kw, f"Hy version {min_version} or later required")
if have > need:
break

elif kw == Keyword("warn-on-core-shadow"):
compiler.local_state_stack[-1]['warn_on_core_shadow'] = (
bool(compiler.eval(value)))

else:
raise compiler._syntax_error(kw, f"Unknown pragma `{kw}`. Perhaps it's implemented by a newer version of Hy.")

return Result()


Expand Down Expand Up @@ -565,17 +586,16 @@ def compile_global_or_nonlocal(compiler, expr, root, syms):
return asty.Pass(expr)

names = [mangle(s) for s in syms]
if root == "global":
ret = asty.Global(expr, names=names)
else:
ret = OuterVar(expr, compiler.scope, names)
ret = (asty.Global(expr, names = names)
if root == "global" else
OuterVar(expr, compiler.scope, names))

try:
compiler.scope.define_nonlocal(ret, root)
except SyntaxError as e:
raise compiler._syntax_error(expr, e.msg)

return ret if syms else Result()
return ret


@pattern_macro("del", [many(FORM)])
Expand Down
22 changes: 21 additions & 1 deletion tests/native_tests/other.hy
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,27 @@
importlib
pydoc
pytest
hy.errors [HyLanguageError])
hy.errors [HyLanguageError HySyntaxError])


(defn test-pragma-hy []

(pragma :hy "1")
(pragma :hy "1.0")
(pragma :hy "1.0.0")
(pragma :hy "0.28.0")

(eval-when-compile (setv a-version "1.0"))
(pragma :hy a-version)

(defn bad [v msg]
(with [e (pytest.raises HySyntaxError)]
(hy.eval `(pragma :hy ~v)))
(assert (in msg e.value.msg)))
(bad "5" "version 5 or later required")
(bad "1.99.1" "version 1.99.1 or later required")
(bad 5 "must be a string")
(bad "Afternoon Review" "must be a dot-separated sequence of integers"))


(defn test-illegal-assignments []
Expand Down

0 comments on commit 7bdc5a8

Please sign in to comment.