Merge pull request #1517 from Kodiologist/mangling-makeover

Mangling makeover
This commit is contained in:
Kodi Arfer 2018-03-13 12:16:13 -07:00 committed by GitHub
commit b023ebd0b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 613 additions and 482 deletions

View File

@ -5,10 +5,20 @@ Unreleased
Other Breaking Changes Other Breaking Changes
------------------------------ ------------------------------
* Mangling rules have been overhauled, such that mangled names
are always legal Python identifiers
* `_` and `-` are now equivalent even as single-character names
* The REPL history variable `_` is now `*1`
* Non-shadow unary `=`, `is`, `<`, etc. now evaluate their argument * Non-shadow unary `=`, `is`, `<`, etc. now evaluate their argument
instead of ignoring it. This change increases consistency a bit instead of ignoring it. This change increases consistency a bit
and makes accidental unary uses easier to notice. and makes accidental unary uses easier to notice.
New Features
------------------------------
* Added `mangle` and `unmangle` as core functions
Bug Fixes Bug Fixes
------------------------------ ------------------------------
* Fix `(return)` so it works correctly to exit a Python 2 generator * Fix `(return)` so it works correctly to exit a Python 2 generator

View File

@ -1,142 +1,6 @@
================= =================
Hy (the language)
=================
.. warning::
This is incomplete; please consider contributing to the documentation
effort.
Theory of Hy
============
Hy maintains, over everything else, 100% compatibility in both directions
with Python itself. All Hy code follows a few simple rules. Memorize
this, as it's going to come in handy.
These rules help ensure that Hy code is idiomatic and interfaceable in both
languages.
* Symbols in earmuffs will be translated to the upper-cased version of that
string. For example, ``foo`` will become ``FOO``.
* UTF-8 entities will be encoded using
`punycode <https://en.wikipedia.org/wiki/Punycode>`_ and prefixed with
``hy_``. For instance, ```` will become ``hy_w7h``, ```` will become
``hy_g6h``, and ``i♥u`` will become ``hy_iu_t0x``.
* Symbols that contain dashes will have them replaced with underscores. For
example, ``render-template`` will become ``render_template``. This means
that symbols with dashes will shadow their underscore equivalents, and vice
versa.
Notes on Syntax
===============
numeric literals
----------------
In addition to regular numbers, standard notation from Python 3 for non-base 10
integers is used. ``0x`` for Hex, ``0o`` for Octal, ``0b`` for Binary.
.. code-block:: clj
(print 0x80 0b11101 0o102 30)
Underscores and commas can appear anywhere in a numeric literal except the very
beginning. They have no effect on the value of the literal, but they're useful
for visually separating digits.
.. code-block:: clj
(print 10,000,000,000 10_000_000_000)
Unlike Python, Hy provides literal forms for NaN and infinity: ``NaN``,
``Inf``, and ``-Inf``.
string literals
---------------
Hy allows double-quoted strings (e.g., ``"hello"``), but not single-quoted
strings like Python. The single-quote character ``'`` is reserved for
preventing the evaluation of a form (e.g., ``'(+ 1 1)``), as in most Lisps.
Python's so-called triple-quoted strings (e.g., ``'''hello'''`` and
``"""hello"""``) aren't supported. However, in Hy, unlike Python, any string
literal can contain newlines. Furthermore, Hy supports an alternative form of
string literal called a "bracket string" similar to Lua's long brackets.
Bracket strings have customizable delimiters, like the here-documents of other
languages. A bracket string begins with ``#[FOO[`` and ends with ``]FOO]``,
where ``FOO`` is any string not containing ``[`` or ``]``, including the empty
string. For example::
=> (print #[["That's very kind of yuo [sic]" Tom wrote back.]])
"That's very kind of yuo [sic]" Tom wrote back.
=> (print #[==[1 + 1 = 2]==])
1 + 1 = 2
A bracket string can contain newlines, but if it begins with one, the newline
is removed, so you can begin the content of a bracket string on the line
following the opening delimiter with no effect on the content. Any leading
newlines past the first are preserved.
Plain string literals support :ref:`a variety of backslash escapes
<py:strings>`. To create a "raw string" that interprets all backslashes
literally, prefix the string with ``r``, as in ``r"slash\not"``. Bracket
strings are always raw strings and don't allow the ``r`` prefix.
Whether running under Python 2 or Python 3, Hy treats all string literals as
sequences of Unicode characters by default, and allows you to prefix a plain
string literal (but not a bracket string) with ``b`` to treat it as a sequence
of bytes. So when running under Python 3, Hy translates ``"foo"`` and
``b"foo"`` to the identical Python code, but when running under Python 2,
``"foo"`` is translated to ``u"foo"`` and ``b"foo"`` is translated to
``"foo"``.
.. _syntax-keywords:
keywords
--------
An identifier headed by a colon, such as ``:foo``, is a keyword. Keywords
evaluate to a string preceded by the Unicode non-character code point U+FDD0,
like ``"\ufdd0:foo"``, so ``:foo`` and ``":foo"`` aren't equal. However, if a
literal keyword appears in a function call, it's used to indicate a keyword
argument rather than passed in as a value. For example, ``(f :foo 3)`` calls
the function ``f`` with the keyword argument named ``foo`` set to ``3``. Hence,
trying to call a function on a literal keyword may fail: ``(f :foo)`` yields
the error ``Keyword argument :foo needs a value``. To avoid this, you can quote
the keyword, as in ``(f ':foo)``, or use it as the value of another keyword
argument, as in ``(f :arg :foo)``.
discard prefix
--------------
Hy supports the Extensible Data Notation discard prefix, like Clojure.
Any form prefixed with ``#_`` is discarded instead of compiled.
This completely removes the form so it doesn't evaluate to anything,
not even None.
It's often more useful than linewise comments for commenting out a
form, because it respects code structure even when part of another
form is on the same line. For example:
.. code-block:: clj
=> (print "Hy" "cruel" "World!")
Hy cruel World!
=> (print "Hy" #_"cruel" "World!")
Hy World!
=> (+ 1 1 (print "Math is hard!"))
Math is hard!
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
=> (+ 1 1 #_(print "Math is hard!"))
2
Built-Ins Built-Ins
========= =================
Hy features a number of special forms that are used to help generate Hy features a number of special forms that are used to help generate
correct Python AST. The following are "special" forms, which may have correct Python AST. The following are "special" forms, which may have

View File

@ -699,6 +699,20 @@ Returns the single step macro expansion of *form*.
HySymbol('e'), HySymbol('e'),
HySymbol('f')])]) HySymbol('f')])])
.. _mangle-fn:
mangle
------
Usage: ``(mangle x)``
Stringify the input and translate it according to :ref:`Hy's mangling rules
<mangling>`.
.. code-block:: hylang
=> (mangle "foo-bar")
'foo_bar'
.. _merge-with-fn: .. _merge-with-fn:
@ -1431,6 +1445,22 @@ Returns an iterator from *coll* as long as *pred* returns ``True``.
=> (list (take-while neg? [ 1 2 3 -4 5])) => (list (take-while neg? [ 1 2 3 -4 5]))
[] []
.. _unmangle-fn:
unmangle
--------
Usage: ``(unmangle x)``
Stringify the input and return a string that would :ref:`mangle <mangling>` to
it. Note that this isn't a one-to-one operation, and nor is ``mangle``, so
``mangle`` and ``unmangle`` don't always round-trip.
.. code-block:: hylang
=> (unmangle "foo_bar")
'foo-bar'
Included itertools Included itertools
================== ==================

View File

@ -9,6 +9,7 @@ Contents:
cli cli
interop interop
syntax
api api
core core
internals internals

View File

@ -157,17 +157,8 @@ HySymbol
``hy.models.HySymbol`` is the model used to represent symbols ``hy.models.HySymbol`` is the model used to represent symbols
in the Hy language. It inherits :ref:`HyString`. in the Hy language. It inherits :ref:`HyString`.
``HySymbol`` objects are mangled in the parsing phase, to help Python Symbols are :ref:`mangled <mangling>` when they are compiled
interoperability: to Python variable names.
- Symbols surrounded by asterisks (``*``) are turned into uppercase;
- Dashes (``-``) are turned into underscores (``_``);
- One trailing question mark (``?``) is turned into a leading ``is_``.
Caveat: as the mangling is done during the parsing phase, it is possible
to programmatically generate HySymbols that can't be generated with Hy
source code. Such a mechanism is used by :ref:`gensym` to generate
"uninterned" symbols.
.. _hykeyword: .. _hykeyword:
@ -340,7 +331,7 @@ Since they have no "value" to Python, this makes working in Hy hard, since
doing something like ``(print (if True True False))`` is not just common, it's doing something like ``(print (if True True False))`` is not just common, it's
expected. expected.
As a result, we auto-mangle things using a ``Result`` object, where we offer As a result, we reconfigure things using a ``Result`` object, where we offer
up any ``ast.stmt`` that need to get run, and a single ``ast.expr`` that can up any ``ast.stmt`` that need to get run, and a single ``ast.expr`` that can
be used to get the value of whatever was just run. Hy does this by forcing be used to get the value of whatever was just run. Hy does this by forcing
assignment to things while running. assignment to things while running.
@ -352,11 +343,11 @@ As example, the Hy::
Will turn into:: Will turn into::
if True: if True:
_mangled_name_here = True _temp_name_here = True
else: else:
_mangled_name_here = False _temp_name_here = False
print _mangled_name_here print _temp_name_here
OK, that was a bit of a lie, since we actually turn that statement OK, that was a bit of a lie, since we actually turn that statement

View File

@ -8,6 +8,12 @@ Hy <-> Python interop
Despite being a Lisp, Hy aims to be fully compatible with Python. That means Despite being a Lisp, Hy aims to be fully compatible with Python. That means
every Python module or package can be imported in Hy code, and vice versa. every Python module or package can be imported in Hy code, and vice versa.
:ref:`Mangling <mangling>` allows variable names to be spelled differently in
Hy and Python. For example, Python's ``str.format_map`` can be written
``str.format-map`` in Hy, and a Hy function named ``valid?`` would be called
``is_valid`` in Python. In Python, you can import Hy's core functions
``mangle`` and ``unmangle`` directly from the ``hy`` package.
Using Python from Hy Using Python from Hy
==================== ====================
@ -27,41 +33,6 @@ You can use it in Hy:
You can also import ``.pyc`` bytecode files, of course. You can also import ``.pyc`` bytecode files, of course.
A quick note about mangling
--------
In Python, snake_case is used by convention. Lisp dialects tend to use dashes
instead of underscores, so Hy does some magic to give you more pleasant names.
In the same way, ``UPPERCASE_NAMES`` from Python can be used ``*with-earmuffs*``
instead.
You can use either the original names or the new ones.
Imagine ``example.py``::
def function_with_a_long_name():
print(42)
FOO = "bar"
Then, in Hy:
.. code-block:: clj
(import example)
(.function-with-a-long-name example) ; prints "42"
(.function_with_a_long_name example) ; also prints "42"
(print (. example *foo*)) ; prints "bar"
(print (. example FOO)) ; also prints "bar"
.. warning::
Mangling isnt that simple; there is more to discuss about it, yet it doesnt
belong in this section.
.. TODO: link to mangling section, when it is done
Using Hy from Python Using Hy from Python
==================== ====================

156
docs/language/syntax.rst Normal file
View File

@ -0,0 +1,156 @@
==============
Syntax
==============
identifiers
-----------
An identifier consists of a nonempty sequence of Unicode characters that are not whitespace nor any of the following: ``( ) [ ] { } ' "``. Hy first tries to parse each identifier into a numeric literal, then into a keyword if that fails, and finally into a symbol if that fails.
numeric literals
----------------
In addition to regular numbers, standard notation from Python 3 for non-base 10
integers is used. ``0x`` for Hex, ``0o`` for Octal, ``0b`` for Binary.
.. code-block:: clj
(print 0x80 0b11101 0o102 30)
Underscores and commas can appear anywhere in a numeric literal except the very
beginning. They have no effect on the value of the literal, but they're useful
for visually separating digits.
.. code-block:: clj
(print 10,000,000,000 10_000_000_000)
Unlike Python, Hy provides literal forms for NaN and infinity: ``NaN``,
``Inf``, and ``-Inf``.
string literals
---------------
Hy allows double-quoted strings (e.g., ``"hello"``), but not single-quoted
strings like Python. The single-quote character ``'`` is reserved for
preventing the evaluation of a form (e.g., ``'(+ 1 1)``), as in most Lisps.
Python's so-called triple-quoted strings (e.g., ``'''hello'''`` and
``"""hello"""``) aren't supported. However, in Hy, unlike Python, any string
literal can contain newlines. Furthermore, Hy supports an alternative form of
string literal called a "bracket string" similar to Lua's long brackets.
Bracket strings have customizable delimiters, like the here-documents of other
languages. A bracket string begins with ``#[FOO[`` and ends with ``]FOO]``,
where ``FOO`` is any string not containing ``[`` or ``]``, including the empty
string. For example::
=> (print #[["That's very kind of yuo [sic]" Tom wrote back.]])
"That's very kind of yuo [sic]" Tom wrote back.
=> (print #[==[1 + 1 = 2]==])
1 + 1 = 2
A bracket string can contain newlines, but if it begins with one, the newline
is removed, so you can begin the content of a bracket string on the line
following the opening delimiter with no effect on the content. Any leading
newlines past the first are preserved.
Plain string literals support :ref:`a variety of backslash escapes
<py:strings>`. To create a "raw string" that interprets all backslashes
literally, prefix the string with ``r``, as in ``r"slash\not"``. Bracket
strings are always raw strings and don't allow the ``r`` prefix.
Whether running under Python 2 or Python 3, Hy treats all string literals as
sequences of Unicode characters by default, and allows you to prefix a plain
string literal (but not a bracket string) with ``b`` to treat it as a sequence
of bytes. So when running under Python 3, Hy translates ``"foo"`` and
``b"foo"`` to the identical Python code, but when running under Python 2,
``"foo"`` is translated to ``u"foo"`` and ``b"foo"`` is translated to
``"foo"``.
.. _syntax-keywords:
keywords
--------
An identifier headed by a colon, such as ``:foo``, is a keyword. Keywords
evaluate to a string preceded by the Unicode non-character code point U+FDD0,
like ``"\ufdd0:foo"``, so ``:foo`` and ``":foo"`` aren't equal. However, if a
literal keyword appears in a function call, it's used to indicate a keyword
argument rather than passed in as a value. For example, ``(f :foo 3)`` calls
the function ``f`` with the keyword argument named ``foo`` set to ``3``. Hence,
trying to call a function on a literal keyword may fail: ``(f :foo)`` yields
the error ``Keyword argument :foo needs a value``. To avoid this, you can quote
the keyword, as in ``(f ':foo)``, or use it as the value of another keyword
argument, as in ``(f :arg :foo)``.
.. _mangling:
symbols
-------
Symbols are identifiers that are neither legal numeric literals nor legal
keywords. In most contexts, symbols are compiled to Python variable names. Some
example symbols are ``hello``, ``+++``, ``3fiddy``, ``$40``, ``just✈wrong``,
and ``🦑``.
Since the rules for Hy symbols are much more permissive than the rules for
Python identifiers, Hy uses a mangling algorithm to convert its own names to
Python-legal names. The rules are:
- Convert all hyphens (``-``) to underscores (``_``). Thus, ``foo-bar`` becomes
``foo_bar``.
- If the name ends with ``?``, remove it and prepend ``is``. Thus, ``tasty?``
becomes ``is_tasty``.
- If the name still isn't Python-legal, make the following changes. A name
could be Python-illegal because it contains a character that's never legal in
a Python name, it contains a character that's illegal in that position, or
it's equal to a Python reserved word.
- Prepend ``hyx_`` to the name.
- Replace each illegal character with ``ΔfooΔ`` (or on Python 2, ``XfooX``),
where ``foo`` is the the Unicode character name in lowercase, with spaces
replaced by underscores and hyphens replaced by ``H``. Replace ``Δ`` itself
(or on Python 2, ``X``) the same way. If the character doesn't have a name,
use ``U`` followed by its code point in lowercase hexadecimal.
Thus, ``green☘`` becomes ``hyx_greenΔshamrockΔ`` and ``if`` becomes
``hyx_if``.
- Finally, any added ``hyx_`` or ``is_`` is added after any leading
underscores, because leading underscores have special significance to Python.
Thus, ``_tasty?`` becomes ``_is_tasty`` instead of ``is__tasty``.
Mangling isn't something you should have to think about often, but you may see
mangled names in error messages, the output of ``hy2py``, etc. A catch to be
aware of is that mangling, as well as the inverse "unmangling" operation
offered by the ``unmangle`` function, isn't one-to-one. Two different symbols
can mangle to the same string and hence compile to the same Python variable.
The chief practical consequence of this is that ``-`` and ``_`` are
interchangeable in all symbol names, so you shouldn't assign to the
one-character name ``_`` , or else you'll interfere with certain uses of
subtraction.
discard prefix
--------------
Hy supports the Extensible Data Notation discard prefix, like Clojure.
Any form prefixed with ``#_`` is discarded instead of compiled.
This completely removes the form so it doesn't evaluate to anything,
not even None.
It's often more useful than linewise comments for commenting out a
form, because it respects code structure even when part of another
form is on the same line. For example:
.. code-block:: clj
=> (print "Hy" "cruel" "World!")
Hy cruel World!
=> (print "Hy" #_"cruel" "World!")
Hy World!
=> (+ 1 1 (print "Math is hard!"))
Math is hard!
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
=> (+ 1 1 #_(print "Math is hard!"))
2

View File

@ -12,5 +12,5 @@ import hy.importer # NOQA
# we import for side-effects. # we import for side-effects.
from hy.core.language import read, read_str # NOQA from hy.core.language import read, read_str, mangle, unmangle # NOQA
from hy.importer import hy_eval as eval # NOQA from hy.importer import hy_eval as eval # NOQA

View File

@ -18,7 +18,7 @@ except ImportError:
(x >> 8) & 0xff, (x >> 8) & 0xff,
(x >> 16) & 0xff, (x >> 16) & 0xff,
(x >> 24) & 0xff])) (x >> 24) & 0xff]))
import sys import sys, keyword
PY3 = sys.version_info[0] >= 3 PY3 = sys.version_info[0] >= 3
PY35 = sys.version_info >= (3, 5) PY35 = sys.version_info >= (3, 5)
@ -35,3 +35,24 @@ if PY3:
else: else:
def raise_empty(t, *args): def raise_empty(t, *args):
raise t(*args) raise t(*args)
def isidentifier(x):
if x in ('True', 'False', 'None', 'print'):
# `print` is special-cased here because Python 2's
# keyword.iskeyword will count it as a keyword, but we
# use the __future__ feature print_function, which makes
# it a non-keyword.
return True
if keyword.iskeyword(x):
return False
if PY3:
return x.isidentifier()
if x.rstrip() != x:
return False
import tokenize as T
from io import StringIO
try:
tokens = list(T.generate_tokens(StringIO(x).readline))
except T.TokenError:
return False
return len(tokens) == 2 and tokens[0][0] == T.NAME

View File

@ -16,7 +16,7 @@ import astor.code_gen
import hy import hy
from hy.lex import LexException, PrematureEndOfInput from hy.lex import LexException, PrematureEndOfInput
from hy.lex.parser import hy_symbol_mangle from hy.lex.parser import mangle
from hy.compiler import HyTypeError from hy.compiler import HyTypeError
from hy.importer import (hy_eval, import_buffer_to_module, from hy.importer import (hy_eval, import_buffer_to_module,
import_file_to_ast, import_file_to_hst, import_file_to_ast, import_file_to_hst,
@ -63,12 +63,12 @@ class HyREPL(code.InteractiveConsole):
elif callable(output_fn): elif callable(output_fn):
self.output_fn = output_fn self.output_fn = output_fn
else: else:
f = hy_symbol_mangle(output_fn)
if "." in output_fn: if "." in output_fn:
module, f = f.rsplit(".", 1) parts = [mangle(x) for x in output_fn.split(".")]
module, f = '.'.join(parts[:-1]), parts[-1]
self.output_fn = getattr(importlib.import_module(module), f) self.output_fn = getattr(importlib.import_module(module), f)
else: else:
self.output_fn = __builtins__[f] self.output_fn = __builtins__[mangle(output_fn)]
code.InteractiveConsole.__init__(self, locals=locals, code.InteractiveConsole.__init__(self, locals=locals,
filename=filename) filename=filename)
@ -112,8 +112,8 @@ class HyREPL(code.InteractiveConsole):
if value is not None: if value is not None:
# Make the last non-None value available to # Make the last non-None value available to
# the user as `_`. # the user as `*1`.
self.locals['_'] = value self.locals[mangle("*1")] = value
# Print the value. # Print the value.
try: try:
output = self.output_fn(value) output = self.output_fn(value)

View File

@ -8,7 +8,7 @@ from hy.models import (HyObject, HyExpression, HyKeyword, HyInteger, HyComplex,
HyDict, HyCons, wrap_value) HyDict, HyCons, wrap_value)
from hy.errors import HyCompileError, HyTypeError from hy.errors import HyCompileError, HyTypeError
from hy.lex.parser import hy_symbol_mangle from hy.lex.parser import mangle
import hy.macros import hy.macros
from hy._compat import ( from hy._compat import (
@ -53,7 +53,7 @@ def load_stdlib():
import hy.core import hy.core
for module in hy.core.STDLIB: for module in hy.core.STDLIB:
mod = importlib.import_module(module) mod = importlib.import_module(module)
for e in mod.EXPORTS: for e in map(ast_str, mod.EXPORTS):
if getattr(mod, e) is not getattr(builtins, e, ''): if getattr(mod, e) is not getattr(builtins, e, ''):
# Don't bother putting a name in _stdlib if it # Don't bother putting a name in _stdlib if it
# points to a builtin with the same name. This # points to a builtin with the same name. This
@ -61,38 +61,17 @@ def load_stdlib():
_stdlib[e] = module _stdlib[e] = module
# True, False and None included here since they
# are assignable in Python 2.* but become
# keywords in Python 3.*
def _is_hy_builtin(name, module_name):
extras = ['True', 'False', 'None']
if name in extras or keyword.iskeyword(name):
return True
# for non-Hy modules, check for pre-existing name in
# _compile_table
if not module_name.startswith("hy."):
return name in _compile_table
return False
_compile_table = {} _compile_table = {}
_decoratables = (ast.FunctionDef, ast.ClassDef) _decoratables = (ast.FunctionDef, ast.ClassDef)
if PY35: if PY35:
_decoratables += (ast.AsyncFunctionDef,) _decoratables += (ast.AsyncFunctionDef,)
def ast_str(foobar): def ast_str(x, piecewise=False):
if PY3: if piecewise:
return str(foobar) return ".".join(ast_str(s) if s else "" for s in x.split("."))
x = mangle(x)
try: return x if PY3 else x.encode('UTF8')
return str(foobar)
except UnicodeEncodeError:
pass
enc = codecs.getencoder('punycode')
foobar, _ = enc(foobar)
return "hy_%s" % (str(foobar).replace("-", "_"))
def builds(*types, **kwargs): def builds(*types, **kwargs):
@ -104,6 +83,8 @@ def builds(*types, **kwargs):
def _dec(fn): def _dec(fn):
for t in types: for t in types:
if isinstance(t, string_types):
t = ast_str(t)
_compile_table[t] = fn _compile_table[t] = fn
return fn return fn
return _dec return _dec
@ -379,7 +360,7 @@ def is_unpack(kind, x):
return (isinstance(x, HyExpression) return (isinstance(x, HyExpression)
and len(x) > 0 and len(x) > 0
and isinstance(x[0], HySymbol) and isinstance(x[0], HySymbol)
and x[0] == "unpack_" + kind) and x[0] == "unpack-" + kind)
def ends_with_else(expr): def ends_with_else(expr):
@ -393,7 +374,6 @@ def ends_with_else(expr):
class HyASTCompiler(object): class HyASTCompiler(object):
def __init__(self, module_name): def __init__(self, module_name):
self.allow_builtins = module_name.startswith("hy.core")
self.anon_var_count = 0 self.anon_var_count = 0
self.imports = defaultdict(set) self.imports = defaultdict(set)
self.module_name = module_name self.module_name = module_name
@ -437,6 +417,8 @@ class HyASTCompiler(object):
return ret.stmts return ret.stmts
def compile_atom(self, atom_type, atom): def compile_atom(self, atom_type, atom):
if isinstance(atom_type, string_types):
atom_type = ast_str(atom_type)
if atom_type in _compile_table: if atom_type in _compile_table:
# _compile_table[atom_type] is a method for compiling this # _compile_table[atom_type] is a method for compiling this
# type of atom, so call it. If it has an extra parameter, # type of atom, so call it. If it has an extra parameter,
@ -525,10 +507,11 @@ class HyASTCompiler(object):
compiled_value = self.compile(value) compiled_value = self.compile(value)
ret += compiled_value ret += compiled_value
# no unicode for py2 in ast names keyword = expr[2:]
keyword = str(expr[2:]) if not keyword:
if "-" in keyword and keyword != "-": raise HyTypeError(expr, "Can't call a function with the "
keyword = keyword.replace("-", "_") "empty keyword")
keyword = ast_str(keyword)
keywords.append(asty.keyword( keywords.append(asty.keyword(
expr, arg=keyword, value=compiled_value.force_expr)) expr, arg=keyword, value=compiled_value.force_expr))
@ -699,17 +682,17 @@ class HyASTCompiler(object):
""" """
if level == 0: if level == 0:
if isinstance(form, HyExpression): if isinstance(form, HyExpression):
if form and form[0] in ("unquote", "unquote_splice"): if form and form[0] in ("unquote", "unquote-splice"):
if len(form) != 2: if len(form) != 2:
raise HyTypeError(form, raise HyTypeError(form,
("`%s' needs 1 argument, got %s" % ("`%s' needs 1 argument, got %s" %
form[0], len(form) - 1)) form[0], len(form) - 1))
return set(), form[1], (form[0] == "unquote_splice") return set(), form[1], (form[0] == "unquote-splice")
if isinstance(form, HyExpression): if isinstance(form, HyExpression):
if form and form[0] == "quasiquote": if form and form[0] == "quasiquote":
level += 1 level += 1
if form and form[0] in ("unquote", "unquote_splice"): if form and form[0] in ("unquote", "unquote-splice"):
level -= 1 level -= 1
name = form.__class__.__name__ name = form.__class__.__name__
@ -783,12 +766,12 @@ class HyASTCompiler(object):
ret.add_imports("hy", imports) ret.add_imports("hy", imports)
return ret return ret
@builds("unquote", "unquote_splicing") @builds("unquote", "unquote-splicing")
def compile_unquote(self, expr): def compile_unquote(self, expr):
raise HyTypeError(expr, raise HyTypeError(expr,
"`%s' can't be used at the top-level" % expr[0]) "`%s' can't be used at the top-level" % expr[0])
@builds("unpack_iterable") @builds("unpack-iterable")
@checkargs(exact=1) @checkargs(exact=1)
def compile_unpack_iterable(self, expr): def compile_unpack_iterable(self, expr):
if not PY3: if not PY3:
@ -797,7 +780,7 @@ class HyASTCompiler(object):
ret += asty.Starred(expr, value=ret.force_expr, ctx=ast.Load()) ret += asty.Starred(expr, value=ret.force_expr, ctx=ast.Load())
return ret return ret
@builds("unpack_mapping") @builds("unpack-mapping")
@checkargs(exact=1) @checkargs(exact=1)
def compile_unpack_mapping(self, expr): def compile_unpack_mapping(self, expr):
raise HyTypeError(expr, "`unpack-mapping` isn't allowed here") raise HyTypeError(expr, "`unpack-mapping` isn't allowed here")
@ -1143,12 +1126,12 @@ class HyASTCompiler(object):
ret += self.compile(expr[1]) ret += self.compile(expr[1])
return ret + asty.Yield(expr, value=ret.force_expr) return ret + asty.Yield(expr, value=ret.force_expr)
@builds("yield_from", iff=PY3) @builds("yield-from", iff=PY3)
@builds("await", iff=PY35) @builds("await", iff=PY35)
@checkargs(1) @checkargs(1)
def compile_yield_from_or_await_expression(self, expr): def compile_yield_from_or_await_expression(self, expr):
ret = Result() + self.compile(expr[1]) ret = Result() + self.compile(expr[1])
node = asty.YieldFrom if expr[0] == "yield_from" else asty.Await node = asty.YieldFrom if expr[0] == "yield-from" else asty.Await
return ret + node(expr, value=ret.force_expr) return ret + node(expr, value=ret.force_expr)
@builds("import") @builds("import")
@ -1156,19 +1139,16 @@ class HyASTCompiler(object):
expr = copy.deepcopy(expr) expr = copy.deepcopy(expr)
def _compile_import(expr, module, names=None, importer=asty.Import): def _compile_import(expr, module, names=None, importer=asty.Import):
if not names: if not names:
names = [ast.alias(name=ast_str(module), asname=None)] names = [ast.alias(name=ast_str(module, piecewise=True), asname=None)]
ast_module = ast_str(module) ast_module = ast_str(module, piecewise=True)
module = ast_module.lstrip(".") module = ast_module.lstrip(".")
level = len(ast_module) - len(module) level = len(ast_module) - len(module)
if not module: if not module:
module = None module = None
ret = importer(expr, return Result() + importer(
module=module, expr, module=module, names=names, level=level)
names=names,
level=level)
return Result() + ret
expr.pop(0) # index expr.pop(0) # index
rimports = Result() rimports = Result()
@ -1196,7 +1176,7 @@ class HyASTCompiler(object):
"garbage after aliased import") "garbage after aliased import")
iexpr.pop(0) # :as iexpr.pop(0) # :as
alias = iexpr.pop(0) alias = iexpr.pop(0)
names = [ast.alias(name=ast_str(module), names = [ast.alias(name=ast_str(module, piecewise=True),
asname=ast_str(alias))] asname=ast_str(alias))]
rimports += _compile_import(expr, ast_str(module), names) rimports += _compile_import(expr, ast_str(module), names)
continue continue
@ -1210,7 +1190,7 @@ class HyASTCompiler(object):
alias = ast_str(entry.pop(0)) alias = ast_str(entry.pop(0))
else: else:
alias = None alias = None
names.append(ast.alias(name=ast_str(sym), names.append(ast.alias(name=(str(sym) if sym == "*" else ast_str(sym)),
asname=alias)) asname=alias))
rimports += _compile_import(expr, module, rimports += _compile_import(expr, module,
@ -1307,7 +1287,7 @@ class HyASTCompiler(object):
slice=ast.Slice(lower=nodes[1], upper=nodes[2], step=nodes[3]), slice=ast.Slice(lower=nodes[1], upper=nodes[2], step=nodes[3]),
ctx=ast.Load()) ctx=ast.Load())
@builds("with_decorator") @builds("with-decorator")
@checkargs(min=1) @checkargs(min=1)
def compile_decorate_expression(self, expr): def compile_decorate_expression(self, expr):
expr.pop(0) # with-decorator expr.pop(0) # with-decorator
@ -1403,7 +1383,7 @@ class HyASTCompiler(object):
return gen_res + cond, gen return gen_res + cond, gen
@builds("list_comp", "set_comp", "genexpr") @builds("list-comp", "set-comp", "genexpr")
@checkargs(min=2, max=3) @checkargs(min=2, max=3)
def compile_comprehension(self, expr): def compile_comprehension(self, expr):
# (list-comp expr (target iter) cond?) # (list-comp expr (target iter) cond?)
@ -1421,13 +1401,13 @@ class HyASTCompiler(object):
ret = self.compile(expression) ret = self.compile(expression)
node_class = ( node_class = (
asty.ListComp if form == "list_comp" else asty.ListComp if form == "list-comp" else
asty.SetComp if form == "set_comp" else asty.SetComp if form == "set-comp" else
asty.GeneratorExp) asty.GeneratorExp)
return ret + gen_res + node_class( return ret + gen_res + node_class(
expr, elt=ret.force_expr, generators=gen) expr, elt=ret.force_expr, generators=gen)
@builds("dict_comp") @builds("dict-comp")
@checkargs(min=3, max=4) @checkargs(min=3, max=4)
def compile_dict_comprehension(self, expr): def compile_dict_comprehension(self, expr):
expr.pop(0) # dict-comp expr.pop(0) # dict-comp
@ -1554,15 +1534,16 @@ class HyASTCompiler(object):
values=[value.force_expr for value in values]) values=[value.force_expr for value in values])
return ret return ret
def _compile_compare_op_expression(self, expression):
ops = {"=": ast.Eq, "!=": ast.NotEq, ops = {"=": ast.Eq, "!=": ast.NotEq,
"<": ast.Lt, "<=": ast.LtE, "<": ast.Lt, "<=": ast.LtE,
">": ast.Gt, ">=": ast.GtE, ">": ast.Gt, ">=": ast.GtE,
"is": ast.Is, "is_not": ast.IsNot, "is": ast.Is, "is-not": ast.IsNot,
"in": ast.In, "not_in": ast.NotIn} "in": ast.In, "not-in": ast.NotIn}
ops = {ast_str(k): v for k, v in ops.items()}
inv = expression.pop(0) def _compile_compare_op_expression(self, expression):
ops = [ops[inv]() for _ in range(len(expression) - 1)] inv = ast_str(expression.pop(0))
ops = [self.ops[inv]() for _ in range(len(expression) - 1)]
e = expression[0] e = expression[0]
exprs, ret, _ = self._compile_collect(expression) exprs, ret, _ = self._compile_collect(expression)
@ -1578,12 +1559,12 @@ class HyASTCompiler(object):
asty.Name(expression, id="True", ctx=ast.Load())) asty.Name(expression, id="True", ctx=ast.Load()))
return self._compile_compare_op_expression(expression) return self._compile_compare_op_expression(expression)
@builds("!=", "is_not") @builds("!=", "is-not")
@checkargs(min=2) @checkargs(min=2)
def compile_compare_op_expression_coll(self, expression): def compile_compare_op_expression_coll(self, expression):
return self._compile_compare_op_expression(expression) return self._compile_compare_op_expression(expression)
@builds("in", "not_in") @builds("in", "not-in")
@checkargs(2) @checkargs(2)
def compile_compare_op_expression_binary(self, expression): def compile_compare_op_expression_binary(self, expression):
return self._compile_compare_op_expression(expression) return self._compile_compare_op_expression(expression)
@ -1680,7 +1661,7 @@ class HyASTCompiler(object):
def compile_maths_expression_sub(self, expression): def compile_maths_expression_sub(self, expression):
return self._compile_maths_expression_additive(expression) return self._compile_maths_expression_additive(expression)
@builds("+=", "/=", "//=", "*=", "_=", "%=", "**=", "<<=", ">>=", "|=", @builds("+=", "/=", "//=", "*=", "-=", "%=", "**=", "<<=", ">>=", "|=",
"^=", "&=") "^=", "&=")
@builds("@=", iff=PY35) @builds("@=", iff=PY35)
@checkargs(2) @checkargs(2)
@ -1689,7 +1670,7 @@ class HyASTCompiler(object):
"/=": ast.Div, "/=": ast.Div,
"//=": ast.FloorDiv, "//=": ast.FloorDiv,
"*=": ast.Mult, "*=": ast.Mult,
"_=": ast.Sub, "-=": ast.Sub,
"%=": ast.Mod, "%=": ast.Mod,
"**=": ast.Pow, "**=": ast.Pow,
"<<=": ast.LShift, "<<=": ast.LShift,
@ -1732,7 +1713,7 @@ class HyASTCompiler(object):
if isinstance(fn, HySymbol): if isinstance(fn, HySymbol):
# First check if `fn` is a special form, unless it has an # First check if `fn` is a special form, unless it has an
# `unpack_iterable` in it, since Python's operators (`+`, # `unpack-iterable` in it, since Python's operators (`+`,
# etc.) can't unpack. An exception to this exception is that # etc.) can't unpack. An exception to this exception is that
# tuple literals (`,`) can unpack. # tuple literals (`,`) can unpack.
if fn == "," or not ( if fn == "," or not (
@ -1785,7 +1766,7 @@ class HyASTCompiler(object):
# An exception for pulling together keyword args is if we're doing # An exception for pulling together keyword args is if we're doing
# a typecheck, eg (type :foo) # a typecheck, eg (type :foo)
with_kwargs = fn not in ( with_kwargs = fn not in (
"type", "HyKeyword", "keyword", "name", "is_keyword") "type", "HyKeyword", "keyword", "name", "keyword?")
args, ret, keywords, oldpy_star, oldpy_kw = self._compile_collect( args, ret, keywords, oldpy_star, oldpy_kw = self._compile_collect(
expression[1:], with_kwargs, oldpy_unpack=True) expression[1:], with_kwargs, oldpy_unpack=True)
@ -1813,10 +1794,11 @@ class HyASTCompiler(object):
def _compile_assign(self, name, result): def _compile_assign(self, name, result):
str_name = "%s" % name str_name = "%s" % name
if (_is_hy_builtin(str_name, self.module_name) and if str_name in (["None"] + (["True", "False"] if PY3 else [])):
not self.allow_builtins): # Python 2 allows assigning to True and False, although
# this is rarely wise.
raise HyTypeError(name, raise HyTypeError(name,
"Can't assign to a builtin: `%s'" % str_name) "Can't assign to `%s'" % str_name)
result = self.compile(result) result = self.compile(result)
ld_name = self.compile(name) ld_name = self.compile(name)
@ -2057,7 +2039,7 @@ class HyASTCompiler(object):
pairs = expr[1:] pairs = expr[1:]
while len(pairs) > 0: while len(pairs) > 0:
k, v = (pairs.pop(0), pairs.pop(0)) k, v = (pairs.pop(0), pairs.pop(0))
if k == HySymbol("__init__"): if ast_str(k) == "__init__":
v.append(HySymbol("None")) v.append(HySymbol("None"))
new_args.append(k) new_args.append(k)
new_args.append(v) new_args.append(v)
@ -2092,8 +2074,6 @@ class HyASTCompiler(object):
body += self._compile_assign(symb, docstring) body += self._compile_assign(symb, docstring)
body += body.expr_as_stmt() body += body.expr_as_stmt()
allow_builtins = self.allow_builtins
self.allow_builtins = True
if expressions and isinstance(expressions[0], HyList) \ if expressions and isinstance(expressions[0], HyList) \
and not isinstance(expressions[0], HyExpression): and not isinstance(expressions[0], HyExpression):
expr = expressions.pop(0) expr = expressions.pop(0)
@ -2105,8 +2085,6 @@ class HyASTCompiler(object):
for expression in expressions: for expression in expressions:
body += self.compile(rewire_init(macroexpand(expression, self))) body += self.compile(rewire_init(macroexpand(expression, self)))
self.allow_builtins = allow_builtins
if not body.stmts: if not body.stmts:
body += asty.Pass(expressions) body += asty.Pass(expressions)
@ -2120,7 +2098,7 @@ class HyASTCompiler(object):
bases=bases_expr, bases=bases_expr,
body=body.stmts) body=body.stmts)
@builds("dispatch_tag_macro") @builds("dispatch-tag-macro")
@checkargs(exact=2) @checkargs(exact=2)
def compile_dispatch_tag_macro(self, expression): def compile_dispatch_tag_macro(self, expression):
expression.pop(0) # dispatch-tag-macro expression.pop(0) # dispatch-tag-macro
@ -2131,11 +2109,11 @@ class HyASTCompiler(object):
"Trying to expand a tag macro using `{0}' instead " "Trying to expand a tag macro using `{0}' instead "
"of string".format(type(tag).__name__), "of string".format(type(tag).__name__),
) )
tag = HyString(hy_symbol_mangle(str(tag))).replace(tag) tag = HyString(mangle(tag)).replace(tag)
expr = tag_macroexpand(tag, expression.pop(0), self) expr = tag_macroexpand(tag, expression.pop(0), self)
return self.compile(expr) return self.compile(expr)
@builds("eval_and_compile", "eval_when_compile") @builds("eval-and-compile", "eval-when-compile")
def compile_eval_and_compile(self, expression, building): def compile_eval_and_compile(self, expression, building):
expression[0] = HySymbol("do") expression[0] = HySymbol("do")
hy.importer.hy_eval(expression, hy.importer.hy_eval(expression,
@ -2198,8 +2176,8 @@ class HyASTCompiler(object):
attr=ast_str(local), attr=ast_str(local),
ctx=ast.Load()) ctx=ast.Load())
if symbol in _stdlib: if ast_str(symbol) in _stdlib:
self.imports[_stdlib[symbol]].add(symbol) self.imports[_stdlib[ast_str(symbol)]].add(ast_str(symbol))
return asty.Name(symbol, id=ast_str(symbol), ctx=ast.Load()) return asty.Name(symbol, id=ast_str(symbol), ctx=ast.Load())

View File

@ -75,9 +75,9 @@
'quote "'" 'quote "'"
'quasiquote "`" 'quasiquote "`"
'unquote "~" 'unquote "~"
'unquote_splice "~@" 'unquote-splice "~@"
'unpack_iterable "#* " 'unpack-iterable "#* "
'unpack_mapping "#** "}) 'unpack-mapping "#** "})
(if (and x (symbol? (first x)) (in (first x) syntax)) (if (and x (symbol? (first x)) (in (first x) syntax))
(+ (get syntax (first x)) (hy-repr (second x))) (+ (get syntax (first x)) (hy-repr (second x)))
(+ "(" (-cat x) ")")))) (+ "(" (-cat x) ")"))))

View File

@ -257,7 +257,7 @@ Arguments without a header are under None.
(= head 'defclass) (self.handle-defclass) (= head 'defclass) (self.handle-defclass)
(= head 'quasiquote) (self.+quote) (= head 'quasiquote) (self.+quote)
;; must be checked last! ;; must be checked last!
(in head special-forms) (self.handle-special-form) (in (mangle head) special-forms) (self.handle-special-form)
;; Not a special form. Traverse it like a coll ;; Not a special form. Traverse it like a coll
(self.handle-coll))) (self.handle-coll)))

View File

@ -18,6 +18,7 @@
(import [hy._compat [long-type]]) ; long for python2, int for python3 (import [hy._compat [long-type]]) ; long for python2, int for python3
(import [hy.models [HyCons HySymbol HyKeyword]]) (import [hy.models [HyCons HySymbol HyKeyword]])
(import [hy.lex [LexException PrematureEndOfInput tokenize]]) (import [hy.lex [LexException PrematureEndOfInput tokenize]])
(import [hy.lex.parser [mangle unmangle]])
(import [hy.compiler [HyASTCompiler spoof-positions]]) (import [hy.compiler [HyASTCompiler spoof-positions]])
(import [hy.importer [hy-eval :as eval]]) (import [hy.importer [hy-eval :as eval]])
@ -87,7 +88,7 @@ If the second argument `codegen` is true, generate python code instead."
"Return a generator from the original collection `coll` with no duplicates." "Return a generator from the original collection `coll` with no duplicates."
(setv seen (set) citer (iter coll)) (setv seen (set) citer (iter coll))
(for* [val citer] (for* [val citer]
(if (not_in val seen) (if (not-in val seen)
(do (do
(yield val) (yield val)
(.add seen val))))) (.add seen val)))))
@ -453,20 +454,16 @@ as EOF (defaults to an empty string)."
"Reads and tokenizes first line of `input`." "Reads and tokenizes first line of `input`."
(read :from-file (StringIO input))) (read :from-file (StringIO input)))
(defn hyify [text]
"Convert `text` to match hy identifier."
(.replace (string text) "_" "-"))
(defn keyword [value] (defn keyword [value]
"Create a keyword from `value`. "Create a keyword from `value`.
Strings numbers and even objects with the __name__ magic will work." Strings numbers and even objects with the __name__ magic will work."
(if (and (string? value) (value.startswith HyKeyword.PREFIX)) (if (and (string? value) (value.startswith HyKeyword.PREFIX))
(hyify value) (unmangle value)
(if (string? value) (if (string? value)
(HyKeyword (+ ":" (hyify value))) (HyKeyword (+ ":" (unmangle value)))
(try (try
(hyify (.__name__ value)) (unmangle (.__name__ value))
(except [] (HyKeyword (+ ":" (string value)))))))) (except [] (HyKeyword (+ ":" (string value))))))))
(defn name [value] (defn name [value]
@ -475,11 +472,11 @@ Strings numbers and even objects with the __name__ magic will work."
Keyword special character will be stripped. String will be used as is. Keyword special character will be stripped. String will be used as is.
Even objects with the __name__ magic will work." Even objects with the __name__ magic will work."
(if (and (string? value) (value.startswith HyKeyword.PREFIX)) (if (and (string? value) (value.startswith HyKeyword.PREFIX))
(hyify (cut value 2)) (unmangle (cut value 2))
(if (string? value) (if (string? value)
(hyify value) (unmangle value)
(try (try
(hyify (. value __name__)) (unmangle (. value __name__))
(except [] (string value)))))) (except [] (string value))))))
(defn xor [a b] (defn xor [a b]
@ -488,14 +485,14 @@ Even objects with the __name__ magic will work."
False False
(or a b))) (or a b)))
(setv *exports* (setv EXPORTS
'[*map accumulate butlast calling-module-name chain coll? combinations '[*map accumulate butlast calling-module-name chain coll? combinations
comp complement compress cons cons? constantly count cycle dec distinct comp complement compress cons cons? constantly count cycle dec distinct
disassemble drop drop-last drop-while empty? eval even? every? exec first disassemble drop drop-last drop-while empty? eval even? every? exec first
filter flatten float? fraction gensym group-by identity inc input instance? filter flatten float? fraction gensym group-by identity inc input instance?
integer integer? integer-char? interleave interpose islice iterable? integer integer? integer-char? interleave interpose islice iterable?
iterate iterator? juxt keyword keyword? last list* macroexpand iterate iterator? juxt keyword keyword? last list* macroexpand
macroexpand-1 map merge-with multicombinations name neg? none? nth macroexpand-1 mangle map merge-with multicombinations name neg? none? nth
numeric? odd? partition permutations pos? product range read read-str numeric? odd? partition permutations pos? product range read read-str
remove repeat repeatedly rest reduce second some string string? symbol? remove repeat repeatedly rest reduce second some string string? symbol?
take take-nth take-while xor tee zero? zip zip-longest]) take take-nth take-while unmangle xor tee zero? zip zip-longest])

View File

@ -163,7 +163,7 @@
(setv coll (get coll k))) (setv coll (get coll k)))
coll) coll)
(setv *exports* [ (setv EXPORTS [
'+ '- '* '** '/ '// '% '@ '+ '- '* '** '/ '// '% '@
'<< '>> '& '| '^ '~ '<< '>> '& '| '^ '~
'< '> '<= '>= '= '!= '< '> '<= '>= '= '!=
@ -171,4 +171,4 @@
'is 'is-not 'in 'not-in 'is 'is-not 'in 'not-in
'get]) 'get])
(if (not PY35) (if (not PY35)
(.remove *exports* '@)) (.remove EXPORTS '@))

View File

@ -13,10 +13,9 @@
The result of the first call is cached." The result of the first call is cached."
(global _cache) (global _cache)
(if (is _cache None) (do (if (is _cache None) (do
(setv unmangle (. sys.modules ["hy.lex.parser"] hy_symbol_unmangle))
(setv _cache (frozenset (map unmangle (+ (setv _cache (frozenset (map unmangle (+
hy.core.language.*exports* hy.core.language.EXPORTS
hy.core.shadow.*exports* hy.core.shadow.EXPORTS
(list (.keys (get hy.macros._hy_macros None))) (list (.keys (get hy.macros._hy_macros None)))
keyword.kwlist keyword.kwlist
(list-comp k [k (.keys hy.compiler.-compile-table)] (list-comp k [k (.keys hy.compiler.-compile-table)]

View File

@ -1,3 +1,4 @@
# -*- encoding: utf-8 -*-
# Copyright 2018 the authors. # Copyright 2018 the authors.
# This file is part of Hy, which is free software licensed under the Expat # This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE. # license. See the LICENSE.
@ -5,10 +6,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from functools import wraps from functools import wraps
import string, re, unicodedata
from rply import ParserGenerator from rply import ParserGenerator
from hy._compat import str_type from hy._compat import PY3, str_type, isidentifier
from hy.models import (HyBytes, HyComplex, HyCons, HyDict, HyExpression, from hy.models import (HyBytes, HyComplex, HyCons, HyDict, HyExpression,
HyFloat, HyInteger, HyKeyword, HyList, HySet, HyString, HyFloat, HyInteger, HyKeyword, HyList, HySet, HyString,
HySymbol) HySymbol)
@ -21,43 +23,65 @@ pg = ParserGenerator(
cache_id="hy_parser" cache_id="hy_parser"
) )
mangle_delim = 'Δ' if PY3 else 'X'
def hy_symbol_mangle(p): def mangle(s):
if p.startswith("*") and p.endswith("*") and p not in ("*", "**"): """Stringify the argument and convert it to a valid Python identifier
p = p[1:-1].upper() according to Hy's mangling rules."""
if "-" in p and p != "-": assert s
p = p.replace("-", "_")
if p.endswith("?") and p != "?": s = str_type(s)
p = "is_%s" % (p[:-1])
if p.endswith("!") and p != "!": s = s.replace("-", "_")
p = "%s_bang" % (p[:-1]) s2 = s.lstrip('_')
leading_underscores = '_' * (len(s) - len(s2))
s = s2
return p if s.endswith("?"):
s = 'is_' + s[:-1]
if not isidentifier(leading_underscores + s):
# Replace illegal characters with their Unicode character
# names, or hexadecimal if they don't have one.
s = 'hyx_' + ''.join(
c
if c != mangle_delim and isidentifier('S' + c)
# We prepend the "S" because some characters aren't
# allowed at the start of an identifier.
else '{0}{1}{0}'.format(mangle_delim,
unicodedata.name(c, '').lower().replace('-', 'H').replace(' ', '_')
or 'U{:x}'.format(ord(c)))
for c in s)
s = leading_underscores + s
assert isidentifier(s)
return s
def hy_symbol_unmangle(p): def unmangle(s):
# hy_symbol_mangle is one-way, so this can't be perfect. """Stringify the argument and try to convert it to a pretty unmangled
# But it can be useful till we have a way to get the original form. This may not round-trip, because different Hy symbol names can
# symbol (https://github.com/hylang/hy/issues/360). mangle to the same Python identifier."""
p = str_type(p)
if p.endswith("_bang") and p != "_bang": s = str_type(s)
p = p[:-len("_bang")] + "!"
if p.startswith("is_") and p != "is_": s2 = s.lstrip('_')
p = p[len("is_"):] + "?" leading_underscores = len(s) - len(s2)
s = s2
if "_" in p and p != "_": if s.startswith('hyx_'):
p = p.replace("_", "-") s = re.sub('{0}(U)?([_a-z0-9H]+?){0}'.format(mangle_delim),
lambda mo:
chr(int(mo.group(2), base=16))
if mo.group(1)
else unicodedata.lookup(
mo.group(2).replace('_', ' ').replace('H', '-').upper()),
s[len('hyx_'):])
if s.startswith('is_'):
s = s[len("is_"):] + "?"
s = s.replace('_', '-')
if (all([c.isalpha() and c.isupper() or c == '_' for c in p]) and return '-' * leading_underscores + s
any([c.isalpha() for c in p])):
p = '*' + p.lower() + '*'
return p
def set_boundaries(fun): def set_boundaries(fun):
@ -201,7 +225,7 @@ def term_unquote(p):
@pg.production("term : UNQUOTESPLICE term") @pg.production("term : UNQUOTESPLICE term")
@set_quote_boundaries @set_quote_boundaries
def term_unquote_splice(p): def term_unquote_splice(p):
return HyExpression([HySymbol("unquote_splice"), p[1]]) return HyExpression([HySymbol("unquote-splice"), p[1]])
@pg.production("term : HASHSTARS term") @pg.production("term : HASHSTARS term")
@ -209,9 +233,9 @@ def term_unquote_splice(p):
def term_hashstars(p): def term_hashstars(p):
n_stars = len(p[0].getstr()[1:]) n_stars = len(p[0].getstr()[1:])
if n_stars == 1: if n_stars == 1:
sym = "unpack_iterable" sym = "unpack-iterable"
elif n_stars == 2: elif n_stars == 2:
sym = "unpack_mapping" sym = "unpack-mapping"
else: else:
raise LexException( raise LexException(
"Too many stars in `#*` construct (if you want to unpack a symbol " "Too many stars in `#*` construct (if you want to unpack a symbol "
@ -227,7 +251,7 @@ def hash_other(p):
st = p[0].getstr()[1:] st = p[0].getstr()[1:]
str_object = HyString(st) str_object = HyString(st)
expr = p[1] expr = p[1]
return HyExpression([HySymbol("dispatch_tag_macro"), str_object, expr]) return HyExpression([HySymbol("dispatch-tag-macro"), str_object, expr])
@pg.production("set : HLCURLY list_contents RCURLY") @pg.production("set : HLCURLY list_contents RCURLY")
@ -307,7 +331,7 @@ def t_identifier(p):
'`(. <expression> <attr>)` or `(.<attr> <expression>)`)', '`(. <expression> <attr>)` or `(.<attr> <expression>)`)',
p[0].source_pos.lineno, p[0].source_pos.colno) p[0].source_pos.lineno, p[0].source_pos.colno)
return HySymbol(".".join(hy_symbol_mangle(x) for x in obj.split("."))) return HySymbol(obj)
def symbol_like(obj): def symbol_like(obj):

View File

@ -2,8 +2,12 @@
# This file is part of Hy, which is free software licensed under the Expat # This file is part of Hy, which is free software licensed under the Expat
# license. See the LICENSE. # license. See the LICENSE.
from hy._compat import PY3
import hy.inspect import hy.inspect
from hy.models import replace_hy_obj, HyExpression, HySymbol from hy.models import replace_hy_obj, HyExpression, HySymbol
from hy.lex.parser import mangle
from hy._compat import str_type
from hy.errors import HyTypeError, HyMacroExpansionError from hy.errors import HyTypeError, HyMacroExpansionError
from collections import defaultdict from collections import defaultdict
@ -32,6 +36,7 @@ def macro(name):
This function is called from the `defmacro` special form in the compiler. This function is called from the `defmacro` special form in the compiler.
""" """
name = mangle(name)
def _(fn): def _(fn):
fn.__name__ = '({})'.format(name) fn.__name__ = '({})'.format(name)
try: try:
@ -62,11 +67,14 @@ def tag(name):
""" """
def _(fn): def _(fn):
fn.__name__ = '#{}'.format(name) _name = mangle('#{}'.format(name))
if not PY3:
_name = _name.encode('UTF-8')
fn.__name__ = _name
module_name = fn.__module__ module_name = fn.__module__
if module_name.startswith("hy.core"): if module_name.startswith("hy.core"):
module_name = None module_name = None
_hy_tag[module_name][name] = fn _hy_tag[module_name][mangle(name)] = fn
return fn return fn
return _ return _
@ -89,14 +97,15 @@ def require(source_module, target_module,
seen_names = set() seen_names = set()
if prefix: if prefix:
prefix += "." prefix += "."
assignments = {mangle(str_type(k)): v for k, v in assignments.items()}
for d in _hy_macros, _hy_tag: for d in _hy_macros, _hy_tag:
for name, macro in d[source_module].items(): for name, macro in d[source_module].items():
seen_names.add(name) seen_names.add(name)
if all_macros: if all_macros:
d[target_module][prefix + name] = macro d[target_module][mangle(prefix + name)] = macro
elif name in assignments: elif name in assignments:
d[target_module][prefix + assignments[name]] = macro d[target_module][mangle(prefix + assignments[name])] = macro
if not all_macros: if not all_macros:
unseen = frozenset(assignments.keys()).difference(seen_names) unseen = frozenset(assignments.keys()).difference(seen_names)
@ -178,6 +187,7 @@ def macroexpand_1(tree, compiler):
opts = {} opts = {}
if isinstance(fn, HySymbol): if isinstance(fn, HySymbol):
fn = mangle(str_type(fn))
m = _hy_macros[compiler.module_name].get(fn) m = _hy_macros[compiler.module_name].get(fn)
if m is None: if m is None:
m = _hy_macros[None].get(fn) m = _hy_macros[None].get(fn)

View File

@ -338,7 +338,7 @@ class HyCons(HyObject):
# Keep unquotes in the cdr of conses # Keep unquotes in the cdr of conses
if type(cdr) == HyExpression: if type(cdr) == HyExpression:
if len(cdr) > 0 and type(cdr[0]) == HySymbol: if len(cdr) > 0 and type(cdr[0]) == HySymbol:
if cdr[0] in ("unquote", "unquote_splice"): if cdr[0] in ("unquote", "unquote-splice"):
return super(HyCons, cls).__new__(cls) return super(HyCons, cls).__new__(cls)
return cdr.__class__([wrap_value(car)] + cdr) return cdr.__class__([wrap_value(car)] + cdr)

View File

@ -596,13 +596,11 @@ def test_invalid_list_comprehension():
def test_bad_setv(): def test_bad_setv():
"""Ensure setv handles error cases""" """Ensure setv handles error cases"""
cant_compile("(setv if* 1)")
cant_compile("(setv (a b) [1 2])") cant_compile("(setv (a b) [1 2])")
def test_defn(): def test_defn():
"""Ensure that defn works correctly in various corner cases""" """Ensure that defn works correctly in various corner cases"""
cant_compile("(defn if* [] 1)")
cant_compile("(defn \"hy\" [] 1)") cant_compile("(defn \"hy\" [] 1)")
cant_compile("(defn :hy [] 1)") cant_compile("(defn :hy [] 1)")
can_compile("(defn &hy [] 1)") can_compile("(defn &hy [] 1)")
@ -611,7 +609,6 @@ def test_defn():
def test_setv_builtins(): def test_setv_builtins():
"""Ensure that assigning to a builtin fails, unless in a class""" """Ensure that assigning to a builtin fails, unless in a class"""
cant_compile("(setv None 42)") cant_compile("(setv None 42)")
cant_compile("(defn get [&rest args] 42)")
can_compile("(defclass A [] (defn get [self] 42))") can_compile("(defclass A [] (defn get [self] 42))")
can_compile(""" can_compile("""
(defclass A [] (defclass A []

View File

@ -65,19 +65,19 @@
(defn test-setv-builtin [] (defn test-setv-builtin []
"NATIVE: test that setv doesn't work on builtins" "NATIVE: test that setv doesn't work on names Python can't assign to
(try (eval '(setv False 1)) and that we can't mangle"
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
(try (eval '(setv True 0))
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))
(try (eval '(setv None 1)) (try (eval '(setv None 1))
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e))))) (except [e [TypeError]] (assert (in "Can't assign to" (str e)))))
(try (eval '(defn defclass [] (print "hello"))) (try (eval '(defn None [] (print "hello")))
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e))))) (except [e [TypeError]] (assert (in "Can't assign to" (str e)))))
(try (eval '(defn get [] (print "hello"))) (when PY3
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e))))) (try (eval '(setv False 1))
(try (eval '(defn fn [] (print "hello"))) (except [e [TypeError]] (assert (in "Can't assign to" (str e)))))
(except [e [TypeError]] (assert (in "Can't assign to a builtin" (str e)))))) (try (eval '(setv True 0))
(except [e [TypeError]] (assert (in "Can't assign to" (str e)))))
(try (eval '(defn True [] (print "hello")))
(except [e [TypeError]] (assert (in "Can't assign to" (str e)))))))
(defn test-setv-pairs [] (defn test-setv-pairs []
@ -187,14 +187,6 @@
(assert (in "takes a parameter list as second" (str e)))))) (assert (in "takes a parameter list as second" (str e))))))
(defn test-alias-names-in-errors []
"NATIVE: tests that native aliases show the correct names in errors"
(try (eval '(list-comp 1 2 3 4))
(except [e [Exception]] (assert (in "list_comp" (str e)))))
(try (eval '(set-comp 1 2 3 4))
(except [e [Exception]] (assert (in "set_comp" (str e))))))
(defn test-for-loop [] (defn test-for-loop []
"NATIVE: test for loops" "NATIVE: test for loops"
(setv count1 0 count2 0) (setv count1 0 count2 0)
@ -223,14 +215,14 @@
; don't be fooled by constructs that look like else ; don't be fooled by constructs that look like else
(setv s "") (setv s "")
(setv (get (globals) "else") True) (setv else True)
(for [x "abcde"] (for [x "abcde"]
(+= s x) (+= s x)
[else (+= s "_")]) [else (+= s "_")])
(assert (= s "a_b_c_d_e_")) (assert (= s "a_b_c_d_e_"))
(setv s "") (setv s "")
(setv (get (globals) "else") True) (setv else True)
(with [(pytest.raises TypeError)] (with [(pytest.raises TypeError)]
(for [x "abcde"] (for [x "abcde"]
(+= s x) (+= s x)
@ -329,7 +321,7 @@
; don't be fooled by constructs that look like else clauses ; don't be fooled by constructs that look like else clauses
(setv x 2) (setv x 2)
(setv a []) (setv a [])
(setv (get (globals) "else") True) (setv else True)
(while x (while x
(.append a x) (.append a x)
(-= x 1) (-= x 1)
@ -738,13 +730,6 @@
(assert (= x 2))) (assert (= x 2)))
(defn test-earmuffs []
"NATIVE: Test earmuffs"
(setv *foo* "2")
(setv foo "3")
(assert (= *foo* FOO))
(assert (!= *foo* foo)))
(defn test-threading [] (defn test-threading []
"NATIVE: test threading macro" "NATIVE: test threading macro"
@ -1112,27 +1097,6 @@
(assert (= ((fn [] (-> 2 (+ 1 1) (* 1 2)))) 8))) (assert (= ((fn [] (-> 2 (+ 1 1) (* 1 2)))) 8)))
(defn test-symbol-utf-8 []
"NATIVE: test symbol encoded"
(setv "love"
"flower")
(assert (= (+ ) "flowerlove")))
(defn test-symbol-dash []
"NATIVE: test symbol encoded"
(setv - "doublelove"
-_- "what?")
(assert (= - "doublelove"))
(assert (= -_- "what?")))
(defn test-symbol-question-mark []
"NATIVE: test foo? -> is_foo behavior"
(setv foo? "nachos")
(assert (= is_foo "nachos")))
(defn test-and [] (defn test-and []
"NATIVE: test the and function" "NATIVE: test the and function"
@ -1260,11 +1224,7 @@
(assert (= : :)) (assert (= : :))
(assert (keyword? :)) (assert (keyword? :))
(assert (!= : ":")) (assert (!= : ":"))
(assert (= (name :) "")) (assert (= (name :) "")))
(defn f [&kwargs kwargs]
(list (.items kwargs)))
(assert (= (f : 3) [(, "" 3)])))
(defn test-nested-if [] (defn test-nested-if []
@ -1816,4 +1776,4 @@ macros()
(defn test-relative-import [] (defn test-relative-import []
"Make sure relative imports work properly" "Make sure relative imports work properly"
(import [..resources [tlib]]) (import [..resources [tlib]])
(assert (= tlib.*secret-message* "Hello World"))) (assert (= tlib.SECRET-MESSAGE "Hello World")))

View File

@ -0,0 +1,189 @@
;; Copyright 2018 the authors.
;; This file is part of Hy, which is free software licensed under the Expat
;; license. See the LICENSE.
(import [hy._compat [PY3]])
(defn test-hyphen []
(setv a-b 1)
(assert (= a-b 1))
(assert (= a_b 1))
(setv -a-_b- 2)
(assert (= -a-_b- 2))
(assert (= -a--b- 2))
(assert (= -a__b- 2))
(setv -_- 3)
(assert (= -_- 3))
(assert (= --- 3))
(assert (= ___ 3)))
(defn test-underscore-number []
(setv _42 3)
(assert (= _42 3))
(assert (!= _42 -42))
(assert (not (in "_hyx_42" (locals)))))
(defn test-question-mark []
(setv foo? "nachos")
(assert (= foo? "nachos"))
(assert (= is_foo "nachos"))
(setv ___ab_cd? "tacos")
(assert (= ___ab_cd? "tacos"))
(assert (= ___is_ab_cd "tacos")))
(defn test-py-forbidden-ascii []
(setv # "no comment")
(assert (= # "no comment"))
(if PY3
(assert (= hyx_Δnumber_signΔ "no comment"))
(assert (= hyx_Xnumber_signX "no comment")))
(setv $ "dosh")
(assert (= $ "dosh"))
(if PY3
(assert (= hyx_Δdollar_signΔ "dosh"))
(assert (= hyx_Xdollar_signX "dosh"))))
(defn test-basic-multilingual-plane []
(setv "love"
ab "flower")
(assert (= (+ ab ) "flowerlove"))
(if PY3
(assert (= (+ hyx_ΔflowerΔab hyx_Δblack_heart_suitΔ) "flowerlove"))
(assert (= (+ hyx_XflowerXab hyx_Xblack_heart_suitX) "flowerlove")))
(setv - "doubleflower")
(assert (= - "doubleflower"))
(if PY3
(assert (= hyx_ΔflowerΔ_ΔflowerΔ "doubleflower"))
(assert (= hyx_XflowerX_XflowerX "doubleflower")))
(setv ? "mystery")
(assert (= ? "mystery"))
(if PY3
(assert (= hyx_is_ΔflowerΔ "mystery"))
(assert (= hyx_is_XflowerX "mystery"))))
(defn test-higher-unicode []
(setv 😂 "emoji")
(assert (= 😂 "emoji"))
(if PY3
(assert (= hyx_Δface_with_tears_of_joyΔ "emoji"))
(assert (= hyx_XU1f602X "emoji"))))
(defn test-nameless-unicode []
(setv "private use")
(assert (= "private use"))
(if PY3
(assert (= hyx_ΔUe000Δ "private use"))
(assert (= hyx_XUe000X "private use"))))
(defn test-charname-with-hyphen []
(setv a<b "little")
(assert (= a<b "little"))
(if PY3
(assert (= hyx_aΔlessHthan_signΔb "little"))
(assert (= hyx_aXlessHthan_signXb "little"))))
(defn test-delimiters []
(setv Δ "Delta Air Lines")
(assert (= Δ "Delta Air Lines"))
(if PY3
(assert (= hyx_Δgreek_capital_letter_deltaΔΔairplaneΔ "Delta Air Lines"))
(assert (= hyx_Xgreek_capital_letter_deltaXXairplaneX "Delta Air Lines")))
(setv X "treasure")
(if PY3
(assert (= hyx_XΔskull_and_crossbonesΔ "treasure"))
(assert (= hyx_Xlatin_capital_letter_xXXskull_and_crossbonesX "treasure"))))
(defmacro m---x [form]
[form form])
(defn test-macro []
(setv x "")
(assert (= (m---x (do (+= x "a") 1)) [1 1]))
(assert (= (m___x (do (+= x "b") 2)) [2 2]))
(assert (= x "aabb")))
(deftag tm---x [form]
[form form])
(defn test-tag-macro []
(setv x "")
(assert (= #tm---x (do (+= x "a") 1) [1 1]))
(assert (= #tm___x (do (+= x "b") 2) [2 2]))
(assert (= x "aabb")))
(defn test-special-form []
(setv not-in 1)
; We set the variable to make sure that if this test works, it's
; because we're calling the special form instead of the shadow
; function.
(assert (is (not-in 2 [1 2 3]) False))
(assert (is (not_in 2 [1 2 3]) False)))
(defn test-python-keyword []
(setv if 3)
(assert (= if 3))
(assert (= hyx_if 3)))
(defn test-operator []
(setv + 3)
(assert (= + 3))
(if PY3
(assert (= hyx_Δplus_signΔ 3))
(assert (= hyx_Xplus_signX 3))))
(defn test-keyword-args []
(defn f [a a-b foo? ]
[a a-b foo? ])
(assert (= (f :foo? 3 : 4 :a 1 :a-b 2) [1 2 3 4]))
(if PY3
(assert (= (f :is_foo 3 :hyx_ΔshamrockΔ 4 :a 1 :a_b 2) [1 2 3 4]))
(assert (= (f :is_foo 3 :hyx_XshamrockX 4 :a 1 :a_b 2) [1 2 3 4])))
(defn g [&kwargs x]
x)
(setv sk (.format "hyx_{0}shamrock{0}" (if PY3 "Δ" "X")))
(assert (= (g :foo? 3 : 4 :a 1 :a-b 2)
{"a" 1 "a_b" 2 "is_foo" 3 sk 4}))
(if PY3
(assert (= (g :is_foo 3 :hyx_ΔshamrockΔ 4 :a 1 :a_b 2)
{"a" 1 "a_b" 2 "is_foo" 3 sk 4}))
(assert (= (g :is_foo 3 :hyx_XshamrockX 4 :a 1 :a_b 2)
{"a" 1 "a_b" 2 "is_foo" 3 sk 4}))))
(defn test-late-mangling []
; Mangling should only happen during compilation.
(assert (!= 'foo? 'is_foo))
(setv sym 'foo?)
(assert (= sym "foo?"))
(assert (!= sym "is_foo"))
(setv out (eval `(do
(setv ~sym 10)
[foo? is_foo])))
(assert (= out [10 10])))
(defn test-functions []
(for [[a b] [
["---ab-cd?" "___is_ab_cd"]
["if" "hyx_if"]
["⚘-⚘" (if PY3 "hyx_ΔflowerΔ_ΔflowerΔ" "hyx_XflowerX_XflowerX")]]]
(assert (= (mangle a) b))
(assert (= (unmangle b) a))))

View File

@ -162,9 +162,9 @@
(setv _ast2 (import_buffer_to_ast macro1 "foo")) (setv _ast2 (import_buffer_to_ast macro1 "foo"))
(setv s1 (to_source _ast1)) (setv s1 (to_source _ast1))
(setv s2 (to_source _ast2)) (setv s2 (to_source _ast2))
;; and make sure there is something new that starts with :G_ ;; and make sure there is something new that starts with _;G|
(assert (in "_;G|" s1)) (assert (in (mangle "_;G|") s1))
(assert (in "_;G|" s2)) (assert (in (mangle "_;G|") s2))
;; but make sure the two don't match each other ;; but make sure the two don't match each other
(assert (not (= s1 s2)))) (assert (not (= s1 s2))))
@ -188,8 +188,8 @@
(setv _ast2 (import_buffer_to_ast macro1 "foo")) (setv _ast2 (import_buffer_to_ast macro1 "foo"))
(setv s1 (to_source _ast1)) (setv s1 (to_source _ast1))
(setv s2 (to_source _ast2)) (setv s2 (to_source _ast2))
(assert (in "_;a|" s1)) (assert (in (mangle "_;a|") s1))
(assert (in "_;a|" s2)) (assert (in (mangle "_;a|") s2))
(assert (not (= s1 s2)))) (assert (not (= s1 s2))))
(defn test-defmacro-g! [] (defn test-defmacro-g! []

View File

@ -247,9 +247,9 @@
(forbid (f)) (forbid (f))
(forbid (f "hello")) (forbid (f "hello"))
(defclass C) (defclass C)
(setv x (get {"is_not" (C) "!=" 0} f-name)) (setv x (get {"is-not" (C) "!=" 0} f-name))
(setv y (get {"is_not" (C) "!=" 1} f-name)) (setv y (get {"is-not" (C) "!=" 1} f-name))
(setv z (get {"is_not" (C) "!=" 2} f-name)) (setv z (get {"is-not" (C) "!=" 2} f-name))
(assert (is (f x x) False)) (assert (is (f x x) False))
(assert (is (f y y) False)) (assert (is (f y y) False))
(assert (is (f x y) True)) (assert (is (f x y) True))

View File

@ -74,6 +74,11 @@ def test_bin_hy_stdin_multiline():
assert "'abcd'" in output assert "'abcd'" in output
def test_bin_hy_history():
output, _ = run_cmd("hy", '(+ "a" "b")\n(+ *1 "y" "z")')
assert "'abyz'" in output
def test_bin_hy_stdin_comments(): def test_bin_hy_stdin_comments():
_, err_empty = run_cmd("hy", '') _, err_empty = run_cmd("hy", '')

View File

@ -121,8 +121,8 @@ def test_lex_nan_and_inf():
assert tokenize("INF") == [HySymbol("INF")] assert tokenize("INF") == [HySymbol("INF")]
assert tokenize("-Inf") == [HyFloat(float("-inf"))] assert tokenize("-Inf") == [HyFloat(float("-inf"))]
assert tokenize("-inf") == [HySymbol("_inf")] assert tokenize("-inf") == [HySymbol("-inf")]
assert tokenize("-INF") == [HySymbol("_INF")] assert tokenize("-INF") == [HySymbol("-INF")]
def test_lex_expression_complex(): def test_lex_expression_complex():
@ -140,7 +140,7 @@ def test_lex_expression_complex():
assert t("nanj") == f(HySymbol("nanj")) assert t("nanj") == f(HySymbol("nanj"))
assert t("Inf+Infj") == f(HyComplex(complex(float("inf"), float("inf")))) assert t("Inf+Infj") == f(HyComplex(complex(float("inf"), float("inf"))))
assert t("Inf-Infj") == f(HyComplex(complex(float("inf"), float("-inf")))) assert t("Inf-Infj") == f(HyComplex(complex(float("inf"), float("-inf"))))
assert t("Inf-INFj") == f(HySymbol("Inf_INFj")) assert t("Inf-INFj") == f(HySymbol("Inf-INFj"))
def test_lex_digit_separators(): def test_lex_digit_separators():
@ -332,7 +332,7 @@ def test_complex():
def test_tag_macro(): def test_tag_macro():
"""Ensure tag macros are handled properly""" """Ensure tag macros are handled properly"""
entry = tokenize("#^()") entry = tokenize("#^()")
assert entry[0][0] == HySymbol("dispatch_tag_macro") assert entry[0][0] == HySymbol("dispatch-tag-macro")
assert entry[0][1] == HyString("^") assert entry[0][1] == HyString("^")
assert len(entry[0]) == 3 assert len(entry[0]) == 3
@ -343,78 +343,6 @@ def test_lex_comment_382():
assert entry == [HySymbol("foo")] assert entry == [HySymbol("foo")]
def test_lex_mangling_star():
"""Ensure that mangling starred identifiers works according to plan"""
entry = tokenize("*foo*")
assert entry == [HySymbol("FOO")]
entry = tokenize("*")
assert entry == [HySymbol("*")]
entry = tokenize("*foo")
assert entry == [HySymbol("*foo")]
def test_lex_mangling_hyphen():
"""Ensure that hyphens get translated to underscores during mangling"""
entry = tokenize("foo-bar")
assert entry == [HySymbol("foo_bar")]
entry = tokenize("-")
assert entry == [HySymbol("-")]
def test_lex_mangling_qmark():
"""Ensure that identifiers ending with a question mark get mangled ok"""
entry = tokenize("foo?")
assert entry == [HySymbol("is_foo")]
entry = tokenize("?")
assert entry == [HySymbol("?")]
entry = tokenize("im?foo")
assert entry == [HySymbol("im?foo")]
entry = tokenize(".foo?")
assert entry == [HySymbol(".is_foo")]
entry = tokenize("foo.bar?")
assert entry == [HySymbol("foo.is_bar")]
entry = tokenize("foo?.bar")
assert entry == [HySymbol("is_foo.bar")]
entry = tokenize(".foo?.bar.baz?")
assert entry == [HySymbol(".is_foo.bar.is_baz")]
def test_lex_mangling_bang():
"""Ensure that identifiers ending with a bang get mangled ok"""
entry = tokenize("foo!")
assert entry == [HySymbol("foo_bang")]
entry = tokenize("!")
assert entry == [HySymbol("!")]
entry = tokenize("im!foo")
assert entry == [HySymbol("im!foo")]
entry = tokenize(".foo!")
assert entry == [HySymbol(".foo_bang")]
entry = tokenize("foo.bar!")
assert entry == [HySymbol("foo.bar_bang")]
entry = tokenize("foo!.bar")
assert entry == [HySymbol("foo_bang.bar")]
entry = tokenize(".foo!.bar.baz!")
assert entry == [HySymbol(".foo_bang.bar.baz_bang")]
def test_unmangle():
import sys
f = sys.modules["hy.lex.parser"].hy_symbol_unmangle
assert f("FOO") == "*foo*"
assert f("<") == "<"
assert f("FOOa") == "FOOa"
assert f("foo_bar") == "foo-bar"
assert f("_") == "_"
assert f("is_foo") == "foo?"
assert f("is_") == "is-"
assert f("foo_bang") == "foo!"
assert f("_bang") == "-bang"
def test_simple_cons(): def test_simple_cons():
"""Check that cons gets tokenized correctly""" """Check that cons gets tokenized correctly"""
entry = tokenize("(a . b)")[0] entry = tokenize("(a . b)")[0]