From 32e76caafee4f8c44edfccea3a62e3ce5a74a73f Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 17 Mar 2017 09:31:54 -0700 Subject: [PATCH 1/5] Refactor test_bin --- tests/test_bin.py | 133 ++++++++++++++++++---------------------------- 1 file changed, 51 insertions(+), 82 deletions(-) diff --git a/tests/test_bin.py b/tests/test_bin.py index e66ef3f..218b030 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -29,110 +29,87 @@ from hy._compat import PY3 hy_dir = os.environ.get('HY_DIR', '') -def run_cmd(cmd, stdin_data=None): +def run_cmd(cmd, stdin_data=None, expect=0): p = subprocess.Popen(os.path.join(hy_dir, cmd), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - stdout = "" - stderr = "" if stdin_data is not None: p.stdin.write(stdin_data.encode('ASCII')) p.stdin.flush() p.stdin.close() # Read stdout and stderr otherwise if the PIPE buffer is full, we might # wait for ever… + stdout = "" + stderr = "" while p.poll() is None: stdout += p.stdout.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(): - ret = run_cmd("hy", "") - assert ret[0] == 0 + run_cmd("hy", "") def test_bin_hy_stdin(): - ret = run_cmd("hy", '(koan)') - assert ret[0] == 0 - assert "monk" in ret[1] + output, _ = run_cmd("hy", '(koan)') + assert "monk" in output def test_bin_hy_cmd(): - ret = run_cmd("hy -c \"(koan)\"") - assert ret[0] == 0 - assert "monk" in ret[1] + output, _ = run_cmd("hy -c \"(koan)\"") + assert "monk" in output - ret = run_cmd("hy -c \"(koan\"") - assert ret[0] == 1 - assert "Premature end of input" in ret[2] + _, err = run_cmd("hy -c \"(koan\"", expect=1) + assert "Premature end of input" in err def test_bin_hy_icmd(): - ret = run_cmd("hy -i \"(koan)\"", "(ideas)") - assert ret[0] == 0 - output = ret[1] - + output, _ = run_cmd("hy -i \"(koan)\"", "(ideas)") assert "monk" in output assert "figlet" in output def test_bin_hy_icmd_file(): - ret = run_cmd("hy -i resources/icmd_test_file.hy", "(ideas)") - assert ret[0] == 0 - output = ret[1] - + output, _ = run_cmd("hy -i resources/icmd_test_file.hy", "(ideas)") assert "Hy!" in output def test_bin_hy_icmd_and_spy(): - ret = run_cmd("hy -i \"(+ [] [])\" --spy", "(+ 1 1)") - assert ret[0] == 0 - output = ret[1] - + output, _ = run_cmd("hy -i \"(+ [] [])\" --spy", "(+ 1 1)") assert "([] + [])" in output def test_bin_hy_missing_file(): - ret = run_cmd("hy foobarbaz") - assert ret[0] == 2 - assert "No such file" in ret[2] + _, err = run_cmd("hy foobarbaz", expect=2) + assert "No such file" in err def test_bin_hy_file_with_args(): - ret = run_cmd("hy tests/resources/argparse_ex.hy -h") - assert ret[0] == 0 - assert "usage" in ret[1] - ret = run_cmd("hy tests/resources/argparse_ex.hy -c bar") - 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] + assert "usage" in run_cmd("hy tests/resources/argparse_ex.hy -h")[0] + assert "got c" in run_cmd("hy tests/resources/argparse_ex.hy -c bar")[0] + assert "foo" in run_cmd("hy tests/resources/argparse_ex.hy -i foo")[0] + assert "foo" in run_cmd("hy tests/resources/argparse_ex.hy -i foo -c bar")[0] # noqa def test_bin_hyc(): - ret = run_cmd("hyc") - assert ret[0] == 2 - assert "usage" in ret[2] - ret = run_cmd("hyc -h") - assert ret[0] == 0 - assert "usage" in ret[1] - ret = run_cmd("hyc tests/resources/argparse_ex.hy") - assert ret[0] == 0 - assert "Compiling" in ret[1] + _, err = run_cmd("hyc", expect=2) + assert "usage" in err + + output, _ = run_cmd("hyc -h") + assert "usage" in output + + output, _ = run_cmd("hyc tests/resources/argparse_ex.hy") + assert "Compiling" in output assert os.path.exists("tests/resources/argparse_ex.pyc") def test_bin_hyc_missing_file(): - ret = run_cmd("hyc foobarbaz") - assert ret[0] == 2 - assert "[Errno 2]" in ret[2] + _, err = run_cmd("hyc foobarbaz", expect=2) + assert "[Errno 2]" in err def test_hy2py(): @@ -144,10 +121,10 @@ def test_hy2py(): continue else: i += 1 - ret = run_cmd("hy2py -s -a " + os.path.join(dirpath, f)) - assert ret[0] == 0, f - assert len(ret[1]) > 1, f - assert len(ret[2]) == 0, f + output, err = run_cmd("hy2py -s -a " + + os.path.join(dirpath, f)) + assert len(output) > 1, f + assert len(err) == 0, f assert i @@ -159,48 +136,40 @@ def test_bin_hy_builtins(): def test_bin_hy_main(): - ret = run_cmd("hy tests/resources/bin/main.hy") - assert ret[0] == 0 - assert "Hello World" in ret[1] + output, _ = run_cmd("hy tests/resources/bin/main.hy") + assert "Hello World" in output def test_bin_hy_main_args(): - ret = run_cmd("hy tests/resources/bin/main.hy test 123") - assert ret[0] == 0 - assert "test" in ret[1] - assert "123" in ret[1] + output, _ = run_cmd("hy tests/resources/bin/main.hy test 123") + assert "test" in output + assert "123" in output def test_bin_hy_main_exitvalue(): - ret = run_cmd("hy tests/resources/bin/main.hy exit1") - assert ret[0] == 1 + run_cmd("hy tests/resources/bin/main.hy exit1", expect=1) def test_bin_hy_no_main(): - ret = run_cmd("hy tests/resources/bin/nomain.hy") - assert ret[0] == 0 - assert "This Should Still Work" in ret[1] + output, _ = run_cmd("hy tests/resources/bin/nomain.hy") + assert "This Should Still Work" in output def test_bin_hy_module_main(): - ret = run_cmd("hy -m tests.resources.bin.main") - assert ret[0] == 0 - assert "Hello World" in ret[1] + output, _ = run_cmd("hy -m tests.resources.bin.main") + assert "Hello World" in output def test_bin_hy_module_main_args(): - ret = run_cmd("hy -m tests.resources.bin.main test 123") - assert ret[0] == 0 - assert "test" in ret[1] - assert "123" in ret[1] + output, _ = run_cmd("hy -m tests.resources.bin.main test 123") + assert "test" in output + assert "123" in output def test_bin_hy_module_main_exitvalue(): - ret = run_cmd("hy -m tests.resources.bin.main exit1") - assert ret[0] == 1 + run_cmd("hy -m tests.resources.bin.main exit1", expect=1) def test_bin_hy_module_no_main(): - ret = run_cmd("hy -m tests.resources.bin.nomain") - assert ret[0] == 0 - assert "This Should Still Work" in ret[1] + output, _ = run_cmd("hy -m tests.resources.bin.nomain") + assert "This Should Still Work" in output From bf2f90a0d9d4d4ec53424d306a410653d01e7c86 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Mon, 20 Mar 2017 12:04:00 -0700 Subject: [PATCH 2/5] Add hy.contrib.hy-repr --- NEWS | 4 ++ docs/contrib/hy_repr.rst | 42 ++++++++++++++ docs/contrib/index.rst | 1 + hy/contrib/hy_repr.hy | 77 +++++++++++++++++++++++++ tests/__init__.py | 1 + tests/native_tests/contrib/hy_repr.hy | 82 +++++++++++++++++++++++++++ 6 files changed, 207 insertions(+) create mode 100644 docs/contrib/hy_repr.rst create mode 100644 hy/contrib/hy_repr.hy create mode 100644 tests/native_tests/contrib/hy_repr.hy diff --git a/NEWS b/NEWS index 7584628..b0d5d24 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,10 @@ Changes from 0.12.1 returns. * `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 [ Bug Fixes ] diff --git a/docs/contrib/hy_repr.rst b/docs/contrib/hy_repr.rst new file mode 100644 index 0000000..c9ad777 --- /dev/null +++ b/docs/contrib/hy_repr.rst @@ -0,0 +1,42 @@ +================== +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]])``. + +.. _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``. diff --git a/docs/contrib/index.rst b/docs/contrib/index.rst index 67c8abe..917e12b 100644 --- a/docs/contrib/index.rst +++ b/docs/contrib/index.rst @@ -16,3 +16,4 @@ Contents: profile sequences walk + hy_repr diff --git a/hy/contrib/hy_repr.hy b/hy/contrib/hy_repr.hy new file mode 100644 index 0000000..e70a5a8 --- /dev/null +++ b/hy/contrib/hy_repr.hy @@ -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)) diff --git a/tests/__init__.py b/tests/__init__.py index 88a1cad..74b8390 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,6 +19,7 @@ from .native_tests.contrib.loop import * # noqa from .native_tests.contrib.walk import * # noqa from .native_tests.contrib.multi import * # noqa from .native_tests.contrib.sequences import * # noqa +from .native_tests.contrib.hy_repr import * # noqa if PY3: from .native_tests.py3_only_tests import * # noqa diff --git a/tests/native_tests/contrib/hy_repr.hy b/tests/native_tests/contrib/hy_repr.hy new file mode 100644 index 0000000..04dbce6 --- /dev/null +++ b/tests/native_tests/contrib/hy_repr.hy @@ -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"))) From 33a696d48738ed56113c0d8600d9525f626cc5a4 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 24 Mar 2017 09:03:12 -0700 Subject: [PATCH 3/5] Add a command-line option --repl-output-fn (especially for hy.contrib.hy-repr) --- docs/contrib/hy_repr.rst | 8 ++++- docs/tutorial.rst | 15 +++++++- hy/cmdline.py | 75 ++++++++++++++++++++++++++-------------- hy/importer.py | 5 ++- tests/test_bin.py | 60 ++++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 28 deletions(-) diff --git a/docs/contrib/hy_repr.rst b/docs/contrib/hy_repr.rst index c9ad777..51d8842 100644 --- a/docs/contrib/hy_repr.rst +++ b/docs/contrib/hy_repr.rst @@ -5,7 +5,13 @@ 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 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: diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 50f0e73..19edbb9 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -193,7 +193,6 @@ Hy. Let's experiment with this in the hy interpreter:: [1, 2, 3] => {"dog" "bark" ... "cat" "meow"} - ... {'dog': 'bark', 'cat': 'meow'} => (, 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. +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 supports the Common Lisp method of quoting: diff --git a/hy/cmdline.py b/hy/cmdline.py index 87dbe67..699e5f6 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -32,14 +32,16 @@ import code import ast import sys import os +import importlib import astor.codegen import hy from hy.lex import LexException, PrematureEndOfInput, tokenize -from hy.compiler import hy_compile, HyTypeError -from hy.importer import (ast_compile, import_buffer_to_module, +from hy.lex.parser import hy_symbol_mangle +from hy.compiler import HyTypeError +from hy.importer import (hy_eval, import_buffer_to_module, import_file_to_ast, import_file_to_hst, import_buffer_to_ast, import_buffer_to_hst) from hy.completer import completion @@ -73,25 +75,35 @@ builtins.quit = HyQuitter('quit') 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): - def __init__(self, spy=False, locals=None, filename=""): + def __init__(self, spy=False, output_fn=None, locals=None, + filename=""): + 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, filename=filename) def runsource(self, source, filename='', symbol='single'): global SIMPLE_TRACEBACKS try: - tokens = tokenize(source) - except PrematureEndOfInput: - return True + try: + tokens = tokenize(source) + except PrematureEndOfInput: + return True + tokens = tokenize("(do " + source + "\n)") except LexException as e: if e.source is None: e.source = source @@ -100,10 +112,15 @@ class HyREPL(code.InteractiveConsole): return False try: - _ast = hy_compile(tokens, "__console__", root=ast.Interactive) - if self.spy: - print_python_code(_ast) - code = ast_compile(_ast, filename, symbol) + def ast_callback(main_ast, expr_ast): + if self.spy: + # Mush the two AST chunks into a single module for + # conversion into Python. + new_ast = ast.Module(main_ast.body + + [ast.Expr(expr_ast.body)]) + print(astor.to_source(new_ast)) + value = hy_eval(tokens[0], self.locals, "__console__", + ast_callback) except HyTypeError as e: if e.source is None: e.source = source @@ -117,7 +134,12 @@ class HyREPL(code.InteractiveConsole): self.showtraceback() 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 @@ -211,7 +233,7 @@ def run_file(filename): return 0 -def run_repl(hr=None, spy=False): +def run_repl(hr=None, **kwargs): import platform sys.ps1 = "=> " sys.ps2 = "... " @@ -221,7 +243,7 @@ def run_repl(hr=None, spy=False): with completion(Completer(namespace)): if not hr: - hr = HyREPL(spy, namespace) + hr = HyREPL(locals=namespace, **kwargs) hr.interact("{appname} {version} using " "{py}({build}) {pyversion} on {os}".format( @@ -236,8 +258,8 @@ def run_repl(hr=None, spy=False): return 0 -def run_icommand(source, spy=False): - hr = HyREPL(spy) +def run_icommand(source, **kwargs): + hr = HyREPL(**kwargs) if os.path.exists(source): with open(source, "r") as f: source = f.read() @@ -272,7 +294,9 @@ def cmdline_handler(scriptname, argv): help="program passed in as a string, then stay in REPL") parser.add_argument("--spy", action="store_true", 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("--show-tracebacks", action="store_true", @@ -315,7 +339,8 @@ def cmdline_handler(scriptname, argv): if options.icommand: # 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[0] == "-": @@ -332,7 +357,7 @@ def cmdline_handler(scriptname, argv): sys.exit(e.errno) # 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" diff --git a/hy/importer.py b/hy/importer.py index 8418b7c..c3c0192 100644 --- a/hy/importer.py +++ b/hy/importer.py @@ -111,7 +111,7 @@ def import_buffer_to_module(module_name, buf): return mod -def hy_eval(hytree, namespace, module_name): +def hy_eval(hytree, namespace, module_name, ast_callback=None): foo = HyObject() foo.start_line = 0 foo.end_line = 0 @@ -133,6 +133,9 @@ def hy_eval(hytree, namespace, module_name): node.lineno = 1 node.col_offset = 1 + if ast_callback: + ast_callback(_ast, expr) + if not isinstance(namespace, dict): raise HyTypeError(foo, "Globals must be a dictionary") diff --git a/tests/test_bin.py b/tests/test_bin.py index 218b030..f670f2c 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -23,12 +23,17 @@ # DEALINGS IN THE SOFTWARE. import os import subprocess +import re from hy._compat import PY3 hy_dir = os.environ.get('HY_DIR', '') +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), stdin=subprocess.PIPE, @@ -58,6 +63,61 @@ def test_bin_hy_stdin(): output, _ = run_cmd("hy", '(koan)') assert "monk" in output + 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_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(): output, _ = run_cmd("hy -c \"(koan)\"") From ca1bd0ffd3a8baf96a484661543a6c6c53d2ddd7 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 24 Mar 2017 09:03:55 -0700 Subject: [PATCH 4/5] Add a test for as-> in the REPL Closes #1255. --- tests/test_bin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_bin.py b/tests/test_bin.py index f670f2c..bc32749 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -103,6 +103,12 @@ def test_bin_hy_stdin_assignment(): 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_hy_repr(): output, _ = run_cmd("hy", '(+ [1] [2])') assert "[1, 2]" in output.replace('L', '') From e478008cceab11255bfabcf99129730e827672b9 Mon Sep 17 00:00:00 2001 From: Kodi Arfer Date: Fri, 24 Mar 2017 11:09:30 -0700 Subject: [PATCH 5/5] Fix HyMacroExpansionError underline alignment --- hy/cmdline.py | 6 ++++-- tests/test_bin.py | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/hy/cmdline.py b/hy/cmdline.py index 699e5f6..beb84f7 100644 --- a/hy/cmdline.py +++ b/hy/cmdline.py @@ -103,7 +103,9 @@ class HyREPL(code.InteractiveConsole): tokens = tokenize(source) except PrematureEndOfInput: return True - tokens = tokenize("(do " + source + "\n)") + 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: if e.source is None: e.source = source @@ -119,7 +121,7 @@ class HyREPL(code.InteractiveConsole): new_ast = ast.Module(main_ast.body + [ast.Expr(expr_ast.body)]) print(astor.to_source(new_ast)) - value = hy_eval(tokens[0], self.locals, "__console__", + value = hy_eval(do, self.locals, "__console__", ast_callback) except HyTypeError as e: if e.source is None: diff --git a/tests/test_bin.py b/tests/test_bin.py index bc32749..b355e63 100644 --- a/tests/test_bin.py +++ b/tests/test_bin.py @@ -109,6 +109,11 @@ def test_bin_hy_stdin_as_arrow(): 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', '')