Merge branch 'hy-repr'
This commit is contained in:
commit
741b9b9bd6
4
NEWS
4
NEWS
@ -22,6 +22,10 @@ Changes from 0.12.1
|
|||||||
returns.
|
returns.
|
||||||
* `setv` no longer unnecessarily tries to get attributes
|
* `setv` no longer unnecessarily tries to get attributes
|
||||||
|
|
||||||
|
[ Misc. Improvements ]
|
||||||
|
* New contrib module `hy-repr`
|
||||||
|
* Added a command-line option --hy-repr
|
||||||
|
|
||||||
Changes from 0.12.0
|
Changes from 0.12.0
|
||||||
|
|
||||||
[ Bug Fixes ]
|
[ Bug Fixes ]
|
||||||
|
48
docs/contrib/hy_repr.rst
Normal file
48
docs/contrib/hy_repr.rst
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
==================
|
||||||
|
Hy representations
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. versionadded:: 0.13.0
|
||||||
|
|
||||||
|
``hy.contrib.hy-repr`` is a module containing a single function.
|
||||||
|
To import it, say::
|
||||||
|
|
||||||
|
(import [hy.contrib.hy-repr [hy-repr]])
|
||||||
|
|
||||||
|
To make the Hy REPL use it for output, invoke Hy like so::
|
||||||
|
|
||||||
|
$ hy --repl-output-fn=hy.contrib.hy-repr.hy-repr
|
||||||
|
|
||||||
|
.. _hy-repr-fn:
|
||||||
|
|
||||||
|
hy-repr
|
||||||
|
-------
|
||||||
|
|
||||||
|
Usage: ``(hy-repr x)``
|
||||||
|
|
||||||
|
This function is Hy's equivalent of Python's built-in ``repr``.
|
||||||
|
It returns a string representing the input object in Hy syntax.
|
||||||
|
|
||||||
|
.. code-block:: hy
|
||||||
|
|
||||||
|
=> (hy-repr [1 2 3])
|
||||||
|
'[1 2 3]'
|
||||||
|
=> (repr [1 2 3])
|
||||||
|
'[1, 2, 3]'
|
||||||
|
|
||||||
|
If the input object has a method ``__hy-repr__``, it will be called
|
||||||
|
instead of doing anything else.
|
||||||
|
|
||||||
|
.. code-block:: hy
|
||||||
|
|
||||||
|
=> (defclass C [list] [__hy-repr__ (fn [self] "cuddles")])
|
||||||
|
=> (hy-repr (C))
|
||||||
|
'cuddles'
|
||||||
|
|
||||||
|
When ``hy-repr`` doesn't know how to handle its input, it falls back
|
||||||
|
on ``repr``.
|
||||||
|
|
||||||
|
Like ``repr`` in Python, ``hy-repr`` can round-trip many kinds of
|
||||||
|
values. Round-tripping implies that given an object ``x``,
|
||||||
|
``(eval (read-str (hy-repr x)))`` returns ``x``, or at least a value
|
||||||
|
that's equal to ``x``.
|
@ -16,3 +16,4 @@ Contents:
|
|||||||
profile
|
profile
|
||||||
sequences
|
sequences
|
||||||
walk
|
walk
|
||||||
|
hy_repr
|
||||||
|
@ -193,7 +193,6 @@ Hy. Let's experiment with this in the hy interpreter::
|
|||||||
[1, 2, 3]
|
[1, 2, 3]
|
||||||
=> {"dog" "bark"
|
=> {"dog" "bark"
|
||||||
... "cat" "meow"}
|
... "cat" "meow"}
|
||||||
...
|
|
||||||
{'dog': 'bark', 'cat': 'meow'}
|
{'dog': 'bark', 'cat': 'meow'}
|
||||||
=> (, 1 2 3)
|
=> (, 1 2 3)
|
||||||
(1, 2, 3)
|
(1, 2, 3)
|
||||||
@ -204,6 +203,20 @@ Hy. Let's experiment with this in the hy interpreter::
|
|||||||
|
|
||||||
Notice the last two lines: Hy has a fraction literal like Clojure.
|
Notice the last two lines: Hy has a fraction literal like Clojure.
|
||||||
|
|
||||||
|
If you start Hy like this (a shell alias might be helpful)::
|
||||||
|
|
||||||
|
$ hy --repl-output-fn=hy.contrib.hy-repr.hy-repr
|
||||||
|
|
||||||
|
the interactive mode will use :ref:`_hy-repr-fn` instead of Python's
|
||||||
|
native ``repr`` function to print out values, so you'll see values in
|
||||||
|
Hy syntax rather than Python syntax::
|
||||||
|
|
||||||
|
=> [1 2 3]
|
||||||
|
[1 2 3]
|
||||||
|
=> {"dog" "bark"
|
||||||
|
... "cat" "meow"}
|
||||||
|
{"dog" "bark" "cat" "meow"}
|
||||||
|
|
||||||
If you are familiar with other Lisps, you may be interested that Hy
|
If you are familiar with other Lisps, you may be interested that Hy
|
||||||
supports the Common Lisp method of quoting:
|
supports the Common Lisp method of quoting:
|
||||||
|
|
||||||
|
@ -32,14 +32,16 @@ import code
|
|||||||
import ast
|
import ast
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import importlib
|
||||||
|
|
||||||
import astor.codegen
|
import astor.codegen
|
||||||
|
|
||||||
import hy
|
import hy
|
||||||
|
|
||||||
from hy.lex import LexException, PrematureEndOfInput, tokenize
|
from hy.lex import LexException, PrematureEndOfInput, tokenize
|
||||||
from hy.compiler import hy_compile, HyTypeError
|
from hy.lex.parser import hy_symbol_mangle
|
||||||
from hy.importer import (ast_compile, import_buffer_to_module,
|
from hy.compiler import HyTypeError
|
||||||
|
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,
|
||||||
import_buffer_to_ast, import_buffer_to_hst)
|
import_buffer_to_ast, import_buffer_to_hst)
|
||||||
from hy.completer import completion
|
from hy.completer import completion
|
||||||
@ -73,25 +75,37 @@ builtins.quit = HyQuitter('quit')
|
|||||||
builtins.exit = HyQuitter('exit')
|
builtins.exit = HyQuitter('exit')
|
||||||
|
|
||||||
|
|
||||||
def print_python_code(_ast):
|
|
||||||
# astor cannot handle ast.Interactive, so disguise it as a module
|
|
||||||
_ast_for_print = ast.Module()
|
|
||||||
_ast_for_print.body = _ast.body
|
|
||||||
print(astor.codegen.to_source(_ast_for_print))
|
|
||||||
|
|
||||||
|
|
||||||
class HyREPL(code.InteractiveConsole):
|
class HyREPL(code.InteractiveConsole):
|
||||||
def __init__(self, spy=False, locals=None, filename="<input>"):
|
def __init__(self, spy=False, output_fn=None, locals=None,
|
||||||
|
filename="<input>"):
|
||||||
|
|
||||||
self.spy = spy
|
self.spy = spy
|
||||||
|
|
||||||
|
if output_fn is None:
|
||||||
|
self.output_fn = repr
|
||||||
|
elif callable(output_fn):
|
||||||
|
self.output_fn = output_fn
|
||||||
|
else:
|
||||||
|
f = hy_symbol_mangle(output_fn)
|
||||||
|
if "." in output_fn:
|
||||||
|
module, f = f.rsplit(".", 1)
|
||||||
|
self.output_fn = getattr(importlib.import_module(module), f)
|
||||||
|
else:
|
||||||
|
self.output_fn = __builtins__[f]
|
||||||
|
|
||||||
code.InteractiveConsole.__init__(self, locals=locals,
|
code.InteractiveConsole.__init__(self, locals=locals,
|
||||||
filename=filename)
|
filename=filename)
|
||||||
|
|
||||||
def runsource(self, source, filename='<input>', symbol='single'):
|
def runsource(self, source, filename='<input>', symbol='single'):
|
||||||
global SIMPLE_TRACEBACKS
|
global SIMPLE_TRACEBACKS
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
tokens = tokenize(source)
|
tokens = tokenize(source)
|
||||||
except PrematureEndOfInput:
|
except PrematureEndOfInput:
|
||||||
return True
|
return True
|
||||||
|
do = HyExpression([HySymbol('do')] + tokens)
|
||||||
|
do.start_line = do.end_line = do.start_column = do.end_column = 1
|
||||||
|
do.replace(do)
|
||||||
except LexException as e:
|
except LexException as e:
|
||||||
if e.source is None:
|
if e.source is None:
|
||||||
e.source = source
|
e.source = source
|
||||||
@ -100,10 +114,15 @@ class HyREPL(code.InteractiveConsole):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_ast = hy_compile(tokens, "__console__", root=ast.Interactive)
|
def ast_callback(main_ast, expr_ast):
|
||||||
if self.spy:
|
if self.spy:
|
||||||
print_python_code(_ast)
|
# Mush the two AST chunks into a single module for
|
||||||
code = ast_compile(_ast, filename, symbol)
|
# conversion into Python.
|
||||||
|
new_ast = ast.Module(main_ast.body +
|
||||||
|
[ast.Expr(expr_ast.body)])
|
||||||
|
print(astor.to_source(new_ast))
|
||||||
|
value = hy_eval(do, self.locals, "__console__",
|
||||||
|
ast_callback)
|
||||||
except HyTypeError as e:
|
except HyTypeError as e:
|
||||||
if e.source is None:
|
if e.source is None:
|
||||||
e.source = source
|
e.source = source
|
||||||
@ -117,7 +136,12 @@ class HyREPL(code.InteractiveConsole):
|
|||||||
self.showtraceback()
|
self.showtraceback()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.runcode(code)
|
if value is not None:
|
||||||
|
# Make the last non-None value available to
|
||||||
|
# the user as `_`.
|
||||||
|
self.locals['_'] = value
|
||||||
|
# Print the value.
|
||||||
|
print(self.output_fn(value))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -211,7 +235,7 @@ def run_file(filename):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def run_repl(hr=None, spy=False):
|
def run_repl(hr=None, **kwargs):
|
||||||
import platform
|
import platform
|
||||||
sys.ps1 = "=> "
|
sys.ps1 = "=> "
|
||||||
sys.ps2 = "... "
|
sys.ps2 = "... "
|
||||||
@ -221,7 +245,7 @@ def run_repl(hr=None, spy=False):
|
|||||||
with completion(Completer(namespace)):
|
with completion(Completer(namespace)):
|
||||||
|
|
||||||
if not hr:
|
if not hr:
|
||||||
hr = HyREPL(spy, namespace)
|
hr = HyREPL(locals=namespace, **kwargs)
|
||||||
|
|
||||||
hr.interact("{appname} {version} using "
|
hr.interact("{appname} {version} using "
|
||||||
"{py}({build}) {pyversion} on {os}".format(
|
"{py}({build}) {pyversion} on {os}".format(
|
||||||
@ -236,8 +260,8 @@ def run_repl(hr=None, spy=False):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def run_icommand(source, spy=False):
|
def run_icommand(source, **kwargs):
|
||||||
hr = HyREPL(spy)
|
hr = HyREPL(**kwargs)
|
||||||
if os.path.exists(source):
|
if os.path.exists(source):
|
||||||
with open(source, "r") as f:
|
with open(source, "r") as f:
|
||||||
source = f.read()
|
source = f.read()
|
||||||
@ -272,7 +296,9 @@ def cmdline_handler(scriptname, argv):
|
|||||||
help="program passed in as a string, then stay in REPL")
|
help="program passed in as a string, then stay in REPL")
|
||||||
parser.add_argument("--spy", action="store_true",
|
parser.add_argument("--spy", action="store_true",
|
||||||
help="print equivalent Python code before executing")
|
help="print equivalent Python code before executing")
|
||||||
|
parser.add_argument("--repl-output-fn",
|
||||||
|
help="function for printing REPL output "
|
||||||
|
"(e.g., hy.contrib.hy-repr.hy-repr)")
|
||||||
parser.add_argument("-v", "--version", action="version", version=VERSION)
|
parser.add_argument("-v", "--version", action="version", version=VERSION)
|
||||||
|
|
||||||
parser.add_argument("--show-tracebacks", action="store_true",
|
parser.add_argument("--show-tracebacks", action="store_true",
|
||||||
@ -315,7 +341,8 @@ def cmdline_handler(scriptname, argv):
|
|||||||
|
|
||||||
if options.icommand:
|
if options.icommand:
|
||||||
# User did "hy -i ..."
|
# User did "hy -i ..."
|
||||||
return run_icommand(options.icommand, spy=options.spy)
|
return run_icommand(options.icommand, spy=options.spy,
|
||||||
|
output_fn=options.repl_output_fn)
|
||||||
|
|
||||||
if options.args:
|
if options.args:
|
||||||
if options.args[0] == "-":
|
if options.args[0] == "-":
|
||||||
@ -332,7 +359,7 @@ def cmdline_handler(scriptname, argv):
|
|||||||
sys.exit(e.errno)
|
sys.exit(e.errno)
|
||||||
|
|
||||||
# User did NOTHING!
|
# User did NOTHING!
|
||||||
return run_repl(spy=options.spy)
|
return run_repl(spy=options.spy, output_fn=options.repl_output_fn)
|
||||||
|
|
||||||
|
|
||||||
# entry point for cmd line script "hy"
|
# entry point for cmd line script "hy"
|
||||||
|
77
hy/contrib/hy_repr.hy
Normal file
77
hy/contrib/hy_repr.hy
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
(import [hy._compat [PY3 str-type bytes-type long-type]])
|
||||||
|
(import [hy.models [HyObject HyExpression HySymbol HyKeyword HyInteger HyList HyDict HySet HyString HyBytes]])
|
||||||
|
|
||||||
|
(defn hy-repr [obj]
|
||||||
|
(setv seen (set))
|
||||||
|
; We keep track of objects we've already seen, and avoid
|
||||||
|
; redisplaying their contents, so a self-referential object
|
||||||
|
; doesn't send us into an infinite loop.
|
||||||
|
(defn f [x q]
|
||||||
|
; `x` is the current object being stringified.
|
||||||
|
; `q` is True if we're inside a single quote, False otherwise.
|
||||||
|
(setv old? (in (id x) seen))
|
||||||
|
(.add seen (id x))
|
||||||
|
(setv t (type x))
|
||||||
|
(defn catted []
|
||||||
|
(if old? "..." (.join " " (list-comp (f it q) [it x]))))
|
||||||
|
(setv prefix "")
|
||||||
|
(if (and (not q) (instance? HyObject x))
|
||||||
|
(setv prefix "'" q True))
|
||||||
|
(+ prefix (if
|
||||||
|
(hasattr x "__hy_repr__")
|
||||||
|
(.__hy-repr__ x)
|
||||||
|
(is t HyExpression)
|
||||||
|
(if (and x (symbol? (first x)))
|
||||||
|
(if
|
||||||
|
(= (first x) 'quote)
|
||||||
|
(+ "'" (f (second x) True))
|
||||||
|
(= (first x) 'quasiquote)
|
||||||
|
(+ "`" (f (second x) q))
|
||||||
|
(= (first x) 'unquote)
|
||||||
|
(+ "~" (f (second x) q))
|
||||||
|
(= (first x) 'unquote_splice)
|
||||||
|
(+ "~@" (f (second x) q))
|
||||||
|
; else
|
||||||
|
(+ "(" (catted) ")"))
|
||||||
|
(+ "(" (catted) ")"))
|
||||||
|
(is t tuple)
|
||||||
|
(+ "(," (if x " " "") (catted) ")")
|
||||||
|
(in t [list HyList])
|
||||||
|
(+ "[" (catted) "]")
|
||||||
|
(is t HyDict)
|
||||||
|
(+ "{" (catted) "}")
|
||||||
|
(is t dict)
|
||||||
|
(+
|
||||||
|
"{"
|
||||||
|
(if old? "..." (.join " " (list-comp
|
||||||
|
(+ (f k q) " " (f v q))
|
||||||
|
[[k v] (.items x)])))
|
||||||
|
"}")
|
||||||
|
(in t [set HySet])
|
||||||
|
(+ "#{" (catted) "}")
|
||||||
|
(is t frozenset)
|
||||||
|
(+ "(frozenset #{" (catted) "})")
|
||||||
|
(is t HySymbol)
|
||||||
|
x
|
||||||
|
(or (is t HyKeyword) (and (is t str-type) (.startswith x HyKeyword.PREFIX)))
|
||||||
|
(cut x 1)
|
||||||
|
(in t [str-type HyString bytes-type HyBytes]) (do
|
||||||
|
(setv r (.lstrip (repr x) "ub"))
|
||||||
|
(+ (if (in t [bytes-type HyBytes]) "b" "") (if (.startswith "\"" r)
|
||||||
|
; If Python's built-in repr produced a double-quoted string, use
|
||||||
|
; that.
|
||||||
|
r
|
||||||
|
; Otherwise, we have a single-quoted string, which isn't valid Hy, so
|
||||||
|
; convert it.
|
||||||
|
(+ "\"" (.replace (cut r 1 -1) "\"" "\\\"") "\""))))
|
||||||
|
(and (not PY3) (is t int))
|
||||||
|
(.format "(int {})" (repr x))
|
||||||
|
(and (not PY3) (in t [long_type HyInteger]))
|
||||||
|
(.rstrip (repr x) "L")
|
||||||
|
(is t complex)
|
||||||
|
(.strip (repr x) "()")
|
||||||
|
(is t fraction)
|
||||||
|
(.format "{}/{}" (f x.numerator q) (f x.denominator q))
|
||||||
|
; else
|
||||||
|
(repr x))))
|
||||||
|
(f obj False))
|
@ -111,7 +111,7 @@ def import_buffer_to_module(module_name, buf):
|
|||||||
return mod
|
return mod
|
||||||
|
|
||||||
|
|
||||||
def hy_eval(hytree, namespace, module_name):
|
def hy_eval(hytree, namespace, module_name, ast_callback=None):
|
||||||
foo = HyObject()
|
foo = HyObject()
|
||||||
foo.start_line = 0
|
foo.start_line = 0
|
||||||
foo.end_line = 0
|
foo.end_line = 0
|
||||||
@ -133,6 +133,9 @@ def hy_eval(hytree, namespace, module_name):
|
|||||||
node.lineno = 1
|
node.lineno = 1
|
||||||
node.col_offset = 1
|
node.col_offset = 1
|
||||||
|
|
||||||
|
if ast_callback:
|
||||||
|
ast_callback(_ast, expr)
|
||||||
|
|
||||||
if not isinstance(namespace, dict):
|
if not isinstance(namespace, dict):
|
||||||
raise HyTypeError(foo, "Globals must be a dictionary")
|
raise HyTypeError(foo, "Globals must be a dictionary")
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ from .native_tests.contrib.loop import * # noqa
|
|||||||
from .native_tests.contrib.walk import * # noqa
|
from .native_tests.contrib.walk import * # noqa
|
||||||
from .native_tests.contrib.multi import * # noqa
|
from .native_tests.contrib.multi import * # noqa
|
||||||
from .native_tests.contrib.sequences import * # noqa
|
from .native_tests.contrib.sequences import * # noqa
|
||||||
|
from .native_tests.contrib.hy_repr import * # noqa
|
||||||
|
|
||||||
if PY3:
|
if PY3:
|
||||||
from .native_tests.py3_only_tests import * # noqa
|
from .native_tests.py3_only_tests import * # noqa
|
||||||
|
82
tests/native_tests/contrib/hy_repr.hy
Normal file
82
tests/native_tests/contrib/hy_repr.hy
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
(import
|
||||||
|
[hy.contrib.hy-repr [hy-repr]])
|
||||||
|
|
||||||
|
(defn test-hy-repr-roundtrip-from-value []
|
||||||
|
; Test that a variety of values round-trip properly.
|
||||||
|
(setv values [
|
||||||
|
None False True
|
||||||
|
5 5.1 '5 '5.1
|
||||||
|
(int 5)
|
||||||
|
1/2
|
||||||
|
5j 5.1j 2+1j 1.2+3.4j
|
||||||
|
"" b""
|
||||||
|
'"" 'b""
|
||||||
|
"apple bloom" b"apple bloom" "⚘"
|
||||||
|
'"apple bloom" 'b"apple bloom" '"⚘"
|
||||||
|
"single ' quotes" b"single ' quotes"
|
||||||
|
"\"double \" quotes\"" b"\"double \" quotes\""
|
||||||
|
'mysymbol :mykeyword
|
||||||
|
[] (,) #{} (frozenset #{})
|
||||||
|
'[] '(,) '#{} '(frozenset #{})
|
||||||
|
'['[]]
|
||||||
|
'(+ 1 2)
|
||||||
|
[1 2 3] (, 1 2 3) #{1 2 3} (frozenset #{1 2 3})
|
||||||
|
'[1 2 3] '(, 1 2 3) '#{1 2 3} '(frozenset #{1 2 3})
|
||||||
|
{"a" 1 "b" 2 "a" 3} '{"a" 1 "b" 2 "a" 3}
|
||||||
|
[1 [2 3] (, 4 (, 'mysymbol :mykeyword)) {"a" b"hello"}]
|
||||||
|
'[1 [2 3] (, 4 (, mysymbol :mykeyword)) {"a" b"hello"}]])
|
||||||
|
(for [original-val values]
|
||||||
|
(setv evaled (eval (read-str (hy-repr original-val))))
|
||||||
|
(assert (= evaled original-val))
|
||||||
|
(assert (is (type evaled) (type original-val)))))
|
||||||
|
|
||||||
|
(defn test-hy-repr-roundtrip-from-str []
|
||||||
|
(setv strs [
|
||||||
|
"[1 2 3]"
|
||||||
|
"'[1 2 3]"
|
||||||
|
"[1 'a 3]"
|
||||||
|
"'[1 a 3]"
|
||||||
|
"'[1 'a 3]"
|
||||||
|
"[1 '[2 3] 4]"
|
||||||
|
"'[1 [2 3] 4]"
|
||||||
|
"'[1 '[2 3] 4]"
|
||||||
|
"'[1 `[2 3] 4]"
|
||||||
|
"'[1 `[~foo ~@bar] 4]"
|
||||||
|
"'[1 `[~(+ 1 2) ~@(+ [1] [2])] 4]"
|
||||||
|
"'[1 `[~(do (print x 'y) 1)] 4]"
|
||||||
|
"{1 20}"
|
||||||
|
"'{1 10 1 20}"
|
||||||
|
"'asymbol"
|
||||||
|
":akeyword"])
|
||||||
|
(for [original-str strs]
|
||||||
|
(setv rep (hy-repr (eval (read-str original-str))))
|
||||||
|
(assert (= rep original-str))))
|
||||||
|
|
||||||
|
(defn test-hy-model-constructors []
|
||||||
|
(import hy)
|
||||||
|
(assert (= (hy-repr (hy.HyInteger 7)) "'7"))
|
||||||
|
(assert (= (hy-repr (hy.HyString "hello")) "'\"hello\""))
|
||||||
|
(assert (= (hy-repr (hy.HyList [1 2 3])) "'[1 2 3]"))
|
||||||
|
(assert (= (hy-repr (hy.HyDict [1 2 3])) "'{1 2 3}")))
|
||||||
|
|
||||||
|
(defn test-hy-repr-self-reference []
|
||||||
|
|
||||||
|
(setv x [1 2 3])
|
||||||
|
(setv (get x 1) x)
|
||||||
|
(assert (= (hy-repr x) "[1 [...] 3]"))
|
||||||
|
|
||||||
|
(setv x {1 2 3 [4 5] 6 7})
|
||||||
|
(setv (get x 3 1) x)
|
||||||
|
(assert (in (hy-repr x) (list-comp
|
||||||
|
; The ordering of a dictionary isn't guaranteed, so we need
|
||||||
|
; to check for all possible orderings.
|
||||||
|
(+ "{" (.join " " p) "}")
|
||||||
|
[p (permutations ["1 2" "3 [4 {...}]" "6 7"])]))))
|
||||||
|
|
||||||
|
(defn test-hy-repr-dunder-method []
|
||||||
|
(defclass C [list] [__hy-repr__ (fn [self] "cuddles")])
|
||||||
|
(assert (= (hy-repr (C)) "cuddles")))
|
||||||
|
|
||||||
|
(defn test-hy-repr-fallback []
|
||||||
|
(defclass D [list] [__repr__ (fn [self] "cuddles")])
|
||||||
|
(assert (= (hy-repr (D)) "cuddles")))
|
@ -23,116 +23,164 @@
|
|||||||
# DEALINGS IN THE SOFTWARE.
|
# DEALINGS IN THE SOFTWARE.
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import re
|
||||||
from hy._compat import PY3
|
from hy._compat import PY3
|
||||||
|
|
||||||
|
|
||||||
hy_dir = os.environ.get('HY_DIR', '')
|
hy_dir = os.environ.get('HY_DIR', '')
|
||||||
|
|
||||||
|
|
||||||
def run_cmd(cmd, stdin_data=None):
|
def hr(s=""):
|
||||||
|
return "hy --repl-output-fn=hy.contrib.hy-repr.hy-repr " + s
|
||||||
|
|
||||||
|
|
||||||
|
def run_cmd(cmd, stdin_data=None, expect=0):
|
||||||
p = subprocess.Popen(os.path.join(hy_dir, cmd),
|
p = subprocess.Popen(os.path.join(hy_dir, cmd),
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
shell=True)
|
shell=True)
|
||||||
stdout = ""
|
|
||||||
stderr = ""
|
|
||||||
if stdin_data is not None:
|
if stdin_data is not None:
|
||||||
p.stdin.write(stdin_data.encode('ASCII'))
|
p.stdin.write(stdin_data.encode('ASCII'))
|
||||||
p.stdin.flush()
|
p.stdin.flush()
|
||||||
p.stdin.close()
|
p.stdin.close()
|
||||||
# Read stdout and stderr otherwise if the PIPE buffer is full, we might
|
# Read stdout and stderr otherwise if the PIPE buffer is full, we might
|
||||||
# wait for ever…
|
# wait for ever…
|
||||||
|
stdout = ""
|
||||||
|
stderr = ""
|
||||||
while p.poll() is None:
|
while p.poll() is None:
|
||||||
stdout += p.stdout.read().decode('utf-8')
|
stdout += p.stdout.read().decode('utf-8')
|
||||||
stderr += p.stderr.read().decode('utf-8')
|
stderr += p.stderr.read().decode('utf-8')
|
||||||
return p.returncode, stdout, stderr
|
assert p.returncode == expect
|
||||||
|
return stdout, stderr
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy():
|
def test_bin_hy():
|
||||||
ret = run_cmd("hy", "")
|
run_cmd("hy", "")
|
||||||
assert ret[0] == 0
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_stdin():
|
def test_bin_hy_stdin():
|
||||||
ret = run_cmd("hy", '(koan)')
|
output, _ = run_cmd("hy", '(koan)')
|
||||||
assert ret[0] == 0
|
assert "monk" in output
|
||||||
assert "monk" in ret[1]
|
|
||||||
|
output, _ = run_cmd("hy --spy", '(koan)')
|
||||||
|
assert "monk" in output
|
||||||
|
assert "\\n Ummon" in output
|
||||||
|
|
||||||
|
# --spy should work even when an exception is thrown
|
||||||
|
output, _ = run_cmd("hy --spy", '(foof)')
|
||||||
|
assert "foof()" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_bin_hy_stdin_multiline():
|
||||||
|
output, _ = run_cmd("hy", '(+ "a" "b"\n"c" "d")')
|
||||||
|
assert "'abcd'" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_bin_hy_stdin_comments():
|
||||||
|
_, err_empty = run_cmd("hy", '')
|
||||||
|
|
||||||
|
output, err = run_cmd("hy", '(+ "a" "b") ; "c"')
|
||||||
|
assert "'ab'" in output
|
||||||
|
assert err == err_empty
|
||||||
|
|
||||||
|
_, err = run_cmd("hy", '; 1')
|
||||||
|
assert err == err_empty
|
||||||
|
|
||||||
|
|
||||||
|
def test_bin_hy_stdin_assignment():
|
||||||
|
# If the last form is an assignment, don't print the value.
|
||||||
|
|
||||||
|
output, _ = run_cmd("hy", '(setv x (+ "A" "Z"))')
|
||||||
|
assert "AZ" not in output
|
||||||
|
|
||||||
|
output, _ = run_cmd("hy", '(setv x (+ "A" "Z")) (+ "B" "Y")')
|
||||||
|
assert "AZ" not in output
|
||||||
|
assert "BY" in output
|
||||||
|
|
||||||
|
output, _ = run_cmd("hy", '(+ "B" "Y") (setv x (+ "A" "Z"))')
|
||||||
|
assert "AZ" not in output
|
||||||
|
assert "BY" not in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_bin_hy_stdin_as_arrow():
|
||||||
|
# https://github.com/hylang/hy/issues/1255
|
||||||
|
output, _ = run_cmd("hy", "(as-> 0 it (inc it) (inc it))")
|
||||||
|
assert re.match(r"=>\s+2L?\s+=>", output)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bin_hy_stdin_error_underline_alignment():
|
||||||
|
_, err = run_cmd("hy", "(defmacro mabcdefghi [x] x)\n(mabcdefghi)")
|
||||||
|
assert "\n (mabcdefghi)\n ^----------^" in err
|
||||||
|
|
||||||
|
|
||||||
|
def test_bin_hy_stdin_hy_repr():
|
||||||
|
output, _ = run_cmd("hy", '(+ [1] [2])')
|
||||||
|
assert "[1, 2]" in output.replace('L', '')
|
||||||
|
|
||||||
|
output, _ = run_cmd(hr(), '(+ [1] [2])')
|
||||||
|
assert "[1 2]" in output
|
||||||
|
|
||||||
|
output, _ = run_cmd(hr("--spy"), '(+ [1] [2])')
|
||||||
|
assert "[1]+[2]" in output.replace('L', '').replace(' ', '')
|
||||||
|
assert "[1 2]" in output
|
||||||
|
|
||||||
|
# --spy should work even when an exception is thrown
|
||||||
|
output, _ = run_cmd(hr("--spy"), '(+ [1] [2] (foof))')
|
||||||
|
assert "[1]+[2]" in output.replace('L', '').replace(' ', '')
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_cmd():
|
def test_bin_hy_cmd():
|
||||||
ret = run_cmd("hy -c \"(koan)\"")
|
output, _ = run_cmd("hy -c \"(koan)\"")
|
||||||
assert ret[0] == 0
|
assert "monk" in output
|
||||||
assert "monk" in ret[1]
|
|
||||||
|
|
||||||
ret = run_cmd("hy -c \"(koan\"")
|
_, err = run_cmd("hy -c \"(koan\"", expect=1)
|
||||||
assert ret[0] == 1
|
assert "Premature end of input" in err
|
||||||
assert "Premature end of input" in ret[2]
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_icmd():
|
def test_bin_hy_icmd():
|
||||||
ret = run_cmd("hy -i \"(koan)\"", "(ideas)")
|
output, _ = run_cmd("hy -i \"(koan)\"", "(ideas)")
|
||||||
assert ret[0] == 0
|
|
||||||
output = ret[1]
|
|
||||||
|
|
||||||
assert "monk" in output
|
assert "monk" in output
|
||||||
assert "figlet" in output
|
assert "figlet" in output
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_icmd_file():
|
def test_bin_hy_icmd_file():
|
||||||
ret = run_cmd("hy -i resources/icmd_test_file.hy", "(ideas)")
|
output, _ = run_cmd("hy -i resources/icmd_test_file.hy", "(ideas)")
|
||||||
assert ret[0] == 0
|
|
||||||
output = ret[1]
|
|
||||||
|
|
||||||
assert "Hy!" in output
|
assert "Hy!" in output
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_icmd_and_spy():
|
def test_bin_hy_icmd_and_spy():
|
||||||
ret = run_cmd("hy -i \"(+ [] [])\" --spy", "(+ 1 1)")
|
output, _ = run_cmd("hy -i \"(+ [] [])\" --spy", "(+ 1 1)")
|
||||||
assert ret[0] == 0
|
|
||||||
output = ret[1]
|
|
||||||
|
|
||||||
assert "([] + [])" in output
|
assert "([] + [])" in output
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_missing_file():
|
def test_bin_hy_missing_file():
|
||||||
ret = run_cmd("hy foobarbaz")
|
_, err = run_cmd("hy foobarbaz", expect=2)
|
||||||
assert ret[0] == 2
|
assert "No such file" in err
|
||||||
assert "No such file" in ret[2]
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_file_with_args():
|
def test_bin_hy_file_with_args():
|
||||||
ret = run_cmd("hy tests/resources/argparse_ex.hy -h")
|
assert "usage" in run_cmd("hy tests/resources/argparse_ex.hy -h")[0]
|
||||||
assert ret[0] == 0
|
assert "got c" in run_cmd("hy tests/resources/argparse_ex.hy -c bar")[0]
|
||||||
assert "usage" in ret[1]
|
assert "foo" in run_cmd("hy tests/resources/argparse_ex.hy -i foo")[0]
|
||||||
ret = run_cmd("hy tests/resources/argparse_ex.hy -c bar")
|
assert "foo" in run_cmd("hy tests/resources/argparse_ex.hy -i foo -c bar")[0] # noqa
|
||||||
assert ret[0] == 0
|
|
||||||
assert "got c" in ret[1]
|
|
||||||
ret = run_cmd("hy tests/resources/argparse_ex.hy -i foo")
|
|
||||||
assert ret[0] == 0
|
|
||||||
assert "foo" in ret[1]
|
|
||||||
ret = run_cmd("hy tests/resources/argparse_ex.hy -i foo -c bar")
|
|
||||||
assert ret[0] == 0
|
|
||||||
assert "foo" in ret[1]
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hyc():
|
def test_bin_hyc():
|
||||||
ret = run_cmd("hyc")
|
_, err = run_cmd("hyc", expect=2)
|
||||||
assert ret[0] == 2
|
assert "usage" in err
|
||||||
assert "usage" in ret[2]
|
|
||||||
ret = run_cmd("hyc -h")
|
output, _ = run_cmd("hyc -h")
|
||||||
assert ret[0] == 0
|
assert "usage" in output
|
||||||
assert "usage" in ret[1]
|
|
||||||
ret = run_cmd("hyc tests/resources/argparse_ex.hy")
|
output, _ = run_cmd("hyc tests/resources/argparse_ex.hy")
|
||||||
assert ret[0] == 0
|
assert "Compiling" in output
|
||||||
assert "Compiling" in ret[1]
|
|
||||||
assert os.path.exists("tests/resources/argparse_ex.pyc")
|
assert os.path.exists("tests/resources/argparse_ex.pyc")
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hyc_missing_file():
|
def test_bin_hyc_missing_file():
|
||||||
ret = run_cmd("hyc foobarbaz")
|
_, err = run_cmd("hyc foobarbaz", expect=2)
|
||||||
assert ret[0] == 2
|
assert "[Errno 2]" in err
|
||||||
assert "[Errno 2]" in ret[2]
|
|
||||||
|
|
||||||
|
|
||||||
def test_hy2py():
|
def test_hy2py():
|
||||||
@ -144,10 +192,10 @@ def test_hy2py():
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
i += 1
|
i += 1
|
||||||
ret = run_cmd("hy2py -s -a " + os.path.join(dirpath, f))
|
output, err = run_cmd("hy2py -s -a " +
|
||||||
assert ret[0] == 0, f
|
os.path.join(dirpath, f))
|
||||||
assert len(ret[1]) > 1, f
|
assert len(output) > 1, f
|
||||||
assert len(ret[2]) == 0, f
|
assert len(err) == 0, f
|
||||||
assert i
|
assert i
|
||||||
|
|
||||||
|
|
||||||
@ -159,48 +207,40 @@ def test_bin_hy_builtins():
|
|||||||
|
|
||||||
|
|
||||||
def test_bin_hy_main():
|
def test_bin_hy_main():
|
||||||
ret = run_cmd("hy tests/resources/bin/main.hy")
|
output, _ = run_cmd("hy tests/resources/bin/main.hy")
|
||||||
assert ret[0] == 0
|
assert "Hello World" in output
|
||||||
assert "Hello World" in ret[1]
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_main_args():
|
def test_bin_hy_main_args():
|
||||||
ret = run_cmd("hy tests/resources/bin/main.hy test 123")
|
output, _ = run_cmd("hy tests/resources/bin/main.hy test 123")
|
||||||
assert ret[0] == 0
|
assert "test" in output
|
||||||
assert "test" in ret[1]
|
assert "123" in output
|
||||||
assert "123" in ret[1]
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_main_exitvalue():
|
def test_bin_hy_main_exitvalue():
|
||||||
ret = run_cmd("hy tests/resources/bin/main.hy exit1")
|
run_cmd("hy tests/resources/bin/main.hy exit1", expect=1)
|
||||||
assert ret[0] == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_no_main():
|
def test_bin_hy_no_main():
|
||||||
ret = run_cmd("hy tests/resources/bin/nomain.hy")
|
output, _ = run_cmd("hy tests/resources/bin/nomain.hy")
|
||||||
assert ret[0] == 0
|
assert "This Should Still Work" in output
|
||||||
assert "This Should Still Work" in ret[1]
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_module_main():
|
def test_bin_hy_module_main():
|
||||||
ret = run_cmd("hy -m tests.resources.bin.main")
|
output, _ = run_cmd("hy -m tests.resources.bin.main")
|
||||||
assert ret[0] == 0
|
assert "Hello World" in output
|
||||||
assert "Hello World" in ret[1]
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_module_main_args():
|
def test_bin_hy_module_main_args():
|
||||||
ret = run_cmd("hy -m tests.resources.bin.main test 123")
|
output, _ = run_cmd("hy -m tests.resources.bin.main test 123")
|
||||||
assert ret[0] == 0
|
assert "test" in output
|
||||||
assert "test" in ret[1]
|
assert "123" in output
|
||||||
assert "123" in ret[1]
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_module_main_exitvalue():
|
def test_bin_hy_module_main_exitvalue():
|
||||||
ret = run_cmd("hy -m tests.resources.bin.main exit1")
|
run_cmd("hy -m tests.resources.bin.main exit1", expect=1)
|
||||||
assert ret[0] == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_bin_hy_module_no_main():
|
def test_bin_hy_module_no_main():
|
||||||
ret = run_cmd("hy -m tests.resources.bin.nomain")
|
output, _ = run_cmd("hy -m tests.resources.bin.nomain")
|
||||||
assert ret[0] == 0
|
assert "This Should Still Work" in output
|
||||||
assert "This Should Still Work" in ret[1]
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user