Add a command-line option --repl-output-fn (especially for hy.contrib.hy-repr)

This commit is contained in:
Kodi Arfer 2017-03-24 09:03:12 -07:00
parent bf2f90a0d9
commit 33a696d487
5 changed files with 135 additions and 28 deletions

View File

@ -5,7 +5,13 @@ Hy representations
.. versionadded:: 0.13.0 .. versionadded:: 0.13.0
``hy.contrib.hy-repr`` is a module containing a single function. ``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: .. _hy-repr-fn:

View File

@ -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:

View File

@ -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,35 @@ 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:
tokens = tokenize(source) try:
except PrematureEndOfInput: tokens = tokenize(source)
return True except PrematureEndOfInput:
return True
tokens = tokenize("(do " + source + "\n)")
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 +112,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(tokens[0], 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 +134,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 +233,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 +243,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 +258,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 +294,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 +339,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 +357,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"

View File

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

View File

@ -23,12 +23,17 @@
# 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 hr(s=""):
return "hy --repl-output-fn=hy.contrib.hy-repr.hy-repr " + s
def run_cmd(cmd, stdin_data=None, expect=0): 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,
@ -58,6 +63,61 @@ def test_bin_hy_stdin():
output, _ = run_cmd("hy", '(koan)') output, _ = run_cmd("hy", '(koan)')
assert "monk" in output 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(): def test_bin_hy_cmd():
output, _ = run_cmd("hy -c \"(koan)\"") output, _ = run_cmd("hy -c \"(koan)\"")