diff --git a/.gitignore b/.gitignore index f5a04a965..2a081d5fb 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ env .idea/ doc/sphinx/build/* bpython/_version.py +venv/* +YAPyPy/* +*.egg-info/* +YAPyPy/* \ No newline at end of file diff --git a/YAPyPy b/YAPyPy new file mode 160000 index 000000000..ac7324a56 --- /dev/null +++ b/YAPyPy @@ -0,0 +1 @@ +Subproject commit ac7324a569ed107b30530395cdc90c20f86408e2 diff --git a/bpython/args.py b/bpython/args.py index 1dfe010df..7b6ce3163 100644 --- a/bpython/args.py +++ b/bpython/args.py @@ -27,8 +27,9 @@ def error(self, msg): def version_banner(): - return 'bpython version %s on top of Python %s %s' % ( - __version__, sys.version.split()[0], sys.executable) + import yapypy + return 'bpython version %s on top of YaPyPy %s %s' % ( + __version__, yapypy.__version__, yapypy) def parse(args, extras=None, ignore_stdin=False): diff --git a/bpython/curtsiesfrontend/interpreter.py b/bpython/curtsiesfrontend/interpreter.py index e1a484802..552996c1a 100644 --- a/bpython/curtsiesfrontend/interpreter.py +++ b/bpython/curtsiesfrontend/interpreter.py @@ -1,6 +1,8 @@ # encoding: utf-8 import sys +from tokenize import TokenError + from six import iteritems, text_type from pygments.token import Generic, Token, Keyword, Name, Comment, String diff --git a/bpython/repl.py b/bpython/repl.py index 4439d71eb..42ea117da 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -39,7 +39,10 @@ import textwrap import time import traceback +from codeop import PyCF_DONT_IMPLY_DEDENT, _features from itertools import takewhile +from tokenize import TokenError + from six import itervalues from types import ModuleType @@ -82,6 +85,33 @@ def estimate(self): return self.running_time - self.last_command +class YaPypyCompile: + """Instances of this class behave much like the built-in compile + function, but if one is used to compile text containing a future + statement, it "remembers" and compiles all subsequent program texts + with the statement in force.""" + + def __init__(self): + self.flags = PyCF_DONT_IMPLY_DEDENT + + def __call__(self, source, filename='', symbol=None): + from yapypy.extended_python.py_compile import py_compile + from yapypy.extended_python.symbol_analyzer import to_tagged_ast + from yapypy.extended_python.parser import parse + + ast_node = None + try: + ast_node = to_tagged_ast(parse(source).result) + except TokenError: + raise SyntaxError + + codeob = py_compile(ast_node, filename, True) + for feature in _features: + if codeob.co_flags & feature.compiler_flag: + self.flags |= feature.compiler_flag + return codeob + + class Interpreter(code.InteractiveInterpreter): """Source code interpreter for use in bpython.""" @@ -121,6 +151,7 @@ def __init__(self, locals=None, encoding=None): # super() code.InteractiveInterpreter.__init__(self, locals) self.timer = RuntimeTimer() + self.compile = YaPypyCompile() def reset_running_time(self): self.running_time = 0 @@ -646,7 +677,6 @@ def get_args(self): f.__new__ is not object.__new__ and # py3 f.__new__.__class__ is not object.__new__.__class__): - class_f = f.__new__ if class_f: @@ -675,14 +705,14 @@ def get_source_of_current_name(self): obj = self.get_object(line) return inspection.get_source_unicode(obj) except (AttributeError, NameError) as e: - msg = _(u"Cannot get source: %s") % (e, ) + msg = _(u"Cannot get source: %s") % (e,) except IOError as e: - msg = u"%s" % (e, ) + msg = u"%s" % (e,) except TypeError as e: - if "built-in" in u"%s" % (e, ): - msg = _("Cannot access source of %r") % (obj, ) + if "built-in" in u"%s" % (e,): + msg = _("Cannot access source of %r") % (obj,) else: - msg = _("No source code found for %s") % (self.current_line, ) + msg = _("No source code found for %s") % (self.current_line,) raise SourceNotFound(msg) def set_docstring(self): @@ -786,6 +816,7 @@ def next_indentation(self): if indentation and self.config.dedent_after > 0: def line_is_empty(line): return not line.strip() + empty_lines = takewhile(line_is_empty, reversed(self.buffer)) if sum(1 for _ in empty_lines) >= self.config.dedent_after: indentation -= 1 @@ -806,6 +837,7 @@ def process(): yield line[len(self.ps2):] elif line.rstrip(): yield "# OUT: %s" % (line,) + return "\n".join(process()) def write2file(self): @@ -831,7 +863,7 @@ def write2file(self): mode = self.interact.file_prompt(_('%s already exists. Do you ' 'want to (c)ancel, ' ' (o)verwrite or ' - '(a)ppend? ') % (fn, )) + '(a)ppend? ') % (fn,)) if mode in ('o', 'overwrite', _('overwrite')): mode = 'w' elif mode in ('a', 'append', _('append')): @@ -848,7 +880,7 @@ def write2file(self): except IOError as e: self.interact.notify(_("Error writing file '%s': %s") % (fn, e)) else: - self.interact.notify(_('Saved to %s.') % (fn, )) + self.interact.notify(_('Saved to %s.') % (fn,)) def copy2clipboard(self): """Copy current content to clipboard.""" @@ -901,7 +933,7 @@ def do_pastebin(self, s): self.interact.notify(_('Pastebin URL: %s - Removal URL: %s') % (paste_url, removal_url), 10) else: - self.interact.notify(_('Pastebin URL: %s') % (paste_url, ), 10) + self.interact.notify(_('Pastebin URL: %s') % (paste_url,), 10) return paste_url @@ -927,7 +959,7 @@ def insert_into_history(self, s): self.rl_history.append_reload_and_write(s, pythonhist, getpreferredencoding()) except RuntimeError as e: - self.interact.notify(u"%s" % (e, )) + self.interact.notify(u"%s" % (e,)) def prompt_undo(self): """Returns how many lines to undo, 0 means don't undo""" diff --git a/bpython/test/test_curtsies_repl.py b/bpython/test/test_curtsies_repl.py index 330ed02d2..21c79fc99 100644 --- a/bpython/test/test_curtsies_repl.py +++ b/bpython/test/test_curtsies_repl.py @@ -49,18 +49,8 @@ def cfwp(self, source): self.repl.interp.compile) def test_code_finished_will_parse(self): - self.repl.buffer = ['1 + 1'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) - self.repl.buffer = ['def foo(x):'] + self.repl.buffer = ['filter(', '', ''] self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (False, True)) - self.repl.buffer = ['def foo(x)'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) - self.repl.buffer = ['def foo(x):', 'return 1'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False)) - self.repl.buffer = ['def foo(x):', ' return 1'] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) - self.repl.buffer = ['def foo(x):', ' return 1', ''] - self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True)) def test_external_communication(self): self.repl.send_current_block_to_external_editor() diff --git a/requirements.txt b/requirements.txt index 78619ff24..ea915a790 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ greenlet requests setuptools six >=1.5 +yapypy \ No newline at end of file