diff --git a/AUTHORS b/AUTHORS index 8217bf0..9592efb 100644 --- a/AUTHORS +++ b/AUTHORS @@ -72,3 +72,4 @@ * Jakub Wilk * Kodi Arfer * Karan Sharma +* Sergey Sobko diff --git a/docs/language/core.rst b/docs/language/core.rst index 452d0d5..288d72a 100644 --- a/docs/language/core.rst +++ b/docs/language/core.rst @@ -1261,3 +1261,22 @@ Returns an iterator from *coll* as long as *pred* returns ``True``. => (list (take-while neg? [ 1 2 3 -4 5])) [] + +Other Built-Ins +=============== + +hy.core.reserved +---------------- + +Usage: ``(hy.core.reserved.names)`` + +This module can be used to get a list (actually, a ``frozenset``) of the +names of Hy's built-in functions, macros, and special forms. The output +also includes all Python reserved words. All names are in unmangled form +(e.g., ``list-comp`` rather than ``list_comp``). + +.. code-block:: hy + + => (import hy) + => (in "defclass" (hy.core.reserved.names)) + True diff --git a/hy/core/__init__.py b/hy/core/__init__.py index be885ce..b46670e 100644 --- a/hy/core/__init__.py +++ b/hy/core/__init__.py @@ -1,3 +1,5 @@ +from . import reserved # noqa + STDLIB = [ "hy.core.language", "hy.core.shadow" diff --git a/hy/core/reserved.hy b/hy/core/reserved.hy new file mode 100644 index 0000000..ab828a6 --- /dev/null +++ b/hy/core/reserved.hy @@ -0,0 +1,41 @@ +;;; Get a frozenset of Hy reserved words +;; +;; Copyright (c) 2016 Paul Tagliamonte +;; +;; Permission is hereby granted, free of charge, to any person obtaining a +;; copy of this software and associated documentation files (the "Software"), +;; to deal in the Software without restriction, including without limitation +;; the rights to use, copy, modify, merge, publish, distribute, sublicense, +;; and/or sell copies of the Software, and to permit persons to whom the +;; Software is furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in +;; all copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +;; DEALINGS IN THE SOFTWARE. + +(import hy sys keyword) + +(setv _cache None) + +(defn names [] + "Return a frozenset of reserved symbol names. + + The result of the first call is cached." + (global _cache) + (if (is _cache None) (do + (setv unmangle (. sys.modules ["hy.lex.parser"] hy_symbol_unmangle)) + (setv _cache (frozenset (map unmangle (+ + hy.core.language.*exports* + hy.core.shadow.*exports* + (list (.keys (get hy.macros._hy_macros None))) + keyword.kwlist + (list-comp k [k (.keys hy.compiler.-compile-table)] + (isinstance k hy._compat.string-types)))))))) + _cache) diff --git a/hy/lex/parser.py b/hy/lex/parser.py index 118c8a4..c790d13 100644 --- a/hy/lex/parser.py +++ b/hy/lex/parser.py @@ -61,6 +61,30 @@ def hy_symbol_mangle(p): return p +def hy_symbol_unmangle(p): + # hy_symbol_mangle is one-way, so this can't be perfect. + # But it can be useful till we have a way to get the original + # symbol (https://github.com/hylang/hy/issues/360). + + from hy._compat import str_type + p = str_type(p) + + if p.endswith("_bang") and p != "_bang": + p = p[:-len("_bang")] + "!" + + if p.startswith("is_") and p != "is_": + p = p[len("is_"):] + "?" + + if "_" in p and p != "_": + p = p.replace("_", "-") + + if (all([c.isalpha() and c.isupper() or c == '_' for c in p]) and + any([c.isalpha() for c in p])): + p = '*' + p.lower() + '*' + + return p + + def set_boundaries(fun): @wraps(fun) def wrapped(p): diff --git a/tests/lex/test_lex.py b/tests/lex/test_lex.py index 81407cc..feba9c3 100644 --- a/tests/lex/test_lex.py +++ b/tests/lex/test_lex.py @@ -360,6 +360,24 @@ def test_lex_mangling_bang(): 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(): """Check that cons gets tokenized correctly""" entry = tokenize("(a . b)")[0] diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy index 13e5b07..f9a540a 100644 --- a/tests/native_tests/core.hy +++ b/tests/native_tests/core.hy @@ -612,3 +612,17 @@ [1 3 6 10 15]) (assert-equal (list (accumulate [1 -2 -3 -4 -5] -)) [1 3 6 10 15])) + +(defn test-reserved [] + (import [hy.core.reserved [names]]) + (assert (is (type (names)) frozenset)) + (assert (in "and" (names))) + (when PY3 + (assert (in "False" (names)))) + (assert (in "pass" (names))) + (assert (in "class" (names))) + (assert (in "defclass" (names))) + (assert (in "->" (names))) + (assert (in "keyword?" (names))) + (assert (not-in "foo" (names))) + (assert (not-in "hy" (names))))