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)\"")