2020-01-03 19:47:51 +01:00
|
|
|
# Copyright 2020 the authors.
|
2017-04-27 23:16:57 +02:00
|
|
|
# This file is part of Hy, which is free software licensed under the Expat
|
|
|
|
# license. See the LICENSE.
|
2013-04-24 14:32:03 +02:00
|
|
|
|
2014-04-10 06:45:11 +02:00
|
|
|
from __future__ import print_function
|
|
|
|
|
2019-10-08 02:20:54 +02:00
|
|
|
import colorama
|
|
|
|
colorama.init()
|
|
|
|
|
2013-06-29 23:56:58 +02:00
|
|
|
import argparse
|
2013-07-05 03:31:17 +02:00
|
|
|
import code
|
|
|
|
import ast
|
2013-04-24 03:57:13 +02:00
|
|
|
import sys
|
2015-02-27 11:59:00 +01:00
|
|
|
import os
|
2018-08-20 06:29:29 +02:00
|
|
|
import io
|
2017-03-24 17:03:12 +01:00
|
|
|
import importlib
|
2018-08-20 06:29:29 +02:00
|
|
|
import py_compile
|
2018-10-13 06:25:43 +02:00
|
|
|
import traceback
|
2018-08-20 06:29:29 +02:00
|
|
|
import runpy
|
2018-10-23 20:41:20 +02:00
|
|
|
import types
|
2018-11-01 22:40:13 +01:00
|
|
|
import time
|
|
|
|
import linecache
|
|
|
|
import hashlib
|
|
|
|
import codeop
|
2019-05-20 22:12:00 +02:00
|
|
|
import builtins
|
2013-04-24 03:57:13 +02:00
|
|
|
|
2017-10-31 21:13:41 +01:00
|
|
|
import astor.code_gen
|
2014-04-10 06:45:11 +02:00
|
|
|
|
2013-04-24 03:57:13 +02:00
|
|
|
import hy
|
2018-11-01 22:40:13 +01:00
|
|
|
|
2018-11-10 19:53:28 +01:00
|
|
|
from hy.lex import hy_parse, mangle
|
2018-11-01 22:40:13 +01:00
|
|
|
from contextlib import contextmanager
|
2018-10-29 02:43:17 +01:00
|
|
|
from hy.lex.exceptions import PrematureEndOfInput
|
2018-11-01 22:40:13 +01:00
|
|
|
from hy.compiler import (HyASTCompiler, hy_eval, hy_compile,
|
|
|
|
hy_ast_compile_flags)
|
2018-10-13 06:25:43 +02:00
|
|
|
from hy.errors import (HyLanguageError, HyRequireError, HyMacroExpansionError,
|
|
|
|
filtered_hy_exceptions, hy_exc_handler)
|
2018-11-10 19:53:28 +01:00
|
|
|
from hy.importer import runhy
|
2018-08-20 06:29:29 +02:00
|
|
|
from hy.completer import completion, Completer
|
2013-04-12 17:30:13 +02:00
|
|
|
from hy.macros import macro, require
|
2017-02-17 04:43:00 +01:00
|
|
|
from hy.models import HyExpression, HyString, HySymbol
|
2013-06-25 17:02:02 +02:00
|
|
|
|
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
sys.last_type = None
|
|
|
|
sys.last_value = None
|
|
|
|
sys.last_traceback = None
|
|
|
|
|
|
|
|
|
2013-06-25 17:02:02 +02:00
|
|
|
class HyQuitter(object):
|
|
|
|
def __init__(self, name):
|
|
|
|
self.name = name
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "Use (%s) or Ctrl-D (i.e. EOF) to exit" % (self.name)
|
|
|
|
|
|
|
|
__str__ = __repr__
|
|
|
|
|
|
|
|
def __call__(self, code=None):
|
|
|
|
try:
|
|
|
|
sys.stdin.close()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
raise SystemExit(code)
|
|
|
|
|
2019-10-14 07:50:47 +02:00
|
|
|
class HyHelper(object):
|
|
|
|
def __repr__(self):
|
|
|
|
return ("Use (help) for interactive help, or (help object) for help "
|
|
|
|
"about object.")
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwds):
|
|
|
|
import pydoc
|
|
|
|
return pydoc.help(*args, **kwds)
|
|
|
|
|
2018-08-20 06:29:29 +02:00
|
|
|
|
2013-06-25 17:02:02 +02:00
|
|
|
builtins.quit = HyQuitter('quit')
|
|
|
|
builtins.exit = HyQuitter('exit')
|
2019-10-14 07:50:47 +02:00
|
|
|
builtins.help = HyHelper()
|
2013-06-25 17:02:02 +02:00
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
@contextmanager
|
|
|
|
def extend_linecache(add_cmdline_cache):
|
|
|
|
_linecache_checkcache = linecache.checkcache
|
|
|
|
|
|
|
|
def _cmdline_checkcache(*args):
|
|
|
|
_linecache_checkcache(*args)
|
|
|
|
linecache.cache.update(add_cmdline_cache)
|
|
|
|
|
|
|
|
linecache.checkcache = _cmdline_checkcache
|
|
|
|
yield
|
|
|
|
linecache.checkcache = _linecache_checkcache
|
|
|
|
|
|
|
|
|
|
|
|
_codeop_maybe_compile = codeop._maybe_compile
|
|
|
|
|
|
|
|
|
|
|
|
def _hy_maybe_compile(compiler, source, filename, symbol):
|
|
|
|
"""The `codeop` version of this will compile the same source multiple
|
|
|
|
times, and, since we have macros and things like `eval-and-compile`, we
|
|
|
|
can't allow that.
|
|
|
|
"""
|
|
|
|
if not isinstance(compiler, HyCompile):
|
|
|
|
return _codeop_maybe_compile(compiler, source, filename, symbol)
|
|
|
|
|
|
|
|
for line in source.split("\n"):
|
|
|
|
line = line.strip()
|
|
|
|
if line and line[0] != ';':
|
|
|
|
# Leave it alone (could do more with Hy syntax)
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
if symbol != "eval":
|
|
|
|
# Replace it with a 'pass' statement (i.e. tell the compiler to do
|
|
|
|
# nothing)
|
|
|
|
source = "pass"
|
2013-08-24 16:33:13 +02:00
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
return compiler(source, filename, symbol)
|
|
|
|
|
|
|
|
|
|
|
|
codeop._maybe_compile = _hy_maybe_compile
|
|
|
|
|
|
|
|
|
|
|
|
class HyCompile(codeop.Compile, object):
|
|
|
|
"""This compiler uses `linecache` like
|
|
|
|
`IPython.core.compilerop.CachingCompiler`.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, module, locals, ast_callback=None,
|
|
|
|
hy_compiler=None, cmdline_cache={}):
|
2018-10-13 06:25:43 +02:00
|
|
|
self.module = module
|
2018-11-01 22:40:13 +01:00
|
|
|
self.locals = locals
|
2018-10-13 06:25:43 +02:00
|
|
|
self.ast_callback = ast_callback
|
|
|
|
self.hy_compiler = hy_compiler
|
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
super(HyCompile, self).__init__()
|
|
|
|
|
|
|
|
self.flags |= hy_ast_compile_flags
|
|
|
|
|
|
|
|
self.cmdline_cache = cmdline_cache
|
|
|
|
|
|
|
|
def _cache(self, source, name):
|
|
|
|
entry = (len(source),
|
|
|
|
time.time(),
|
|
|
|
[line + '\n' for line in source.splitlines()],
|
|
|
|
name)
|
|
|
|
|
|
|
|
linecache.cache[name] = entry
|
|
|
|
self.cmdline_cache[name] = entry
|
|
|
|
|
|
|
|
def _update_exc_info(self):
|
|
|
|
self.locals['_hy_last_type'] = sys.last_type
|
|
|
|
self.locals['_hy_last_value'] = sys.last_value
|
|
|
|
# Skip our frame.
|
|
|
|
sys.last_traceback = getattr(sys.last_traceback, 'tb_next',
|
|
|
|
sys.last_traceback)
|
|
|
|
self.locals['_hy_last_traceback'] = sys.last_traceback
|
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
def __call__(self, source, filename="<input>", symbol="single"):
|
2018-11-01 22:40:13 +01:00
|
|
|
|
|
|
|
if source == 'pass':
|
|
|
|
# We need to return a no-op to signal that no more input is needed.
|
|
|
|
return (compile(source, filename, symbol),) * 2
|
|
|
|
|
|
|
|
hash_digest = hashlib.sha1(source.encode("utf-8").strip()).hexdigest()
|
|
|
|
name = '{}-{}'.format(filename.strip('<>'), hash_digest)
|
|
|
|
|
|
|
|
try:
|
|
|
|
hy_ast = hy_parse(source, filename=name)
|
|
|
|
except Exception:
|
|
|
|
# Capture a traceback without the compiler/REPL frames.
|
|
|
|
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
|
|
|
|
self._update_exc_info()
|
|
|
|
raise
|
|
|
|
|
|
|
|
self._cache(source, name)
|
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
try:
|
|
|
|
hy_ast = hy_parse(source, filename=filename)
|
|
|
|
root_ast = ast.Interactive if symbol == 'single' else ast.Module
|
|
|
|
|
|
|
|
# Our compiler doesn't correspond to a real, fixed source file, so
|
|
|
|
# we need to [re]set these.
|
|
|
|
self.hy_compiler.filename = filename
|
|
|
|
self.hy_compiler.source = source
|
|
|
|
exec_ast, eval_ast = hy_compile(hy_ast, self.module, root=root_ast,
|
|
|
|
get_expr=True,
|
|
|
|
compiler=self.hy_compiler,
|
|
|
|
filename=filename, source=source)
|
|
|
|
|
|
|
|
if self.ast_callback:
|
|
|
|
self.ast_callback(exec_ast, eval_ast)
|
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
exec_code = super(HyCompile, self).__call__(exec_ast, name, symbol)
|
|
|
|
eval_code = super(HyCompile, self).__call__(eval_ast, name, 'eval')
|
2018-10-13 06:25:43 +02:00
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
except HyLanguageError:
|
|
|
|
# Hy will raise exceptions during compile-time that Python would
|
|
|
|
# raise during run-time (e.g. import errors for `require`). In
|
|
|
|
# order to work gracefully with the Python world, we convert such
|
|
|
|
# Hy errors to code that purposefully reraises those exceptions in
|
|
|
|
# the places where Python code expects them.
|
2018-10-13 06:25:43 +02:00
|
|
|
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
|
2018-11-01 22:40:13 +01:00
|
|
|
self._update_exc_info()
|
|
|
|
exec_code = super(HyCompile, self).__call__(
|
|
|
|
'import hy._compat; hy._compat.reraise('
|
|
|
|
'_hy_last_type, _hy_last_value, _hy_last_traceback)',
|
|
|
|
name, symbol)
|
|
|
|
eval_code = super(HyCompile, self).__call__('None', name, 'eval')
|
|
|
|
|
|
|
|
return exec_code, eval_code
|
|
|
|
|
|
|
|
|
|
|
|
class HyCommandCompiler(codeop.CommandCompiler, object):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.compiler = HyCompile(*args, **kwargs)
|
|
|
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
return super(HyCommandCompiler, self).__call__(*args, **kwargs)
|
|
|
|
except PrematureEndOfInput:
|
|
|
|
# We have to do this here, because `codeop._maybe_compile` won't
|
|
|
|
# take `None` for a return value (at least not in Python 2.7) and
|
|
|
|
# this exception type is also a `SyntaxError`, so it will be caught
|
|
|
|
# by `code.InteractiveConsole` base methods before it reaches our
|
|
|
|
# `runsource`.
|
2018-10-13 06:25:43 +02:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
2018-10-23 20:41:20 +02:00
|
|
|
class HyREPL(code.InteractiveConsole, object):
|
2017-03-24 17:03:12 +01:00
|
|
|
def __init__(self, spy=False, output_fn=None, locals=None,
|
2018-10-13 06:25:43 +02:00
|
|
|
filename="<stdin>"):
|
2017-03-24 17:03:12 +01:00
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
# Create a proper module for this REPL so that we can obtain it easily
|
|
|
|
# (e.g. using `importlib.import_module`).
|
|
|
|
# We let `InteractiveConsole` initialize `self.locals` when it's
|
|
|
|
# `None`.
|
2018-10-23 20:41:20 +02:00
|
|
|
super(HyREPL, self).__init__(locals=locals,
|
|
|
|
filename=filename)
|
|
|
|
|
|
|
|
module_name = self.locals.get('__name__', '__console__')
|
2018-10-13 06:25:43 +02:00
|
|
|
# Make sure our newly created module is properly introduced to
|
|
|
|
# `sys.modules`, and consistently use its namespace as `self.locals`
|
|
|
|
# from here on.
|
2018-10-23 20:41:20 +02:00
|
|
|
self.module = sys.modules.setdefault(module_name,
|
|
|
|
types.ModuleType(module_name))
|
|
|
|
self.module.__dict__.update(self.locals)
|
|
|
|
self.locals = self.module.__dict__
|
|
|
|
|
|
|
|
# Load cmdline-specific macros.
|
2018-10-13 06:25:43 +02:00
|
|
|
require('hy.cmdline', self.module, assignments='ALL')
|
2018-10-23 20:41:20 +02:00
|
|
|
|
2018-11-11 05:43:39 +01:00
|
|
|
self.hy_compiler = HyASTCompiler(self.module)
|
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
self.cmdline_cache = {}
|
|
|
|
self.compile = HyCommandCompiler(self.module,
|
|
|
|
self.locals,
|
|
|
|
ast_callback=self.ast_callback,
|
|
|
|
hy_compiler=self.hy_compiler,
|
|
|
|
cmdline_cache=self.cmdline_cache)
|
2018-10-13 06:25:43 +02:00
|
|
|
|
2013-08-19 15:27:55 +02:00
|
|
|
self.spy = spy
|
2018-10-13 06:25:43 +02:00
|
|
|
self.last_value = None
|
2018-11-01 22:40:13 +01:00
|
|
|
self.print_last_value = True
|
2017-03-24 17:03:12 +01:00
|
|
|
|
|
|
|
if output_fn is None:
|
|
|
|
self.output_fn = repr
|
|
|
|
elif callable(output_fn):
|
|
|
|
self.output_fn = output_fn
|
|
|
|
else:
|
|
|
|
if "." in output_fn:
|
2018-03-04 23:20:46 +01:00
|
|
|
parts = [mangle(x) for x in output_fn.split(".")]
|
2018-02-27 20:53:23 +01:00
|
|
|
module, f = '.'.join(parts[:-1]), parts[-1]
|
2017-03-24 17:03:12 +01:00
|
|
|
self.output_fn = getattr(importlib.import_module(module), f)
|
|
|
|
else:
|
2019-05-20 22:12:00 +02:00
|
|
|
self.output_fn = getattr(builtins, mangle(output_fn))
|
2017-03-24 17:03:12 +01:00
|
|
|
|
2018-03-31 23:26:46 +02:00
|
|
|
# Pre-mangle symbols for repl recent results: *1, *2, *3
|
|
|
|
self._repl_results_symbols = [mangle("*{}".format(i + 1)) for i in range(3)]
|
|
|
|
self.locals.update({sym: None for sym in self._repl_results_symbols})
|
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
# Allow access to the running REPL instance
|
|
|
|
self.locals['_hy_repl'] = self
|
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
def ast_callback(self, exec_ast, eval_ast):
|
2018-10-29 03:42:18 +01:00
|
|
|
if self.spy:
|
2018-10-13 06:25:43 +02:00
|
|
|
try:
|
|
|
|
# Mush the two AST chunks into a single module for
|
|
|
|
# conversion into Python.
|
2019-04-08 21:38:03 +02:00
|
|
|
new_ast = ast.Module(
|
|
|
|
exec_ast.body + [ast.Expr(eval_ast.body)],
|
|
|
|
type_ignores=[])
|
2018-10-13 06:25:43 +02:00
|
|
|
print(astor.to_source(new_ast))
|
|
|
|
except Exception:
|
|
|
|
msg = 'Exception in AST callback:\n{}\n'.format(
|
|
|
|
traceback.format_exc())
|
|
|
|
self.write(msg)
|
2018-10-29 03:42:18 +01:00
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
def _error_wrap(self, error_fn, exc_info_override=False, *args, **kwargs):
|
2018-10-29 03:42:18 +01:00
|
|
|
sys.last_type, sys.last_value, sys.last_traceback = sys.exc_info()
|
|
|
|
|
2018-11-01 22:40:13 +01:00
|
|
|
if exc_info_override:
|
|
|
|
# Use a traceback that doesn't have the REPL frames.
|
|
|
|
sys.last_type = self.locals.get('_hy_last_type', sys.last_type)
|
|
|
|
sys.last_value = self.locals.get('_hy_last_value', sys.last_value)
|
|
|
|
sys.last_traceback = self.locals.get('_hy_last_traceback',
|
|
|
|
sys.last_traceback)
|
|
|
|
|
|
|
|
# Sadly, this method in Python 2.7 ignores an overridden `sys.excepthook`.
|
2018-10-29 03:42:18 +01:00
|
|
|
if sys.excepthook is sys.__excepthook__:
|
|
|
|
error_fn(*args, **kwargs)
|
|
|
|
else:
|
|
|
|
sys.excepthook(sys.last_type, sys.last_value, sys.last_traceback)
|
2018-03-31 23:26:46 +02:00
|
|
|
|
2018-10-29 03:42:18 +01:00
|
|
|
self.locals[mangle("*e")] = sys.last_value
|
|
|
|
|
|
|
|
def showsyntaxerror(self, filename=None):
|
|
|
|
if filename is None:
|
|
|
|
filename = self.filename
|
|
|
|
|
|
|
|
self._error_wrap(super(HyREPL, self).showsyntaxerror,
|
2018-11-01 22:40:13 +01:00
|
|
|
exc_info_override=True,
|
2018-10-29 03:42:18 +01:00
|
|
|
filename=filename)
|
|
|
|
|
|
|
|
def showtraceback(self):
|
|
|
|
self._error_wrap(super(HyREPL, self).showtraceback)
|
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
def runcode(self, code):
|
2013-04-24 03:57:13 +02:00
|
|
|
try:
|
2018-10-13 06:25:43 +02:00
|
|
|
eval(code[0], self.locals)
|
|
|
|
self.last_value = eval(code[1], self.locals)
|
|
|
|
# Don't print `None` values.
|
|
|
|
self.print_last_value = self.last_value is not None
|
2018-10-29 03:42:18 +01:00
|
|
|
except SystemExit:
|
|
|
|
raise
|
2018-03-31 23:26:46 +02:00
|
|
|
except Exception as e:
|
2018-10-13 06:25:43 +02:00
|
|
|
# Set this to avoid a print-out of the last value on errors.
|
|
|
|
self.print_last_value = False
|
|
|
|
self.showtraceback()
|
|
|
|
|
|
|
|
def runsource(self, source, filename='<stdin>', symbol='exec'):
|
|
|
|
try:
|
|
|
|
res = super(HyREPL, self).runsource(source, filename, symbol)
|
|
|
|
except (HyMacroExpansionError, HyRequireError):
|
|
|
|
# We need to handle these exceptions ourselves, because the base
|
|
|
|
# method only handles `OverflowError`, `SyntaxError` and
|
|
|
|
# `ValueError`.
|
|
|
|
self.showsyntaxerror(filename)
|
|
|
|
return False
|
|
|
|
except (HyLanguageError):
|
|
|
|
# Our compiler will also raise `TypeError`s
|
2018-10-29 03:42:18 +01:00
|
|
|
self.showtraceback()
|
2013-04-24 03:57:13 +02:00
|
|
|
return False
|
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
# Shift exisitng REPL results
|
|
|
|
if not res:
|
|
|
|
next_result = self.last_value
|
2018-03-31 23:26:46 +02:00
|
|
|
for sym in self._repl_results_symbols:
|
|
|
|
self.locals[sym], next_result = next_result, self.locals[sym]
|
|
|
|
|
2017-03-24 17:03:12 +01:00
|
|
|
# Print the value.
|
2018-10-13 06:25:43 +02:00
|
|
|
if self.print_last_value:
|
|
|
|
try:
|
|
|
|
output = self.output_fn(self.last_value)
|
|
|
|
except Exception:
|
|
|
|
self.showtraceback()
|
|
|
|
return False
|
2018-10-29 03:42:18 +01:00
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
print(output)
|
2018-10-29 03:42:18 +01:00
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
return res
|
2013-04-24 03:57:13 +02:00
|
|
|
|
|
|
|
|
|
|
|
@macro("koan")
|
2017-09-23 20:55:44 +02:00
|
|
|
def koan_macro(ETname):
|
2013-04-24 03:57:13 +02:00
|
|
|
return HyExpression([HySymbol('print'),
|
|
|
|
HyString("""
|
|
|
|
Ummon asked the head monk, "What sutra are you lecturing on?"
|
|
|
|
"The Nirvana Sutra."
|
|
|
|
"The Nirvana Sutra has the Four Virtues, hasn't it?"
|
|
|
|
"It has."
|
|
|
|
Ummon asked, picking up a cup, "How many virtues has this?"
|
2015-01-15 21:06:10 +01:00
|
|
|
"None at all," said the monk.
|
2013-04-24 03:57:13 +02:00
|
|
|
"But ancient people said it had, didn't they?" said Ummon.
|
2015-01-15 21:06:10 +01:00
|
|
|
"What do you think of what they said?"
|
2013-04-24 03:57:13 +02:00
|
|
|
Ummon struck the cup and asked, "You understand?"
|
|
|
|
"No," said the monk.
|
|
|
|
"Then," said Ummon, "You'd better go on with your lectures on the sutra."
|
|
|
|
""")])
|
|
|
|
|
|
|
|
|
|
|
|
@macro("ideas")
|
2017-09-23 20:55:44 +02:00
|
|
|
def ideas_macro(ETname):
|
2013-04-24 03:57:13 +02:00
|
|
|
return HyExpression([HySymbol('print'),
|
2016-12-30 11:20:26 +01:00
|
|
|
HyString(r"""
|
2013-04-24 03:57:13 +02:00
|
|
|
|
|
|
|
=> (import [sh [figlet]])
|
|
|
|
=> (figlet "Hi, Hy!")
|
|
|
|
_ _ _ _ _ _
|
|
|
|
| | | (_) | | | |_ _| |
|
|
|
|
| |_| | | | |_| | | | | |
|
|
|
|
| _ | |_ | _ | |_| |_|
|
|
|
|
|_| |_|_( ) |_| |_|\__, (_)
|
|
|
|
|/ |___/
|
|
|
|
|
|
|
|
|
|
|
|
;;; string things
|
|
|
|
(.join ", " ["what" "the" "heck"])
|
|
|
|
|
|
|
|
|
|
|
|
;;; this one plays with command line bits
|
|
|
|
(import [sh [cat grep]])
|
|
|
|
(-> (cat "/usr/share/dict/words") (grep "-E" "bro$"))
|
|
|
|
|
|
|
|
|
|
|
|
;;; filtering a list w/ a lambda
|
2017-02-23 00:36:52 +01:00
|
|
|
(filter (fn [x] (= (% x 2) 0)) (range 0 10))
|
2013-04-24 03:57:13 +02:00
|
|
|
|
|
|
|
|
|
|
|
;;; swaggin' functional bits (Python rulez)
|
2017-02-23 00:36:52 +01:00
|
|
|
(max (map (fn [x] (len x)) ["hi" "my" "name" "is" "paul"]))
|
2013-04-24 03:57:13 +02:00
|
|
|
|
|
|
|
""")])
|
|
|
|
|
2013-05-16 15:34:14 +02:00
|
|
|
|
2018-10-29 02:43:17 +01:00
|
|
|
def run_command(source, filename=None):
|
|
|
|
__main__ = importlib.import_module('__main__')
|
|
|
|
require("hy.cmdline", __main__, assignments="ALL")
|
2018-10-13 06:25:43 +02:00
|
|
|
try:
|
|
|
|
tree = hy_parse(source, filename=filename)
|
|
|
|
except HyLanguageError:
|
|
|
|
hy_exc_handler(*sys.exc_info())
|
|
|
|
return 1
|
|
|
|
|
2018-10-29 03:42:18 +01:00
|
|
|
with filtered_hy_exceptions():
|
|
|
|
hy_eval(tree, None, __main__, filename=filename, source=source)
|
Much better version of new error messages.
This version is much simpler.
At the point that the exception is raised, we don't have access to
the actual source, just the current expression. but as the
exception percolates up, we can intercept it, add the source and
the re-raise it.
Then at the final point, in the cmdline handler, we can choose to
let the entire traceback print, or just the simpler, direct error
message.
And even with the full traceback, the last bit is nicely formatted
just like the shorter, simpler message.
The error message is colored if clint is installed, but to avoid
yet another dependency, you get monochrome without clint.
I'm sure there is a better way to do the markup, the current method
is kludgy but works.
I wish there was more shared code between HyTypeError and LexException
but they are kind of different in some fundamental ways.
This doesn't work (yet) with runtime errors generated from Python,
like NameError, but I have a method that can catch NameError and turn it
into a more pleasing output.
Finally, there is no obvious way to raise HyTypeError from pure Hy code,
so methods in core/language.hy throw ugly TypeError/ValueError.
2013-12-22 20:56:03 +01:00
|
|
|
return 0
|
2013-04-24 03:57:13 +02:00
|
|
|
|
|
|
|
|
2017-03-24 17:03:12 +01:00
|
|
|
def run_repl(hr=None, **kwargs):
|
2014-05-02 15:55:09 +02:00
|
|
|
import platform
|
2013-04-24 03:57:13 +02:00
|
|
|
sys.ps1 = "=> "
|
|
|
|
sys.ps2 = "... "
|
|
|
|
|
2018-10-23 20:41:20 +02:00
|
|
|
if not hr:
|
|
|
|
hr = HyREPL(**kwargs)
|
2015-01-14 20:39:28 +01:00
|
|
|
|
2018-10-23 20:41:20 +02:00
|
|
|
namespace = hr.locals
|
2018-11-01 22:40:13 +01:00
|
|
|
with filtered_hy_exceptions(), \
|
|
|
|
extend_linecache(hr.cmdline_cache), \
|
|
|
|
completion(Completer(namespace)):
|
2014-05-02 15:55:09 +02:00
|
|
|
hr.interact("{appname} {version} using "
|
|
|
|
"{py}({build}) {pyversion} on {os}".format(
|
|
|
|
appname=hy.__appname__,
|
|
|
|
version=hy.__version__,
|
|
|
|
py=platform.python_implementation(),
|
|
|
|
build=platform.python_build()[0],
|
|
|
|
pyversion=platform.python_version(),
|
|
|
|
os=platform.system()
|
|
|
|
))
|
2013-06-29 16:23:48 +02:00
|
|
|
|
2013-04-24 03:57:13 +02:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2017-03-24 17:03:12 +01:00
|
|
|
def run_icommand(source, **kwargs):
|
2015-02-27 11:59:00 +01:00
|
|
|
if os.path.exists(source):
|
2018-08-22 22:21:17 +02:00
|
|
|
# Emulate Python cmdline behavior by setting `sys.path` relative
|
|
|
|
# to the executed file's location.
|
|
|
|
if sys.path[0] == '':
|
|
|
|
sys.path[0] = os.path.realpath(os.path.split(source)[0])
|
|
|
|
else:
|
|
|
|
sys.path.insert(0, os.path.split(source)[0])
|
|
|
|
|
2018-08-20 06:29:29 +02:00
|
|
|
with io.open(source, "r", encoding='utf-8') as f:
|
2015-02-27 11:59:00 +01:00
|
|
|
source = f.read()
|
|
|
|
filename = source
|
|
|
|
else:
|
2018-10-13 06:25:43 +02:00
|
|
|
filename = '<string>'
|
2018-08-22 22:21:17 +02:00
|
|
|
|
2018-10-13 06:25:43 +02:00
|
|
|
hr = HyREPL(**kwargs)
|
2018-10-29 03:42:18 +01:00
|
|
|
with filtered_hy_exceptions():
|
2018-10-13 06:25:43 +02:00
|
|
|
res = hr.runsource(source, filename=filename)
|
|
|
|
|
|
|
|
# If the command was prematurely ended, show an error (just like Python
|
|
|
|
# does).
|
|
|
|
if res:
|
|
|
|
hy_exc_handler(sys.last_type, sys.last_value, sys.last_traceback)
|
|
|
|
|
|
|
|
return run_repl(hr)
|
2013-04-24 03:57:13 +02:00
|
|
|
|
|
|
|
|
2014-11-23 23:05:20 +01:00
|
|
|
USAGE = "%(prog)s [-h | -i cmd | -c cmd | -m module | file | -] [arg] ..."
|
2013-06-29 23:56:58 +02:00
|
|
|
VERSION = "%(prog)s " + hy.__version__
|
2018-03-30 05:04:22 +02:00
|
|
|
EPILOG = """
|
|
|
|
file program read from script
|
|
|
|
module module to execute as main
|
|
|
|
- program read from stdin
|
|
|
|
[arg] ... arguments passed to program in sys.argv[1:]
|
2013-04-24 03:57:13 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def cmdline_handler(scriptname, argv):
|
2013-06-29 23:56:58 +02:00
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
prog="hy",
|
|
|
|
usage=USAGE,
|
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
|
|
epilog=EPILOG)
|
|
|
|
parser.add_argument("-c", dest="command",
|
|
|
|
help="program passed in as a string")
|
2014-11-23 23:05:20 +01:00
|
|
|
parser.add_argument("-m", dest="mod",
|
|
|
|
help="module to run, passed in as a string")
|
2018-03-31 01:20:24 +02:00
|
|
|
parser.add_argument("-E", action='store_true',
|
|
|
|
help="ignore PYTHON* environment variables")
|
2018-08-22 20:20:07 +02:00
|
|
|
parser.add_argument("-B", action='store_true',
|
|
|
|
help="don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x")
|
2018-03-30 05:04:22 +02:00
|
|
|
parser.add_argument("-i", dest="icommand",
|
|
|
|
help="program passed in as a string, then stay in REPL")
|
2013-08-19 15:27:55 +02:00
|
|
|
parser.add_argument("--spy", action="store_true",
|
|
|
|
help="print equivalent Python code before executing")
|
2017-03-24 17:03:12 +01:00
|
|
|
parser.add_argument("--repl-output-fn",
|
|
|
|
help="function for printing REPL output "
|
|
|
|
"(e.g., hy.contrib.hy-repr.hy-repr)")
|
2016-09-29 18:18:04 +02:00
|
|
|
parser.add_argument("-v", "--version", action="version", version=VERSION)
|
2013-04-24 03:57:13 +02:00
|
|
|
|
2013-06-29 23:56:58 +02:00
|
|
|
# this will contain the script/program name and any arguments for it.
|
|
|
|
parser.add_argument('args', nargs=argparse.REMAINDER,
|
|
|
|
help=argparse.SUPPRESS)
|
|
|
|
|
2018-08-22 20:20:07 +02:00
|
|
|
# Get the path of the Hy cmdline executable and swap it with
|
|
|
|
# `sys.executable` (saving the original, just in case).
|
|
|
|
# XXX: The `__main__` module will also have `__file__` set to the
|
|
|
|
# entry-point script. Currently, I don't see an immediate problem, but
|
|
|
|
# that's not how the Python cmdline works.
|
2013-06-29 23:56:58 +02:00
|
|
|
hy.executable = argv[0]
|
2018-08-22 20:20:07 +02:00
|
|
|
hy.sys_executable = sys.executable
|
|
|
|
sys.executable = hy.executable
|
2013-06-29 23:56:58 +02:00
|
|
|
|
2018-08-22 20:20:07 +02:00
|
|
|
# Need to split the args. If using "-m" all args after the MOD are sent to
|
|
|
|
# the module in sys.argv.
|
2014-11-23 23:05:20 +01:00
|
|
|
module_args = []
|
|
|
|
if "-m" in argv:
|
|
|
|
mloc = argv.index("-m")
|
|
|
|
if len(argv) > mloc+2:
|
|
|
|
module_args = argv[mloc+2:]
|
|
|
|
argv = argv[:mloc+2]
|
|
|
|
|
2013-06-29 23:56:58 +02:00
|
|
|
options = parser.parse_args(argv[1:])
|
|
|
|
|
2018-03-31 01:20:24 +02:00
|
|
|
if options.E:
|
|
|
|
# User did "hy -E ..."
|
|
|
|
_remove_python_envs()
|
|
|
|
|
2018-08-22 20:20:07 +02:00
|
|
|
if options.B:
|
|
|
|
sys.dont_write_bytecode = True
|
|
|
|
|
2013-04-24 03:57:13 +02:00
|
|
|
if options.command:
|
|
|
|
# User did "hy -c ..."
|
2018-10-29 02:43:17 +01:00
|
|
|
return run_command(options.command, filename='<string>')
|
2013-04-24 03:57:13 +02:00
|
|
|
|
2014-11-23 23:05:20 +01:00
|
|
|
if options.mod:
|
|
|
|
# User did "hy -m ..."
|
2018-08-20 06:29:29 +02:00
|
|
|
sys.argv = [sys.argv[0]] + options.args + module_args
|
|
|
|
runpy.run_module(options.mod, run_name='__main__', alter_sys=True)
|
|
|
|
return 0
|
2014-11-23 23:05:20 +01:00
|
|
|
|
2013-04-24 03:57:13 +02:00
|
|
|
if options.icommand:
|
|
|
|
# User did "hy -i ..."
|
2017-03-24 17:03:12 +01:00
|
|
|
return run_icommand(options.icommand, spy=options.spy,
|
|
|
|
output_fn=options.repl_output_fn)
|
2013-04-24 03:57:13 +02:00
|
|
|
|
2013-06-29 23:56:58 +02:00
|
|
|
if options.args:
|
|
|
|
if options.args[0] == "-":
|
2013-04-24 03:57:13 +02:00
|
|
|
# Read the program from stdin
|
2018-10-29 02:43:17 +01:00
|
|
|
return run_command(sys.stdin.read(), filename='<stdin>')
|
2013-04-24 03:57:13 +02:00
|
|
|
|
|
|
|
else:
|
|
|
|
# User did "hy <filename>"
|
2018-08-22 22:21:17 +02:00
|
|
|
filename = options.args[0]
|
|
|
|
|
|
|
|
# Emulate Python cmdline behavior by setting `sys.path` relative
|
|
|
|
# to the executed file's location.
|
|
|
|
if sys.path[0] == '':
|
|
|
|
sys.path[0] = os.path.realpath(os.path.split(filename)[0])
|
|
|
|
else:
|
|
|
|
sys.path.insert(0, os.path.split(filename)[0])
|
|
|
|
|
2013-07-26 07:33:14 +02:00
|
|
|
try:
|
2018-08-20 06:29:29 +02:00
|
|
|
sys.argv = options.args
|
2018-10-29 03:42:18 +01:00
|
|
|
with filtered_hy_exceptions():
|
|
|
|
runhy.run_path(filename, run_name='__main__')
|
2018-08-20 06:29:29 +02:00
|
|
|
return 0
|
|
|
|
except FileNotFoundError as e:
|
|
|
|
print("hy: Can't open file '{0}': [Errno {1}] {2}".format(
|
|
|
|
e.filename, e.errno, e.strerror), file=sys.stderr)
|
2015-02-16 23:27:18 +01:00
|
|
|
sys.exit(e.errno)
|
2018-10-13 06:25:43 +02:00
|
|
|
except HyLanguageError:
|
|
|
|
hy_exc_handler(*sys.exc_info())
|
|
|
|
sys.exit(1)
|
2013-04-24 03:57:13 +02:00
|
|
|
|
|
|
|
# User did NOTHING!
|
2017-03-24 17:03:12 +01:00
|
|
|
return run_repl(spy=options.spy, output_fn=options.repl_output_fn)
|
2013-06-29 23:56:58 +02:00
|
|
|
|
|
|
|
|
|
|
|
# entry point for cmd line script "hy"
|
|
|
|
def hy_main():
|
2018-08-20 06:29:29 +02:00
|
|
|
sys.path.insert(0, "")
|
2013-06-29 23:56:58 +02:00
|
|
|
sys.exit(cmdline_handler("hy", sys.argv))
|
|
|
|
|
|
|
|
|
|
|
|
def hyc_main():
|
2013-07-26 07:33:14 +02:00
|
|
|
parser = argparse.ArgumentParser(prog="hyc")
|
2018-08-20 06:29:29 +02:00
|
|
|
parser.add_argument("files", metavar="FILE", nargs='*',
|
|
|
|
help=('File(s) to compile (use STDIN if only'
|
|
|
|
' "-" or nothing is provided)'))
|
2013-07-26 07:33:14 +02:00
|
|
|
parser.add_argument("-v", action="version", version=VERSION)
|
|
|
|
|
|
|
|
options = parser.parse_args(sys.argv[1:])
|
|
|
|
|
2018-08-20 06:29:29 +02:00
|
|
|
rv = 0
|
|
|
|
if len(options.files) == 0 or (
|
|
|
|
len(options.files) == 1 and options.files[0] == '-'):
|
|
|
|
while True:
|
|
|
|
filename = sys.stdin.readline()
|
|
|
|
if not filename:
|
|
|
|
break
|
|
|
|
filename = filename.rstrip('\n')
|
|
|
|
try:
|
|
|
|
py_compile.compile(filename, doraise=True)
|
|
|
|
except py_compile.PyCompileError as error:
|
|
|
|
rv = 1
|
|
|
|
sys.stderr.write("%s\n" % error.msg)
|
|
|
|
except OSError as error:
|
|
|
|
rv = 1
|
|
|
|
sys.stderr.write("%s\n" % error)
|
|
|
|
else:
|
|
|
|
for filename in options.files:
|
|
|
|
try:
|
|
|
|
print("Compiling %s" % filename)
|
|
|
|
py_compile.compile(filename, doraise=True)
|
|
|
|
except py_compile.PyCompileError as error:
|
|
|
|
# return value to indicate at least one failure
|
|
|
|
rv = 1
|
|
|
|
sys.stderr.write("%s\n" % error.msg)
|
|
|
|
return rv
|
2014-04-10 06:45:11 +02:00
|
|
|
|
|
|
|
|
|
|
|
# entry point for cmd line script "hy2py"
|
|
|
|
def hy2py_main():
|
2014-06-23 02:08:04 +02:00
|
|
|
import platform
|
2014-04-10 06:45:11 +02:00
|
|
|
|
2016-12-13 19:30:12 +01:00
|
|
|
options = dict(prog="hy2py", usage="%(prog)s [options] [FILE]",
|
2014-04-10 06:45:11 +02:00
|
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
|
|
parser = argparse.ArgumentParser(**options)
|
2016-12-13 19:30:12 +01:00
|
|
|
parser.add_argument("FILE", type=str, nargs='?',
|
|
|
|
help="Input Hy code (use STDIN if \"-\" or "
|
|
|
|
"not provided)")
|
2014-04-10 06:45:11 +02:00
|
|
|
parser.add_argument("--with-source", "-s", action="store_true",
|
|
|
|
help="Show the parsed source structure")
|
|
|
|
parser.add_argument("--with-ast", "-a", action="store_true",
|
|
|
|
help="Show the generated AST")
|
|
|
|
parser.add_argument("--without-python", "-np", action="store_true",
|
|
|
|
help=("Do not show the Python code generated "
|
|
|
|
"from the AST"))
|
|
|
|
|
|
|
|
options = parser.parse_args(sys.argv[1:])
|
|
|
|
|
2016-12-13 19:30:12 +01:00
|
|
|
if options.FILE is None or options.FILE == '-':
|
2018-10-13 06:25:43 +02:00
|
|
|
filename = '<stdin>'
|
2018-08-20 06:29:29 +02:00
|
|
|
source = sys.stdin.read()
|
|
|
|
else:
|
2018-10-13 06:25:43 +02:00
|
|
|
filename = options.FILE
|
|
|
|
with io.open(options.FILE, 'r', encoding='utf-8') as source_file:
|
2018-08-20 06:29:29 +02:00
|
|
|
source = source_file.read()
|
2018-10-13 06:25:43 +02:00
|
|
|
|
|
|
|
with filtered_hy_exceptions():
|
|
|
|
hst = hy_parse(source, filename=filename)
|
2014-04-10 06:45:11 +02:00
|
|
|
|
|
|
|
if options.with_source:
|
2014-06-23 02:08:04 +02:00
|
|
|
# need special printing on Windows in case the
|
|
|
|
# codepage doesn't support utf-8 characters
|
2019-05-20 21:18:56 +02:00
|
|
|
if platform.system() == "Windows":
|
2014-06-23 02:08:04 +02:00
|
|
|
for h in hst:
|
|
|
|
try:
|
|
|
|
print(h)
|
|
|
|
except:
|
|
|
|
print(str(h).encode('utf-8'))
|
|
|
|
else:
|
|
|
|
print(hst)
|
2014-04-10 06:45:11 +02:00
|
|
|
print()
|
|
|
|
print()
|
|
|
|
|
2018-10-29 03:42:18 +01:00
|
|
|
with filtered_hy_exceptions():
|
2018-10-13 06:25:43 +02:00
|
|
|
_ast = hy_compile(hst, '__main__', filename=filename, source=source)
|
2018-10-29 03:42:18 +01:00
|
|
|
|
2014-04-10 06:45:11 +02:00
|
|
|
if options.with_ast:
|
2019-05-20 21:18:56 +02:00
|
|
|
if platform.system() == "Windows":
|
2017-10-31 21:13:41 +01:00
|
|
|
_print_for_windows(astor.dump_tree(_ast))
|
2014-06-23 02:08:04 +02:00
|
|
|
else:
|
2017-10-31 21:13:41 +01:00
|
|
|
print(astor.dump_tree(_ast))
|
2014-04-10 06:45:11 +02:00
|
|
|
print()
|
|
|
|
print()
|
|
|
|
|
|
|
|
if not options.without_python:
|
2019-05-20 21:18:56 +02:00
|
|
|
if platform.system() == "Windows":
|
2017-10-31 21:13:41 +01:00
|
|
|
_print_for_windows(astor.code_gen.to_source(_ast))
|
2014-06-23 02:08:04 +02:00
|
|
|
else:
|
2017-10-31 21:13:41 +01:00
|
|
|
print(astor.code_gen.to_source(_ast))
|
2014-04-10 06:45:11 +02:00
|
|
|
|
|
|
|
parser.exit(0)
|
2014-06-23 02:08:04 +02:00
|
|
|
|
|
|
|
|
|
|
|
# need special printing on Windows in case the
|
|
|
|
# codepage doesn't support utf-8 characters
|
|
|
|
def _print_for_windows(src):
|
|
|
|
for line in src.split("\n"):
|
|
|
|
try:
|
|
|
|
print(line)
|
|
|
|
except:
|
|
|
|
print(line.encode('utf-8'))
|
2018-03-31 01:20:24 +02:00
|
|
|
|
|
|
|
# remove PYTHON* environment variables,
|
|
|
|
# such as "PYTHONPATH"
|
|
|
|
def _remove_python_envs():
|
|
|
|
for key in list(os.environ.keys()):
|
|
|
|
if key.startswith("PYTHON"):
|
|
|
|
os.environ.pop(key)
|