Merge pull request #1543 from vodik/keywords-as-objects
Make HyKeyword a first class objects
This commit is contained in:
commit
096aac244f
2
NEWS.rst
2
NEWS.rst
@ -15,6 +15,8 @@ Other Breaking Changes
|
||||
instead of ignoring it. This change increases consistency a bit
|
||||
and makes accidental unary uses easier to notice.
|
||||
* `hy-repr` uses registered functions instead of methods
|
||||
* `HyKeyword` no longer inherits from the string type and has been
|
||||
made into its own object type.
|
||||
|
||||
New Features
|
||||
------------------------------
|
||||
|
@ -581,10 +581,10 @@ objects with the `__name__` magic will work.
|
||||
.. code-block:: hy
|
||||
|
||||
=> (keyword "foo")
|
||||
u'\ufdd0:foo'
|
||||
HyKeyword('foo')
|
||||
|
||||
=> (keyword 1)
|
||||
u'\ufdd0:1'
|
||||
HyKeyword('foo')
|
||||
|
||||
.. _keyword?-fn:
|
||||
|
||||
|
@ -72,9 +72,7 @@ of bytes. So when running under Python 3, Hy translates ``"foo"`` and
|
||||
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
|
||||
An identifier headed by a colon, such as ``:foo``, is a keyword. 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,
|
||||
|
@ -500,19 +500,18 @@ class HyASTCompiler(object):
|
||||
except StopIteration:
|
||||
raise HyTypeError(expr,
|
||||
"Keyword argument {kw} needs "
|
||||
"a value.".format(kw=str(expr[1:])))
|
||||
"a value.".format(kw=expr))
|
||||
|
||||
if not expr:
|
||||
raise HyTypeError(expr, "Can't call a function with the "
|
||||
"empty keyword")
|
||||
|
||||
compiled_value = self.compile(value)
|
||||
ret += compiled_value
|
||||
|
||||
keyword = expr[2:]
|
||||
if not keyword:
|
||||
raise HyTypeError(expr, "Can't call a function with the "
|
||||
"empty keyword")
|
||||
keyword = ast_str(keyword)
|
||||
|
||||
arg = str_type(expr)[1:]
|
||||
keywords.append(asty.keyword(
|
||||
expr, arg=keyword, value=compiled_value.force_expr))
|
||||
expr, arg=ast_str(arg), value=compiled_value.force_expr))
|
||||
|
||||
else:
|
||||
ret += self.compile(expr)
|
||||
@ -742,10 +741,13 @@ class HyASTCompiler(object):
|
||||
return imports, HyExpression([HySymbol(name),
|
||||
HyString(form)]).replace(form), False
|
||||
|
||||
elif isinstance(form, HyKeyword):
|
||||
return imports, form, False
|
||||
|
||||
elif isinstance(form, HyString):
|
||||
x = [HySymbol(name), form]
|
||||
if form.brackets is not None:
|
||||
x.extend([HyKeyword(":brackets"), form.brackets])
|
||||
x.extend([HyKeyword("brackets"), form.brackets])
|
||||
return imports, HyExpression(x).replace(form), False
|
||||
|
||||
return imports, HyExpression([HySymbol(name),
|
||||
@ -809,7 +811,7 @@ class HyASTCompiler(object):
|
||||
ret += self.compile(expr.pop(0))
|
||||
|
||||
cause = None
|
||||
if len(expr) == 2 and expr[0] == HyKeyword(":from"):
|
||||
if len(expr) == 2 and expr[0] == HyKeyword("from"):
|
||||
if not PY3:
|
||||
raise HyCompileError(
|
||||
"raise from only supported in python 3")
|
||||
@ -1168,7 +1170,7 @@ class HyASTCompiler(object):
|
||||
if isinstance(iexpr, HyList) and iexpr:
|
||||
module = iexpr.pop(0)
|
||||
entry = iexpr[0]
|
||||
if isinstance(entry, HyKeyword) and entry == HyKeyword(":as"):
|
||||
if entry == HyKeyword("as"):
|
||||
if not len(iexpr) == 2:
|
||||
raise HyTypeError(iexpr,
|
||||
"garbage after aliased import")
|
||||
@ -1462,7 +1464,7 @@ class HyASTCompiler(object):
|
||||
else:
|
||||
assignments = {}
|
||||
while names:
|
||||
if len(names) > 1 and names[1] == HyKeyword(":as"):
|
||||
if len(names) > 1 and names[1] == HyKeyword("as"):
|
||||
k, _, v = names[:3]
|
||||
del names[:3]
|
||||
assignments[k] = v
|
||||
@ -1471,7 +1473,7 @@ class HyASTCompiler(object):
|
||||
assignments[symbol] = symbol
|
||||
require(module, self.module_name, assignments=assignments)
|
||||
elif (isinstance(entry, HyList) and len(entry) == 3
|
||||
and entry[1] == HyKeyword(":as")):
|
||||
and entry[1] == HyKeyword("as")):
|
||||
# e.g., (require [foo :as bar])
|
||||
module, _, prefix = entry
|
||||
__import__(module)
|
||||
@ -1764,7 +1766,7 @@ class HyASTCompiler(object):
|
||||
# An exception for pulling together keyword args is if we're doing
|
||||
# a typecheck, eg (type :foo)
|
||||
with_kwargs = fn not in (
|
||||
"type", "HyKeyword", "keyword", "name", "keyword?")
|
||||
"type", "HyKeyword", "keyword", "name", "keyword?", "identity")
|
||||
args, ret, keywords, oldpy_star, oldpy_kw = self._compile_collect(
|
||||
expression[1:], with_kwargs, oldpy_unpack=True)
|
||||
|
||||
@ -2187,7 +2189,18 @@ class HyASTCompiler(object):
|
||||
|
||||
return asty.Name(symbol, id=ast_str(symbol), ctx=ast.Load())
|
||||
|
||||
@builds(HyString, HyKeyword, HyBytes)
|
||||
@builds(HyKeyword)
|
||||
def compile_keyword(self, obj):
|
||||
ret = Result()
|
||||
ret += asty.Call(
|
||||
obj,
|
||||
func=asty.Name(obj, id="HyKeyword", ctx=ast.Load()),
|
||||
args=[asty.Str(obj, s=obj.name)],
|
||||
keywords=[])
|
||||
ret.add_imports("hy", {"HyKeyword"})
|
||||
return ret
|
||||
|
||||
@builds(HyString, HyBytes)
|
||||
def compile_string(self, string, building):
|
||||
node = asty.Bytes if PY3 and building is HyBytes else asty.Str
|
||||
f = bytes_type if building is HyBytes else str_type
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
(global -quoting)
|
||||
(setv started-quoting False)
|
||||
(when (and (not -quoting) (instance? HyObject obj))
|
||||
(when (and (not -quoting) (instance? HyObject obj) (not (instance? HyKeyword obj)))
|
||||
(setv -quoting True)
|
||||
(setv started-quoting True))
|
||||
|
||||
@ -82,10 +82,8 @@
|
||||
(+ (get syntax (first x)) (hy-repr (second x)))
|
||||
(+ "(" (-cat x) ")"))))
|
||||
|
||||
(hy-repr-register HySymbol str)
|
||||
(hy-repr-register [str-type bytes-type HyKeyword] (fn [x]
|
||||
(if (and (instance? str-type x) (.startswith x HyKeyword.PREFIX))
|
||||
(return (cut x 1)))
|
||||
(hy-repr-register [HySymbol HyKeyword] str)
|
||||
(hy-repr-register [str-type bytes-type] (fn [x]
|
||||
(setv r (.lstrip (-base-repr x) "ub"))
|
||||
(+
|
||||
(if (instance? bytes-type x) "b" "")
|
||||
|
@ -65,8 +65,7 @@
|
||||
|
||||
(defn keyword? [k]
|
||||
"Check whether `k` is a keyword."
|
||||
(and (instance? (type :foo) k)
|
||||
(.startswith k (get :foo 0))))
|
||||
(instance? HyKeyword k))
|
||||
|
||||
(defn dec [n]
|
||||
"Decrement `n` by 1."
|
||||
@ -460,26 +459,26 @@ as EOF (defaults to an empty string)."
|
||||
"Create a keyword from `value`.
|
||||
|
||||
Strings numbers and even objects with the __name__ magic will work."
|
||||
(if (and (string? value) (value.startswith HyKeyword.PREFIX))
|
||||
(unmangle value)
|
||||
(if (string? value)
|
||||
(HyKeyword (+ ":" (unmangle value)))
|
||||
(try
|
||||
(unmangle (.__name__ value))
|
||||
(except [] (HyKeyword (+ ":" (string value))))))))
|
||||
(if (keyword? value)
|
||||
(HyKeyword (unmangle value.name))
|
||||
(if (string? value)
|
||||
(HyKeyword (unmangle value))
|
||||
(try
|
||||
(unmangle (.__name__ value))
|
||||
(except [] (HyKeyword (string value)))))))
|
||||
|
||||
(defn name [value]
|
||||
"Convert `value` to a string.
|
||||
|
||||
Keyword special character will be stripped. String will be used as is.
|
||||
Even objects with the __name__ magic will work."
|
||||
(if (and (string? value) (value.startswith HyKeyword.PREFIX))
|
||||
(unmangle (cut value 2))
|
||||
(if (string? value)
|
||||
(unmangle value)
|
||||
(try
|
||||
(unmangle (. value __name__))
|
||||
(except [] (string value))))))
|
||||
(if (keyword? value)
|
||||
(unmangle (cut (str value) 1))
|
||||
(if (string? value)
|
||||
(unmangle value)
|
||||
(try
|
||||
(unmangle (. value __name__))
|
||||
(except [] (string value))))))
|
||||
|
||||
(defn xor [a b]
|
||||
"Perform exclusive or between `a` and `b`."
|
||||
|
@ -380,7 +380,7 @@ def symbol_like(obj):
|
||||
pass
|
||||
|
||||
if obj.startswith(":") and "." not in obj:
|
||||
return HyKeyword(obj)
|
||||
return HyKeyword(obj[1:])
|
||||
|
||||
|
||||
@pg.error
|
||||
|
37
hy/models.py
37
hy/models.py
@ -121,22 +121,35 @@ _wrappers[bool] = lambda x: HySymbol("True") if x else HySymbol("False")
|
||||
_wrappers[type(None)] = lambda foo: HySymbol("None")
|
||||
|
||||
|
||||
class HyKeyword(HyObject, str_type):
|
||||
"""Generic Hy Keyword object. It's either a ``str`` or a ``unicode``,
|
||||
depending on the Python version.
|
||||
"""
|
||||
class HyKeyword(HyObject):
|
||||
"""Generic Hy Keyword object."""
|
||||
|
||||
PREFIX = "\uFDD0"
|
||||
__slots__ = ['name']
|
||||
|
||||
def __new__(cls, value):
|
||||
if not value.startswith(cls.PREFIX):
|
||||
value = cls.PREFIX + value
|
||||
|
||||
obj = str_type.__new__(cls, value)
|
||||
return obj
|
||||
def __init__(self, value):
|
||||
self.name = value
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%s)" % (self.__class__.__name__, repr(self[1:]))
|
||||
return "%s(%r)" % (self.__class__.__name__, self.name)
|
||||
|
||||
def __str__(self):
|
||||
return ":%s" % self.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, HyKeyword):
|
||||
return NotImplemented
|
||||
return self.name == other.name
|
||||
|
||||
def __ne__(self, other):
|
||||
if not isinstance(other, HyKeyword):
|
||||
return NotImplemented
|
||||
return self.name != other.name
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.name)
|
||||
|
||||
|
||||
def strip_digit_separators(number):
|
||||
|
@ -79,14 +79,6 @@
|
||||
(assert (is (type (get orig 1)) float))
|
||||
(assert (is (type (get result 1)) HyFloat)))
|
||||
|
||||
(when PY3 (defn test-bytes-keywords []
|
||||
; Make sure that keyword-like bytes objects aren't hy-repred as if
|
||||
; they were real keywords.
|
||||
(setv kw :mykeyword)
|
||||
(assert (= (hy-repr kw) ":mykeyword"))
|
||||
(assert (= (hy-repr (str ':mykeyword)) ":mykeyword"))
|
||||
(assert (= (hy-repr (.encode kw "UTF-8") #[[b"\xef\xb7\x90:hello"]])))))
|
||||
|
||||
(when PY3 (defn test-dict-views []
|
||||
(assert (= (hy-repr (.keys {1 2})) "(dict-keys [1])"))
|
||||
(assert (= (hy-repr (.values {1 2})) "(dict-values [2])"))
|
||||
|
@ -554,7 +554,7 @@
|
||||
(assert (= (.a.b.meth :b "1" :a "2" x "foo" "bar") "meth foo bar 2 1"))
|
||||
(assert (= (.a.b.meth x #* ["foo" "bar"]) "meth foo bar"))
|
||||
|
||||
(assert (is (.isdigit :foo) False)))
|
||||
(assert (= (.__str__ :foo) ":foo")))
|
||||
|
||||
|
||||
(defn test-do []
|
||||
@ -1221,9 +1221,12 @@
|
||||
"NATIVE: test if keywords are recognised"
|
||||
|
||||
(assert (= :foo :foo))
|
||||
(assert (= :foo ':foo))
|
||||
(assert (is (type :foo) (type ':foo)))
|
||||
(assert (= (get {:foo "bar"} :foo) "bar"))
|
||||
(assert (= (get {:bar "quux"} (get {:foo :bar} :foo)) "quux")))
|
||||
|
||||
|
||||
(defn test-keyword-clash []
|
||||
"NATIVE: test that keywords do not clash with normal strings"
|
||||
|
||||
@ -1649,11 +1652,6 @@ macros()
|
||||
(setv (. foo [1] test) "hello")
|
||||
(assert (= (getattr (. foo [1]) "test") "hello")))
|
||||
|
||||
(defn test-keyword-quoting []
|
||||
"NATIVE: test keyword quoting magic"
|
||||
(assert (= :foo "\ufdd0:foo"))
|
||||
(assert (= `:foo "\ufdd0:foo")))
|
||||
|
||||
(defn test-only-parse-lambda-list-in-defn []
|
||||
"NATIVE: test lambda lists are only parsed in defn"
|
||||
(try
|
||||
|
@ -434,9 +434,9 @@ def test_discard():
|
||||
assert tokenize("(#_foo)") == [HyExpression()]
|
||||
assert tokenize("(#_foo bar)") == [HyExpression([HySymbol("bar")])]
|
||||
assert tokenize("(foo #_bar)") == [HyExpression([HySymbol("foo")])]
|
||||
assert tokenize("(foo :bar 1)") == [HyExpression([HySymbol("foo"), HyKeyword(":bar"), HyInteger(1)])]
|
||||
assert tokenize("(foo :bar 1)") == [HyExpression([HySymbol("foo"), HyKeyword("bar"), HyInteger(1)])]
|
||||
assert tokenize("(foo #_:bar 1)") == [HyExpression([HySymbol("foo"), HyInteger(1)])]
|
||||
assert tokenize("(foo :bar #_1)") == [HyExpression([HySymbol("foo"), HyKeyword(":bar")])]
|
||||
assert tokenize("(foo :bar #_1)") == [HyExpression([HySymbol("foo"), HyKeyword("bar")])]
|
||||
# discard term with nesting
|
||||
assert tokenize("[1 2 #_[a b c [d e [f g] h]] 3 4]") == [
|
||||
HyList([HyInteger(1), HyInteger(2), HyInteger(3), HyInteger(4)])
|
||||
|
Loading…
Reference in New Issue
Block a user