diff --git a/joy/gui/init_joy_home.py b/joy/gui/init_joy_home.py index 90895eb..e62cdb6 100644 --- a/joy/gui/init_joy_home.py +++ b/joy/gui/init_joy_home.py @@ -16,17 +16,17 @@ import base64, os, io, zipfile def initialize(joy_home): - Z.extractall(joy_home) + Z.extractall(joy_home) def create_data(from_dir='./default_joy_home'): - f = io.StringIO() - z = zipfile.ZipFile(f, mode='w') - for fn in os.listdir(from_dir): - from_fn = os.path.join(from_dir, fn) - z.write(from_fn, fn) - z.close() - return base64.encodestring(f.getvalue()) + f = io.StringIO() + z = zipfile.ZipFile(f, mode='w') + for fn in os.listdir(from_dir): + from_fn = os.path.join(from_dir, fn) + z.write(from_fn, fn) + z.close() + return base64.encodestring(f.getvalue()) Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\ @@ -103,4 +103,4 @@ AAAAAAAAAAAAtIH2BgAAZGVmaW5pdGlvbnMudHh0UEsFBgAAAAAFAAUAHgEAAF0OAAAAAA=='''))) if __name__ == '__main__': - print(create_data()) + print(create_data()) diff --git a/joy/gui/main.py b/joy/gui/main.py index 0e75fd8..e50df05 100755 --- a/joy/gui/main.py +++ b/joy/gui/main.py @@ -23,10 +23,10 @@ repo = init_home(JOY_HOME) _log = logging.getLogger(__name__) logging.basicConfig( - format='%(asctime)-15s %(levelname)s %(name)s %(message)s', - filename=os.path.join(JOY_HOME, 'thun.log'), - level=logging.INFO, - ) + format='%(asctime)-15s %(levelname)s %(name)s %(message)s', + filename=os.path.join(JOY_HOME, 'thun.log'), + level=logging.INFO, + ) _log.info('Starting with JOY_HOME=%s', JOY_HOME) @@ -39,73 +39,73 @@ from joy.utils.stack import stack_to_string cp = RawConfigParser() cp.optionxform = str # Don't mess with uppercase. with open(os.path.join(args.joy_home, 'thun.config')) as f: - cp.readfp(f) + cp.readfp(f) GLOBAL_COMMANDS = dict(cp.items('key bindings')) def repo_relative_path(path): - return os.path.relpath( - path, - os.path.commonprefix((repo.controldir(), path)) - ) + return os.path.relpath( + path, + os.path.commonprefix((repo.controldir(), path)) + ) def commands(): - # pylint: disable=unused-variable + # pylint: disable=unused-variable - def key_bindings(*args): - commands = [ # These are bound in the TextViewerWidget. - 'Control-Enter - Run the selection as Joy code, or if there\'s no selection the line containing the cursor.', - 'F3 - Copy selection to stack.', - 'Shift-F3 - Cut selection to stack.', - 'F4 - Paste item on top of stack to insertion cursor.', - 'Shift-F4 - Pop and paste top of stack to insertion cursor.', - ] - for key, command in GLOBAL_COMMANDS.items(): - commands.append('%s - %s' % (key.lstrip('<').rstrip('>'), command)) - print('\n'.join([''] + sorted(commands))) - return args + def key_bindings(*args): + commands = [ # These are bound in the TextViewerWidget. + 'Control-Enter - Run the selection as Joy code, or if there\'s no selection the line containing the cursor.', + 'F3 - Copy selection to stack.', + 'Shift-F3 - Cut selection to stack.', + 'F4 - Paste item on top of stack to insertion cursor.', + 'Shift-F4 - Pop and paste top of stack to insertion cursor.', + ] + for key, command in GLOBAL_COMMANDS.items(): + commands.append('%s - %s' % (key.lstrip('<').rstrip('>'), command)) + print('\n'.join([''] + sorted(commands))) + return args - def mouse_bindings(*args): - print(dedent(''' - Mouse button chords (to cancel a chord, click the third mouse button.) + def mouse_bindings(*args): + print(dedent(''' + Mouse button chords (to cancel a chord, click the third mouse button.) - Left - Point, sweep selection - Left-Middle - Copy the selection, place text on stack - Left-Right - Run the selection as Joy code + Left - Point, sweep selection + Left-Middle - Copy the selection, place text on stack + Left-Right - Run the selection as Joy code - Middle - Paste selection (bypass stack); click and drag to scroll. - Middle-Left - Paste from top of stack, preserve - Middle-Right - Paste from top of stack, pop + Middle - Paste selection (bypass stack); click and drag to scroll. + Middle-Left - Paste from top of stack, preserve + Middle-Right - Paste from top of stack, pop - Right - Execute command word under mouse cursor - Right-Left - Print docs of command word under mouse cursor - Right-Middle - Lookup word (kinda useless now) - ''')) - return args + Right - Execute command word under mouse cursor + Right-Left - Print docs of command word under mouse cursor + Right-Middle - Lookup word (kinda useless now) + ''')) + return args - def reset_log(*args): - log.delete('0.0', tk.END) - print(__doc__) - return args + def reset_log(*args): + log.delete('0.0', tk.END) + print(__doc__) + return args - def show_log(*args): - log_window.wm_deiconify() - log_window.update() - return args + def show_log(*args): + log_window.wm_deiconify() + log_window.update() + return args - def grand_reset(s, e, d): - stack = world.load_stack() or () - log.reset() - t.reset() - return stack, e, d + def grand_reset(s, e, d): + stack = world.load_stack() or () + log.reset() + t.reset() + return stack, e, d - return locals() + return locals() STACK_FN = os.path.join(JOY_HOME, 'stack.pickle') @@ -125,19 +125,19 @@ FONT = get_font('Iosevka', size=14) # Requires Tk root already set up. log.init('Log', LOG_FN, repo_relative_path(LOG_FN), repo, FONT) t.init('Joy - ' + JOY_HOME, JOY_FN, repo_relative_path(JOY_FN), repo, FONT) for event, command in GLOBAL_COMMANDS.items(): - callback = lambda _, _command=command: world.interpret(_command) - t.bind(event, callback) - log.bind(event, callback) + callback = lambda _, _command=command: world.interpret(_command) + t.bind(event, callback) + log.bind(event, callback) def main(): - sys.stdout, old_stdout = FileFaker(log), sys.stdout - try: - t.mainloop() - finally: - sys.stdout = old_stdout - return 0 + sys.stdout, old_stdout = FileFaker(log), sys.stdout + try: + t.mainloop() + finally: + sys.stdout = old_stdout + return 0 if __name__ == '__main__': - main() + main() diff --git a/joy/gui/mousebindings.py b/joy/gui/mousebindings.py index 2b5021a..2c43cd1 100644 --- a/joy/gui/mousebindings.py +++ b/joy/gui/mousebindings.py @@ -25,187 +25,187 @@ nothing = lambda event: None class MouseBindingsMixin(object): - """TextViewerWidget mixin class to provide mouse bindings.""" + """TextViewerWidget mixin class to provide mouse bindings.""" - def __init__(self): + def __init__(self): - #Remember our mouse button state - self.B1_DOWN = False - self.B2_DOWN = False - self.B3_DOWN = False + #Remember our mouse button state + self.B1_DOWN = False + self.B2_DOWN = False + self.B3_DOWN = False - #Remember our pending action. - self.dothis = nothing + #Remember our pending action. + self.dothis = nothing - #We'll need to remember whether or not we've been moving B2. - self.beenMovingB2 = False + #We'll need to remember whether or not we've been moving B2. + self.beenMovingB2 = False - #Unbind the events we're interested in. - for sequence in ( - "", "", "", - "", "", "", - "", "", "", - "", "", "", "", "" - ): - self.unbind(sequence) - self.unbind_all(sequence) + #Unbind the events we're interested in. + for sequence in ( + "", "", "", + "", "", "", + "", "", "", + "", "", "", "", "" + ): + self.unbind(sequence) + self.unbind_all(sequence) - self.event_delete('<>') #I forgot what this was for! :-P D'oh! + self.event_delete('<>') #I forgot what this was for! :-P D'oh! - #Bind our event handlers to their events. - self.bind("", self.B1d) - self.bind("", self.B1m) - self.bind("", self.B1r) + #Bind our event handlers to their events. + self.bind("", self.B1d) + self.bind("", self.B1m) + self.bind("", self.B1r) - self.bind("", self.B2d) - self.bind("", self.B2m) - self.bind("", self.B2r) + self.bind("", self.B2d) + self.bind("", self.B2m) + self.bind("", self.B2r) - self.bind("", self.B3d) - self.bind("", self.B3m) - self.bind("", self.B3r) + self.bind("", self.B3d) + self.bind("", self.B3m) + self.bind("", self.B3r) - self.bind("", self.leave) - self.bind("", self.scan_command) + self.bind("", self.leave) + self.bind("", self.scan_command) - def B1d(self, event): - '''button one pressed''' - self.B1_DOWN = True + def B1d(self, event): + '''button one pressed''' + self.B1_DOWN = True - if self.B2_DOWN: + if self.B2_DOWN: - self.unhighlight_command() + self.unhighlight_command() - if self.B3_DOWN : - self.dothis = self.cancel + if self.B3_DOWN : + self.dothis = self.cancel - else: - #copy TOS to the mouse (instead of system selection.) - self.dothis = self.copyto #middle-left-interclick + else: + #copy TOS to the mouse (instead of system selection.) + self.dothis = self.copyto #middle-left-interclick - elif self.B3_DOWN : - self.unhighlight_command() - self.dothis = self.opendoc #right-left-interclick + elif self.B3_DOWN : + self.unhighlight_command() + self.dothis = self.opendoc #right-left-interclick - else: - ##button 1 down, set insertion and begin selection. - ##Actually, do nothing. Tk Text widget defaults take care of it. - self.dothis = nothing - return + else: + ##button 1 down, set insertion and begin selection. + ##Actually, do nothing. Tk Text widget defaults take care of it. + self.dothis = nothing + return - #Prevent further event handling by returning "break". - return "break" + #Prevent further event handling by returning "break". + return "break" - def B2d(self, event): - '''button two pressed''' - self.B2_DOWN = 1 + def B2d(self, event): + '''button two pressed''' + self.B2_DOWN = 1 - if self.B1_DOWN : + if self.B1_DOWN : - if self.B3_DOWN : - self.dothis = self.cancel + if self.B3_DOWN : + self.dothis = self.cancel - else: - #left-middle-interclick - copy selection to stack - self.dothis = self.copy_selection_to_stack + else: + #left-middle-interclick - copy selection to stack + self.dothis = self.copy_selection_to_stack - elif self.B3_DOWN : - self.unhighlight_command() - self.dothis = self.lookup #right-middle-interclick - lookup + elif self.B3_DOWN : + self.unhighlight_command() + self.dothis = self.lookup #right-middle-interclick - lookup - else: - #middle-click - paste X selection to mouse pointer - self.set_insertion_point(event) - self.dothis = self.paste_X_selection_to_mouse_pointer - return + else: + #middle-click - paste X selection to mouse pointer + self.set_insertion_point(event) + self.dothis = self.paste_X_selection_to_mouse_pointer + return - return "break" + return "break" - def B3d(self, event): - '''button three pressed''' - self.B3_DOWN = 1 + def B3d(self, event): + '''button three pressed''' + self.B3_DOWN = 1 - if self.B1_DOWN : + if self.B1_DOWN : - if self.B2_DOWN : - self.dothis = self.cancel + if self.B2_DOWN : + self.dothis = self.cancel - else: - #left-right-interclick - run selection - self.dothis = self.run_selection + else: + #left-right-interclick - run selection + self.dothis = self.run_selection - elif self.B2_DOWN : - #middle-right-interclick - Pop/Cut from TOS to insertion cursor - self.unhighlight_command() - self.dothis = self.pastecut + elif self.B2_DOWN : + #middle-right-interclick - Pop/Cut from TOS to insertion cursor + self.unhighlight_command() + self.dothis = self.pastecut - else: - #right-click - self.CommandFirstDown(event) + else: + #right-click + self.CommandFirstDown(event) - return "break" + return "break" - def B1m(self, event): - '''button one moved''' - if self.B2_DOWN or self.B3_DOWN: - return "break" + def B1m(self, event): + '''button one moved''' + if self.B2_DOWN or self.B3_DOWN: + return "break" - def B2m(self, event): - '''button two moved''' - if self.dothis == self.paste_X_selection_to_mouse_pointer and \ - not (self.B1_DOWN or self.B3_DOWN): + def B2m(self, event): + '''button two moved''' + if self.dothis == self.paste_X_selection_to_mouse_pointer and \ + not (self.B1_DOWN or self.B3_DOWN): - self.beenMovingB2 = True - return + self.beenMovingB2 = True + return - return "break" + return "break" - def B3m(self, event): - '''button three moved''' - if self.dothis == self.do_command and \ - not (self.B1_DOWN or self.B2_DOWN): + def B3m(self, event): + '''button three moved''' + if self.dothis == self.do_command and \ + not (self.B1_DOWN or self.B2_DOWN): - self.update_command_word(event) + self.update_command_word(event) - return "break" + return "break" - def scan_command(self, event): - self.update_command_word(event) + def scan_command(self, event): + self.update_command_word(event) - def B1r(self, event): - '''button one released''' - self.B1_DOWN = False + def B1r(self, event): + '''button one released''' + self.B1_DOWN = False - if not (self.B2_DOWN or self.B3_DOWN): - self.dothis(event) + if not (self.B2_DOWN or self.B3_DOWN): + self.dothis(event) - return "break" + return "break" - def B2r(self, event): - '''button two released''' - self.B2_DOWN = False + def B2r(self, event): + '''button two released''' + self.B2_DOWN = False - if not (self.B1_DOWN or self.B3_DOWN or self.beenMovingB2): - self.dothis(event) + if not (self.B1_DOWN or self.B3_DOWN or self.beenMovingB2): + self.dothis(event) - self.beenMovingB2 = False + self.beenMovingB2 = False - return "break" + return "break" - def B3r(self, event): - '''button three released''' - self.B3_DOWN = False + def B3r(self, event): + '''button three released''' + self.B3_DOWN = False - if not (self.B1_DOWN or self.B2_DOWN) : - self.dothis(event) + if not (self.B1_DOWN or self.B2_DOWN) : + self.dothis(event) - return "break" + return "break" - def InsertFirstDown(self, event): - self.focus() - self.dothis = nothing - self.set_insertion_point(event) + def InsertFirstDown(self, event): + self.focus() + self.dothis = nothing + self.set_insertion_point(event) - def CommandFirstDown(self, event): - self.dothis = self.do_command - self.update_command_word(event) + def CommandFirstDown(self, event): + self.dothis = self.do_command + self.update_command_word(event) diff --git a/joy/gui/textwidget.py b/joy/gui/textwidget.py index c8cbd40..754fbd7 100644 --- a/joy/gui/textwidget.py +++ b/joy/gui/textwidget.py @@ -25,12 +25,12 @@ A Graphical User Interface for a dialect of Joy in Python. The GUI - History - Structure - Commands - Mouse Chords - Keyboard - Output from Joy + History + Structure + Commands + Mouse Chords + Keyboard + Output from Joy ''' @@ -40,11 +40,11 @@ standard_library.install_aliases() from builtins import str, map, object from past.builtins import basestring try: - import tkinter as tk - from tkinter.font import families, Font + import tkinter as tk + from tkinter.font import families, Font except ImportError: - import Tkinter as tk - from tkFont import families, Font + import Tkinter as tk + from tkFont import families, Font from re import compile as regular_expression from traceback import format_exc @@ -58,17 +58,17 @@ from .world import World def make_gui(dictionary): - t = TextViewerWidget(World(dictionary=dictionary)) - t['font'] = get_font() - t._root().title('Joy') - t.pack(expand=True, fill=tk.BOTH) - return t + t = TextViewerWidget(World(dictionary=dictionary)) + t['font'] = get_font() + t._root().title('Joy') + t.pack(expand=True, fill=tk.BOTH) + return t def get_font(family='EB Garamond', size=14): - if family not in families(): - family = 'Times' - return Font(family=family, size=size) + if family not in families(): + family = 'Times' + return Font(family=family, size=size) #: Define mapping between Tkinter events and functions or methods. The @@ -77,85 +77,85 @@ def get_font(family='EB Garamond', size=14): #: must return the actual callable to which to bind the event sequence. TEXT_BINDINGS = { - #I want to ensure that these keyboard shortcuts work. - '': lambda tv: tv._control_enter, - '': lambda tv: tv._paste, - '': lambda tv: tv._paste, - '': lambda tv: tv.copy_selection_to_stack, - '': lambda tv: tv.copyto, - '': lambda tv: tv.cut, - '': lambda tv: tv.pastecut, - '': lambda tv: tv._paste, - } + #I want to ensure that these keyboard shortcuts work. + '': lambda tv: tv._control_enter, + '': lambda tv: tv._paste, + '': lambda tv: tv._paste, + '': lambda tv: tv.copy_selection_to_stack, + '': lambda tv: tv.copyto, + '': lambda tv: tv.cut, + '': lambda tv: tv.pastecut, + '': lambda tv: tv._paste, + } class SavingMixin(object): - def __init__(self, saver=None, filename=None, save_delay=2000): - self.saver = self._saver if saver is None else saver - self.filename = filename - self._save_delay = save_delay - self.tk.call(self._w, 'edit', 'modified', 0) - self.bind('<>', self._beenModified) - self._resetting_modified_flag = False - self._save = None + def __init__(self, saver=None, filename=None, save_delay=2000): + self.saver = self._saver if saver is None else saver + self.filename = filename + self._save_delay = save_delay + self.tk.call(self._w, 'edit', 'modified', 0) + self.bind('<>', self._beenModified) + self._resetting_modified_flag = False + self._save = None - def save(self): - ''' - Call _saveFunc() after a certain amount of idle time. + def save(self): + ''' + Call _saveFunc() after a certain amount of idle time. - Called by _beenModified(). - ''' - self._cancelSave() - if self.saver: - self._saveAfter(self._save_delay) + Called by _beenModified(). + ''' + self._cancelSave() + if self.saver: + self._saveAfter(self._save_delay) - def _saveAfter(self, delay): - ''' - Trigger a cancel-able call to _saveFunc() after delay milliseconds. - ''' - self._save = self.after(delay, self._saveFunc) + def _saveAfter(self, delay): + ''' + Trigger a cancel-able call to _saveFunc() after delay milliseconds. + ''' + self._save = self.after(delay, self._saveFunc) - def _saveFunc(self): - self._save = None - self.saver(self._get_contents()) + def _saveFunc(self): + self._save = None + self.saver(self._get_contents()) - def _saver(self, text): - if not self.filename: - return - with open(self.filename, 'wb') as f: - os.chmod(self.filename, 0o600) - f.write(text.encode('UTF_8')) - f.flush() - os.fsync(f.fileno()) - if hasattr(self, 'repo'): - self.repo.stage([self.repo_relative_filename]) - self.world.save() + def _saver(self, text): + if not self.filename: + return + with open(self.filename, 'wb') as f: + os.chmod(self.filename, 0o600) + f.write(text.encode('UTF_8')) + f.flush() + os.fsync(f.fileno()) + if hasattr(self, 'repo'): + self.repo.stage([self.repo_relative_filename]) + self.world.save() - def _cancelSave(self): - if self._save is not None: - self.after_cancel(self._save) - self._save = None + def _cancelSave(self): + if self._save is not None: + self.after_cancel(self._save) + self._save = None - def _get_contents(self): - self['state'] = 'disabled' - try: - return self.get('0.0', 'end')[:-1] - finally: - self['state'] = 'normal' + def _get_contents(self): + self['state'] = 'disabled' + try: + return self.get('0.0', 'end')[:-1] + finally: + self['state'] = 'normal' - def _beenModified(self, event): - if self._resetting_modified_flag: - return - self._clearModifiedFlag() - self.save() + def _beenModified(self, event): + if self._resetting_modified_flag: + return + self._clearModifiedFlag() + self.save() - def _clearModifiedFlag(self): - self._resetting_modified_flag = True - try: - self.tk.call(self._w, 'edit', 'modified', 0) - finally: - self._resetting_modified_flag = False + def _clearModifiedFlag(self): + self._resetting_modified_flag = True + try: + self.tk.call(self._w, 'edit', 'modified', 0) + finally: + self._resetting_modified_flag = False ## tags = self._saveTags() ## chunks = self.DUMP() @@ -163,309 +163,309 @@ class SavingMixin(object): class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin): - """ - This class is a Tkinter Text with special mousebindings to make - it act as a Xerblin Text Viewer. - """ + """ + This class is a Tkinter Text with special mousebindings to make + it act as a Xerblin Text Viewer. + """ - #This is a regular expression for finding commands in the text. - command_re = regular_expression(r'[-a-zA-Z0-9_\\~/.:!@#$%&*?=+<>]+') + #This is a regular expression for finding commands in the text. + command_re = regular_expression(r'[-a-zA-Z0-9_\\~/.:!@#$%&*?=+<>]+') - #These are the config tags for command text when it's highlighted. - command_tags = dict( - #underline = 1, - #bgstipple = "gray50", - borderwidth = 2, - relief=tk.RIDGE, - foreground = "green" - ) + #These are the config tags for command text when it's highlighted. + command_tags = dict( + #underline = 1, + #bgstipple = "gray50", + borderwidth = 2, + relief=tk.RIDGE, + foreground = "green" + ) - def __init__(self, world, master=None, **kw): + def __init__(self, world, master=None, **kw): - self.world = world - if self.world.text_widget is None: - self.world.text_widget = self + self.world = world + if self.world.text_widget is None: + self.world.text_widget = self - #Turn on undo, but don't override a passed-in setting. - kw.setdefault('undo', True) + #Turn on undo, but don't override a passed-in setting. + kw.setdefault('undo', True) # kw.setdefault('bg', 'white') - kw.setdefault('wrap', 'word') - kw.setdefault('font', 'arial 12') + kw.setdefault('wrap', 'word') + kw.setdefault('font', 'arial 12') - text_bindings = kw.pop('text_bindings', TEXT_BINDINGS) + text_bindings = kw.pop('text_bindings', TEXT_BINDINGS) - #Create ourselves as a Tkinter Text - tk.Text.__init__(self, master, **kw) + #Create ourselves as a Tkinter Text + tk.Text.__init__(self, master, **kw) - #Initialize our mouse mixin. - MouseBindingsMixin.__init__(self) + #Initialize our mouse mixin. + MouseBindingsMixin.__init__(self) - #Initialize our saver mixin. - SavingMixin.__init__(self) + #Initialize our saver mixin. + SavingMixin.__init__(self) - #Add tag config for command highlighting. - self.tag_config('command', **self.command_tags) - self.tag_config('bzzt', foreground = "orange") - self.tag_config('huh', foreground = "grey") - self.tag_config('number', foreground = "blue") + #Add tag config for command highlighting. + self.tag_config('command', **self.command_tags) + self.tag_config('bzzt', foreground = "orange") + self.tag_config('huh', foreground = "grey") + self.tag_config('number', foreground = "blue") - #Create us a command instance variable - self.command = '' + #Create us a command instance variable + self.command = '' - #Activate event bindings. Modify text_bindings in your config - #file to affect the key bindings and whatnot here. - for event_sequence, callback_finder in text_bindings.items(): - callback = callback_finder(self) - self.bind(event_sequence, callback) + #Activate event bindings. Modify text_bindings in your config + #file to affect the key bindings and whatnot here. + for event_sequence, callback_finder in text_bindings.items(): + callback = callback_finder(self) + self.bind(event_sequence, callback) ## T.protocol("WM_DELETE_WINDOW", self.on_close) - def find_command_in_line(self, line, index): - ''' - Return the command at index in line and its begin and end indices. - find_command_in_line(line, index) => command, begin, end - ''' - for match in self.command_re.finditer(line): - b, e = match.span() - if b <= index <= e: - return match.group(), b, e + def find_command_in_line(self, line, index): + ''' + Return the command at index in line and its begin and end indices. + find_command_in_line(line, index) => command, begin, end + ''' + for match in self.command_re.finditer(line): + b, e = match.span() + if b <= index <= e: + return match.group(), b, e - def paste_X_selection_to_mouse_pointer(self, event): - '''Paste the X selection to the mouse pointer.''' - try: - text = self.selection_get() - except tk.TclError: - return 'break' - self.insert_it(text) + def paste_X_selection_to_mouse_pointer(self, event): + '''Paste the X selection to the mouse pointer.''' + try: + text = self.selection_get() + except tk.TclError: + return 'break' + self.insert_it(text) - def update_command_word(self, event): - '''Highlight the command under the mouse.''' - self.unhighlight_command() - self.command = '' - index = '@%d,%d' % (event.x, event.y) - linestart = self.index(index + 'linestart') - lineend = self.index(index + 'lineend') - line = self.get(linestart, lineend) - row, offset = self._get_index(index) + def update_command_word(self, event): + '''Highlight the command under the mouse.''' + self.unhighlight_command() + self.command = '' + index = '@%d,%d' % (event.x, event.y) + linestart = self.index(index + 'linestart') + lineend = self.index(index + 'lineend') + line = self.get(linestart, lineend) + row, offset = self._get_index(index) - if offset >= len(line) or line[offset].isspace(): - # The mouse is off the end of the line or on a space so there's no - # command, we're done. - return + if offset >= len(line) or line[offset].isspace(): + # The mouse is off the end of the line or on a space so there's no + # command, we're done. + return - cmd = self.find_command_in_line(line, offset) - if cmd is None: - return + cmd = self.find_command_in_line(line, offset) + if cmd is None: + return - cmd, b, e = cmd - if is_numerical(cmd): - extra_tags = 'number', - elif self.world.has(cmd): - check = self.world.check(cmd) - if check: extra_tags = () - elif check is None: extra_tags = 'huh', - else: extra_tags = 'bzzt', - else: - return - self.command = cmd - self.highlight_command( - '%d.%d' % (row, b), - '%d.%d' % (row, e), - *extra_tags) + cmd, b, e = cmd + if is_numerical(cmd): + extra_tags = 'number', + elif self.world.has(cmd): + check = self.world.check(cmd) + if check: extra_tags = () + elif check is None: extra_tags = 'huh', + else: extra_tags = 'bzzt', + else: + return + self.command = cmd + self.highlight_command( + '%d.%d' % (row, b), + '%d.%d' % (row, e), + *extra_tags) - def highlight_command(self, from_, to, *extra_tags): - '''Apply command style from from_ to to.''' - cmdstart = self.index(from_) - cmdend = self.index(to) - self.tag_add('command', cmdstart, cmdend) - for tag in extra_tags: - self.tag_add(tag, cmdstart, cmdend) + def highlight_command(self, from_, to, *extra_tags): + '''Apply command style from from_ to to.''' + cmdstart = self.index(from_) + cmdend = self.index(to) + self.tag_add('command', cmdstart, cmdend) + for tag in extra_tags: + self.tag_add(tag, cmdstart, cmdend) - def do_command(self, event): - '''Do the currently highlighted command.''' - self.unhighlight_command() - if self.command: - self.run_command(self.command) + def do_command(self, event): + '''Do the currently highlighted command.''' + self.unhighlight_command() + if self.command: + self.run_command(self.command) - def _control_enter(self, event): - select_indices = self.tag_ranges(tk.SEL) - if select_indices: - command = self.get(select_indices[0], select_indices[1]) - else: - linestart = self.index(tk.INSERT + ' linestart') - lineend = self.index(tk.INSERT + ' lineend') - command = self.get(linestart, lineend) - if command and not command.isspace(): - self.run_command(command) - return 'break' + def _control_enter(self, event): + select_indices = self.tag_ranges(tk.SEL) + if select_indices: + command = self.get(select_indices[0], select_indices[1]) + else: + linestart = self.index(tk.INSERT + ' linestart') + lineend = self.index(tk.INSERT + ' lineend') + command = self.get(linestart, lineend) + if command and not command.isspace(): + self.run_command(command) + return 'break' - def run_command(self, command): - '''Given a string run it on the stack, report errors.''' - try: - self.world.interpret(command) - except SystemExit: - raise - except: - self.popupTB(format_exc().rstrip()) + def run_command(self, command): + '''Given a string run it on the stack, report errors.''' + try: + self.world.interpret(command) + except SystemExit: + raise + except: + self.popupTB(format_exc().rstrip()) - def unhighlight_command(self): - '''Remove any command highlighting.''' - self.tag_remove('number', 1.0, tk.END) - self.tag_remove('huh', 1.0, tk.END) - self.tag_remove('bzzt', 1.0, tk.END) - self.tag_remove('command', 1.0, tk.END) + def unhighlight_command(self): + '''Remove any command highlighting.''' + self.tag_remove('number', 1.0, tk.END) + self.tag_remove('huh', 1.0, tk.END) + self.tag_remove('bzzt', 1.0, tk.END) + self.tag_remove('command', 1.0, tk.END) - def set_insertion_point(self, event): - '''Set the insertion cursor to the current mouse location.''' - self.focus() - self.mark_set(tk.INSERT, '@%d,%d' % (event.x, event.y)) + def set_insertion_point(self, event): + '''Set the insertion cursor to the current mouse location.''' + self.focus() + self.mark_set(tk.INSERT, '@%d,%d' % (event.x, event.y)) - def copy_selection_to_stack(self, event): - '''Copy selection to stack.''' - select_indices = self.tag_ranges(tk.SEL) - if select_indices: - s = self.get(select_indices[0], select_indices[1]) - self.world.push(s) + def copy_selection_to_stack(self, event): + '''Copy selection to stack.''' + select_indices = self.tag_ranges(tk.SEL) + if select_indices: + s = self.get(select_indices[0], select_indices[1]) + self.world.push(s) - def cut(self, event): - '''Cut selection to stack.''' - self.copy_selection_to_stack(event) - # Let the pre-existing machinery take care of cutting the selection. - self.event_generate("<>") + def cut(self, event): + '''Cut selection to stack.''' + self.copy_selection_to_stack(event) + # Let the pre-existing machinery take care of cutting the selection. + self.event_generate("<>") - def copyto(self, event): - '''Actually "paste" from TOS''' - s = self.world.peek() - if s is not None: - self.insert_it(s) + def copyto(self, event): + '''Actually "paste" from TOS''' + s = self.world.peek() + if s is not None: + self.insert_it(s) - def insert_it(self, s): - if not isinstance(s, basestring): - s = stack_to_string(s) + def insert_it(self, s): + if not isinstance(s, basestring): + s = stack_to_string(s) - # When pasting from the mouse we have to remove the current selection - # to prevent destroying it by the paste operation. - select_indices = self.tag_ranges(tk.SEL) - if select_indices: - # Set two marks to remember the selection. - self.mark_set('_sel_start', select_indices[0]) - self.mark_set('_sel_end', select_indices[1]) - self.tag_remove(tk.SEL, 1.0, tk.END) + # When pasting from the mouse we have to remove the current selection + # to prevent destroying it by the paste operation. + select_indices = self.tag_ranges(tk.SEL) + if select_indices: + # Set two marks to remember the selection. + self.mark_set('_sel_start', select_indices[0]) + self.mark_set('_sel_end', select_indices[1]) + self.tag_remove(tk.SEL, 1.0, tk.END) - self.insert(tk.INSERT, s) + self.insert(tk.INSERT, s) - if select_indices: - self.tag_add(tk.SEL, '_sel_start', '_sel_end') - self.mark_unset('_sel_start') - self.mark_unset('_sel_end') + if select_indices: + self.tag_add(tk.SEL, '_sel_start', '_sel_end') + self.mark_unset('_sel_start') + self.mark_unset('_sel_end') - def run_selection(self, event): - '''Run the current selection if any on the stack.''' - select_indices = self.tag_ranges(tk.SEL) - if select_indices: - selection = self.get(select_indices[0], select_indices[1]) - self.tag_remove(tk.SEL, 1.0, tk.END) - self.run_command(selection) + def run_selection(self, event): + '''Run the current selection if any on the stack.''' + select_indices = self.tag_ranges(tk.SEL) + if select_indices: + selection = self.get(select_indices[0], select_indices[1]) + self.tag_remove(tk.SEL, 1.0, tk.END) + self.run_command(selection) - def pastecut(self, event): - '''Cut the TOS item to the mouse.''' - self.copyto(event) - self.world.pop() + def pastecut(self, event): + '''Cut the TOS item to the mouse.''' + self.copyto(event) + self.world.pop() - def opendoc(self, event): - '''OpenDoc the current command.''' - if self.command: - self.world.do_opendoc(self.command) + def opendoc(self, event): + '''OpenDoc the current command.''' + if self.command: + self.world.do_opendoc(self.command) - def lookup(self, event): - '''Look up the current command.''' - if self.command: - self.world.do_lookup(self.command) + def lookup(self, event): + '''Look up the current command.''' + if self.command: + self.world.do_lookup(self.command) - def cancel(self, event): - '''Cancel whatever we're doing.''' - self.leave(None) - self.tag_remove(tk.SEL, 1.0, tk.END) - self._sel_anchor = '0.0' - self.mark_unset(tk.INSERT) + def cancel(self, event): + '''Cancel whatever we're doing.''' + self.leave(None) + self.tag_remove(tk.SEL, 1.0, tk.END) + self._sel_anchor = '0.0' + self.mark_unset(tk.INSERT) - def leave(self, event): - '''Called when mouse leaves the Text window.''' - self.unhighlight_command() - self.command = '' + def leave(self, event): + '''Called when mouse leaves the Text window.''' + self.unhighlight_command() + self.command = '' - def _get_index(self, index): - '''Get the index in (int, int) form of index.''' - return tuple(map(int, self.index(index).split('.'))) + def _get_index(self, index): + '''Get the index in (int, int) form of index.''' + return tuple(map(int, self.index(index).split('.'))) - def _paste(self, event): - '''Paste the system selection to the current selection, replacing it.''' + def _paste(self, event): + '''Paste the system selection to the current selection, replacing it.''' - # If we're "key" pasting, we have to move the insertion point - # to the selection so the pasted text gets inserted at the - # location of the deleted selection. + # If we're "key" pasting, we have to move the insertion point + # to the selection so the pasted text gets inserted at the + # location of the deleted selection. - select_indices = self.tag_ranges(tk.SEL) - if select_indices: - # Mark the location of the current insertion cursor - self.mark_set('tmark', tk.INSERT) - # Put the insertion cursor at the selection - self.mark_set(tk.INSERT, select_indices[1]) + select_indices = self.tag_ranges(tk.SEL) + if select_indices: + # Mark the location of the current insertion cursor + self.mark_set('tmark', tk.INSERT) + # Put the insertion cursor at the selection + self.mark_set(tk.INSERT, select_indices[1]) - # Paste to the current selection, or if none, to the insertion cursor. - self.event_generate("<>") + # Paste to the current selection, or if none, to the insertion cursor. + self.event_generate("<>") - # If we mess with the insertion cursor above, fix it now. - if select_indices: - # Put the insertion cursor back where it was. - self.mark_set(tk.INSERT, 'tmark') - # And get rid of our unneeded mark. - self.mark_unset('tmark') + # If we mess with the insertion cursor above, fix it now. + if select_indices: + # Put the insertion cursor back where it was. + self.mark_set(tk.INSERT, 'tmark') + # And get rid of our unneeded mark. + self.mark_unset('tmark') - return 'break' + return 'break' - def init(self, title, filename, repo_relative_filename, repo, font): - self.winfo_toplevel().title(title) - if os.path.exists(filename): - with open(filename) as f: - data = f.read() - self.insert(tk.END, data) - # Prevent this from triggering a git commit. - self.update() - self._cancelSave() - self.pack(expand=True, fill=tk.BOTH) - self.filename = filename - self.repo_relative_filename = repo_relative_filename - self.repo = repo - self['font'] = font # See below. + def init(self, title, filename, repo_relative_filename, repo, font): + self.winfo_toplevel().title(title) + if os.path.exists(filename): + with open(filename) as f: + data = f.read() + self.insert(tk.END, data) + # Prevent this from triggering a git commit. + self.update() + self._cancelSave() + self.pack(expand=True, fill=tk.BOTH) + self.filename = filename + self.repo_relative_filename = repo_relative_filename + self.repo = repo + self['font'] = font # See below. - def reset(self): - if os.path.exists(self.filename): - with open(self.filename) as f: - data = f.read() - if data: - self.delete('0.0', tk.END) - self.insert(tk.END, data) + def reset(self): + if os.path.exists(self.filename): + with open(self.filename) as f: + data = f.read() + if data: + self.delete('0.0', tk.END) + self.insert(tk.END, data) - def popupTB(self, tb): - top = tk.Toplevel() - T = TextViewerWidget( - self.world, - top, - width=max(len(s) for s in tb.splitlines()) + 3, - ) + def popupTB(self, tb): + top = tk.Toplevel() + T = TextViewerWidget( + self.world, + top, + width=max(len(s) for s in tb.splitlines()) + 3, + ) - T['background'] = 'darkgrey' - T['foreground'] = 'darkblue' - T.tag_config('err', foreground='yellow') + T['background'] = 'darkgrey' + T['foreground'] = 'darkblue' + T.tag_config('err', foreground='yellow') - T.insert(tk.END, tb) - last_line = str(int(T.index(tk.END).split('.')[0]) - 1) + '.0' - T.tag_add('err', last_line, tk.END) - T['state'] = tk.DISABLED + T.insert(tk.END, tb) + last_line = str(int(T.index(tk.END).split('.')[0]) - 1) + '.0' + T.tag_add('err', last_line, tk.END) + T['state'] = tk.DISABLED - top.title(T.get(last_line, tk.END).strip()) + top.title(T.get(last_line, tk.END).strip()) - T.pack(expand=1, fill=tk.BOTH) - T.see(tk.END) + T.pack(expand=1, fill=tk.BOTH) + T.see(tk.END) diff --git a/joy/gui/utils.py b/joy/gui/utils.py index 3c80fc4..37a9b51 100644 --- a/joy/gui/utils.py +++ b/joy/gui/utils.py @@ -13,85 +13,85 @@ DEFAULT_JOY_HOME = expanduser(join('~', '.joypy')) def is_numerical(s): - try: - float(s) - except ValueError: - return False - return True + try: + float(s) + except ValueError: + return False + return True def home_dir(path): - '''Return the absolute path of an existing directory.''' + '''Return the absolute path of an existing directory.''' - fullpath = expanduser(path) if path.startswith('~') else abspath(path) + fullpath = expanduser(path) if path.startswith('~') else abspath(path) - if not exists(fullpath): - if path == DEFAULT_JOY_HOME: - print('Creating JOY_HOME', repr(fullpath)) - mkdir(fullpath, 0o700) - else: - print(repr(fullpath), "doesn't exist.", file=sys.stderr) - raise ValueError(path) + if not exists(fullpath): + if path == DEFAULT_JOY_HOME: + print('Creating JOY_HOME', repr(fullpath)) + mkdir(fullpath, 0o700) + else: + print(repr(fullpath), "doesn't exist.", file=sys.stderr) + raise ValueError(path) - return fullpath + return fullpath def init_home(fullpath): - ''' - Open or create the Repo. - If there are contents in the dir but it's not a git repo, quit. - ''' - try: - repo = Repo(fullpath) - except NotGitRepository: - print(repr(fullpath), "no repository", file=sys.stderr) + ''' + Open or create the Repo. + If there are contents in the dir but it's not a git repo, quit. + ''' + try: + repo = Repo(fullpath) + except NotGitRepository: + print(repr(fullpath), "no repository", file=sys.stderr) - if listdir(fullpath): - print(repr(fullpath), "has contents\nQUIT.", file=sys.stderr) - sys.exit(2) + if listdir(fullpath): + print(repr(fullpath), "has contents\nQUIT.", file=sys.stderr) + sys.exit(2) - print('Initializing repository in', fullpath) - repo = init_repo(fullpath) + print('Initializing repository in', fullpath) + repo = init_repo(fullpath) - print('Using repository in', fullpath) - return repo + print('Using repository in', fullpath) + return repo def init_repo(repo_dir): - ''' - Create a repo, load the initial content, and make the first commit. - Return the Repo object. - ''' - repo = Repo.init(repo_dir) - import joy.gui.init_joy_home - joy.gui.init_joy_home.initialize(repo_dir) - repo.stage([fn for fn in listdir(repo_dir) if isfile(join(repo_dir, fn))]) - repo.do_commit('Initial commit.', committer=COMMITTER) - return repo + ''' + Create a repo, load the initial content, and make the first commit. + Return the Repo object. + ''' + repo = Repo.init(repo_dir) + import joy.gui.init_joy_home + joy.gui.init_joy_home.initialize(repo_dir) + repo.stage([fn for fn in listdir(repo_dir) if isfile(join(repo_dir, fn))]) + repo.do_commit('Initial commit.', committer=COMMITTER) + return repo argparser = argparse.ArgumentParser( - description='Experimental Brutalist UI for Joy.', - ) + description='Experimental Brutalist UI for Joy.', + ) argparser.add_argument( - '-j', '--joy-home', - help='Use a directory other than %s as JOY_HOME' % DEFAULT_JOY_HOME, - default=DEFAULT_JOY_HOME, - dest='joy_home', - type=home_dir, - ) + '-j', '--joy-home', + help='Use a directory other than %s as JOY_HOME' % DEFAULT_JOY_HOME, + default=DEFAULT_JOY_HOME, + dest='joy_home', + type=home_dir, + ) class FileFaker(object): - def __init__(self, T): - self.T = T + def __init__(self, T): + self.T = T - def write(self, text): - self.T.insert('end', text) - self.T.see('end') + def write(self, text): + self.T.insert('end', text) + self.T.see('end') - def flush(self): - pass + def flush(self): + pass diff --git a/joy/gui/world.py b/joy/gui/world.py index 79ec1f2..e2f59d3 100644 --- a/joy/gui/world.py +++ b/joy/gui/world.py @@ -35,118 +35,118 @@ from .utils import is_numerical class World(object): - def __init__(self, stack=(), dictionary=None, text_widget=None): - self.stack = stack - self.dictionary = dictionary or {} - self.text_widget = text_widget - self.check_cache = {} + def __init__(self, stack=(), dictionary=None, text_widget=None): + self.stack = stack + self.dictionary = dictionary or {} + self.text_widget = text_widget + self.check_cache = {} - def check(self, name): - try: - res = self.check_cache[name] - except KeyError: - res = self.check_cache[name] = type_check(name, self.stack) - return res + def check(self, name): + try: + res = self.check_cache[name] + except KeyError: + res = self.check_cache[name] = type_check(name, self.stack) + return res - def do_lookup(self, name): - if name in self.dictionary: - self.stack = (Symbol(name), ()), self.stack - self.print_stack() - self.check_cache.clear() - else: - assert is_numerical(name) - self.interpret(name) + def do_lookup(self, name): + if name in self.dictionary: + self.stack = (Symbol(name), ()), self.stack + self.print_stack() + self.check_cache.clear() + else: + assert is_numerical(name) + self.interpret(name) - def do_opendoc(self, name): - if is_numerical(name): - print('The number', name) - else: - try: - word = self.dictionary[name] - except KeyError: - print(repr(name), '???') - else: - print(getdoc(word)) - self.print_stack() + def do_opendoc(self, name): + if is_numerical(name): + print('The number', name) + else: + try: + word = self.dictionary[name] + except KeyError: + print(repr(name), '???') + else: + print(getdoc(word)) + self.print_stack() - def pop(self): - if self.stack: - self.stack = self.stack[1] - self.print_stack() - self.check_cache.clear() + def pop(self): + if self.stack: + self.stack = self.stack[1] + self.print_stack() + self.check_cache.clear() - def push(self, it): - it = it.encode('utf8') - self.stack = it, self.stack - self.print_stack() - self.check_cache.clear() + def push(self, it): + it = it.encode('utf8') + self.stack = it, self.stack + self.print_stack() + self.check_cache.clear() - def peek(self): - if self.stack: - return self.stack[0] + def peek(self): + if self.stack: + return self.stack[0] - def interpret(self, command): - if self.has(command) and self.check(command) == False: # not in {True, None}: - return - old_stack = self.stack - try: - self.stack, _, self.dictionary = run( - command, - self.stack, - self.dictionary, - ) - finally: - self.print_stack() - if old_stack != self.stack: - self.check_cache.clear() + def interpret(self, command): + if self.has(command) and self.check(command) == False: # not in {True, None}: + return + old_stack = self.stack + try: + self.stack, _, self.dictionary = run( + command, + self.stack, + self.dictionary, + ) + finally: + self.print_stack() + if old_stack != self.stack: + self.check_cache.clear() - def has(self, name): - return name in self.dictionary + def has(self, name): + return name in self.dictionary - def save(self): - pass + def save(self): + pass - def print_stack(self): - stack_out_index = self.text_widget.search('<' 'STACK', 1.0) - if stack_out_index: - self.text_widget.see(stack_out_index) - s = stack_to_string(self.stack) + '\n' - self.text_widget.insert(stack_out_index, s) + def print_stack(self): + stack_out_index = self.text_widget.search('<' 'STACK', 1.0) + if stack_out_index: + self.text_widget.see(stack_out_index) + s = stack_to_string(self.stack) + '\n' + self.text_widget.insert(stack_out_index, s) class StackDisplayWorld(World): - def __init__(self, repo, filename, rel_filename, dictionary=None, text_widget=None): - self.filename = filename - stack = self.load_stack() or () - World.__init__(self, stack, dictionary, text_widget) - self.repo = repo - self.relative_STACK_FN = rel_filename + def __init__(self, repo, filename, rel_filename, dictionary=None, text_widget=None): + self.filename = filename + stack = self.load_stack() or () + World.__init__(self, stack, dictionary, text_widget) + self.repo = repo + self.relative_STACK_FN = rel_filename - def interpret(self, command): - command = command.strip() - if self.has(command) and self.check(command) == False: # not in {True, None}: - return - print('\njoy?', command) - super(StackDisplayWorld, self).interpret(command) + def interpret(self, command): + command = command.strip() + if self.has(command) and self.check(command) == False: # not in {True, None}: + return + print('\njoy?', command) + super(StackDisplayWorld, self).interpret(command) - def print_stack(self): - print('\n%s <-' % stack_to_string(self.stack)) + def print_stack(self): + print('\n%s <-' % stack_to_string(self.stack)) - def save(self): - with open(self.filename, 'wb') as f: - os.chmod(self.filename, 0o600) - pickle.dump(self.stack, f, protocol=2) - f.flush() - os.fsync(f.fileno()) - self.repo.stage([self.relative_STACK_FN]) - commit_id = self.repo.do_commit( - b'auto-save', - committer=b'thun-auto-save ', - ) - _log.info('commit %s', commit_id) + def save(self): + with open(self.filename, 'wb') as f: + os.chmod(self.filename, 0o600) + pickle.dump(self.stack, f, protocol=2) + f.flush() + os.fsync(f.fileno()) + self.repo.stage([self.relative_STACK_FN]) + commit_id = self.repo.do_commit( + b'auto-save', + committer=b'thun-auto-save ', + ) + _log.info('commit %s', commit_id) - def load_stack(self): - if os.path.exists(self.filename): - with open(self.filename, 'rb') as f: - return pickle.load(f) + def load_stack(self): + if os.path.exists(self.filename): + with open(self.filename, 'rb') as f: + return pickle.load(f) diff --git a/joy/joy.py b/joy/joy.py index 1ee73d3..684a305 100644 --- a/joy/joy.py +++ b/joy/joy.py @@ -32,86 +32,86 @@ from .utils.pretty_print import TracePrinter def joy(stack, expression, dictionary, viewer=None): - '''Evaluate a Joy expression on a stack. + '''Evaluate a Joy expression on a stack. - This function iterates through a sequence of terms which are either - literals (strings, numbers, sequences of terms) or function symbols. - Literals are put onto the stack and functions are looked up in the - disctionary and executed. + This function iterates through a sequence of terms which are either + literals (strings, numbers, sequences of terms) or function symbols. + Literals are put onto the stack and functions are looked up in the + disctionary and executed. - The viewer is a function that is called with the stack and expression - on every iteration, its return value is ignored. + The viewer is a function that is called with the stack and expression + on every iteration, its return value is ignored. - :param stack stack: The stack. - :param stack expression: The expression to evaluate. - :param dict dictionary: A ``dict`` mapping names to Joy functions. - :param function viewer: Optional viewer function. - :rtype: (stack, (), dictionary) + :param stack stack: The stack. + :param stack expression: The expression to evaluate. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :param function viewer: Optional viewer function. + :rtype: (stack, (), dictionary) - ''' - while expression: + ''' + while expression: - if viewer: viewer(stack, expression) + if viewer: viewer(stack, expression) - term, expression = expression - if isinstance(term, Symbol): - term = dictionary[term] - stack, expression, dictionary = term(stack, expression, dictionary) - else: - stack = term, stack + term, expression = expression + if isinstance(term, Symbol): + term = dictionary[term] + stack, expression, dictionary = term(stack, expression, dictionary) + else: + stack = term, stack - if viewer: viewer(stack, expression) - return stack, expression, dictionary + if viewer: viewer(stack, expression) + return stack, expression, dictionary def run(text, stack, dictionary, viewer=None): - ''' - Return the stack resulting from running the Joy code text on the stack. + ''' + Return the stack resulting from running the Joy code text on the stack. - :param str text: Joy code. - :param stack stack: The stack. - :param dict dictionary: A ``dict`` mapping names to Joy functions. - :param function viewer: Optional viewer function. - :rtype: (stack, (), dictionary) + :param str text: Joy code. + :param stack stack: The stack. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :param function viewer: Optional viewer function. + :rtype: (stack, (), dictionary) - ''' - expression = text_to_expression(text) - return joy(stack, expression, dictionary, viewer) + ''' + expression = text_to_expression(text) + return joy(stack, expression, dictionary, viewer) def repl(stack=(), dictionary=None): - ''' - Read-Evaluate-Print Loop + ''' + Read-Evaluate-Print Loop - Accept input and run it on the stack, loop. + Accept input and run it on the stack, loop. - :param stack stack: The stack. - :param dict dictionary: A ``dict`` mapping names to Joy functions. - :rtype: stack + :param stack stack: The stack. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :rtype: stack - ''' - if dictionary is None: - dictionary = {} - try: - while True: - print() - print(stack_to_string(stack), '<-top') - print() - try: - text = input('joy? ') - except (EOFError, KeyboardInterrupt): - break - viewer = TracePrinter() - try: - stack, _, dictionary = run(text, stack, dictionary, viewer.viewer) - except: - exc = format_exc() # Capture the exception. - viewer.print_() # Print the Joy trace. - print('-' * 73) - print(exc) # Print the original exception. - else: - viewer.print_() - except: - print_exc() - print() - return stack + ''' + if dictionary is None: + dictionary = {} + try: + while True: + print() + print(stack_to_string(stack), '<-top') + print() + try: + text = input('joy? ') + except (EOFError, KeyboardInterrupt): + break + viewer = TracePrinter() + try: + stack, _, dictionary = run(text, stack, dictionary, viewer.viewer) + except: + exc = format_exc() # Capture the exception. + viewer.print_() # Print the Joy trace. + print('-' * 73) + print(exc) # Print the original exception. + else: + viewer.print_() + except: + print_exc() + print() + return stack diff --git a/joy/library.py b/joy/library.py index a0ee19d..91e5edf 100644 --- a/joy/library.py +++ b/joy/library.py @@ -40,36 +40,36 @@ from .parser import text_to_expression, Symbol from .utils.stack import expression_to_string, list_to_stack, iter_stack, pick, concat import sys if sys.version_info.major < 3: - from .utils.brutal_hackery import rename_code_object + from .utils.brutal_hackery import rename_code_object else: - rename_code_object = lambda _: lambda f: f + rename_code_object = lambda _: lambda f: f from .utils import generated_library as genlib from .utils.types import ( - compose, - ef, - stack_effect, - AnyJoyType, - AnyStarJoyType, - BooleanJoyType, - NumberJoyType, - NumberStarJoyType, - StackJoyType, - StackStarJoyType, - FloatJoyType, - IntJoyType, - SymbolJoyType, - CombinatorJoyType, - TextJoyType, - _functions, - FUNCTIONS, - infer, - infer_expression, - JoyTypeError, - combinator_effect, - poly_combinator_effect, - doc_from_stack_effect, - ) + compose, + ef, + stack_effect, + AnyJoyType, + AnyStarJoyType, + BooleanJoyType, + NumberJoyType, + NumberStarJoyType, + StackJoyType, + StackStarJoyType, + FloatJoyType, + IntJoyType, + SymbolJoyType, + CombinatorJoyType, + TextJoyType, + _functions, + FUNCTIONS, + infer, + infer_expression, + JoyTypeError, + combinator_effect, + poly_combinator_effect, + doc_from_stack_effect, + ) _SYM_NUMS = lambda c=count(): next(c) @@ -107,108 +107,108 @@ _dictionary = {} def inscribe(function): - '''A decorator to inscribe functions into the default dictionary.''' - _dictionary[function.name] = function - return function + '''A decorator to inscribe functions into the default dictionary.''' + _dictionary[function.name] = function + return function def initialize(): - '''Return a dictionary of Joy functions for use with joy().''' - return _dictionary.copy() + '''Return a dictionary of Joy functions for use with joy().''' + return _dictionary.copy() ALIASES = ( - ('add', ['+']), - ('and', ['&']), - ('bool', ['truthy']), - ('mul', ['*']), - ('floordiv', ['/floor', '//']), - ('floor', ['round']), - ('truediv', ['/']), - ('mod', ['%', 'rem', 'remainder', 'modulus']), - ('eq', ['=']), - ('ge', ['>=']), - ('getitem', ['pick', 'at']), - ('gt', ['>']), - ('le', ['<=']), - ('lshift', ['<<']), - ('lt', ['<']), - ('ne', ['<>', '!=']), - ('rshift', ['>>']), - ('sub', ['-']), - ('xor', ['^']), - ('succ', ['++']), - ('pred', ['--']), - ('rolldown', ['roll<']), - ('rollup', ['roll>']), - ('eh', ['?']), - ('id', [u'•']), - ) + ('add', ['+']), + ('and', ['&']), + ('bool', ['truthy']), + ('mul', ['*']), + ('floordiv', ['/floor', '//']), + ('floor', ['round']), + ('truediv', ['/']), + ('mod', ['%', 'rem', 'remainder', 'modulus']), + ('eq', ['=']), + ('ge', ['>=']), + ('getitem', ['pick', 'at']), + ('gt', ['>']), + ('le', ['<=']), + ('lshift', ['<<']), + ('lt', ['<']), + ('ne', ['<>', '!=']), + ('rshift', ['>>']), + ('sub', ['-']), + ('xor', ['^']), + ('succ', ['++']), + ('pred', ['--']), + ('rolldown', ['roll<']), + ('rollup', ['roll>']), + ('eh', ['?']), + ('id', [u'•']), + ) def add_aliases(D, A): - ''' - Given a dict and a iterable of (name, [alias, ...]) pairs, create - additional entries in the dict mapping each alias to the named function - if it's in the dict. Aliases for functions not in the dict are ignored. - ''' - for name, aliases in A: - try: - F = D[name] - except KeyError: - continue - for alias in aliases: - D[alias] = F + ''' + Given a dict and a iterable of (name, [alias, ...]) pairs, create + additional entries in the dict mapping each alias to the named function + if it's in the dict. Aliases for functions not in the dict are ignored. + ''' + for name, aliases in A: + try: + F = D[name] + except KeyError: + continue + for alias in aliases: + D[alias] = F def yin_functions(): - ''' - Return a dict of named stack effects. + ''' + Return a dict of named stack effects. - "Yin" functions are those that only rearrange items in stacks and - can be defined completely by their stack effects. This means they - can be auto-compiled. - ''' - # pylint: disable=unused-variable - cons = ef(a1, s0)((a1, s0)) - ccons = compose(cons, cons) - dup = ef(a1)(a1, a1) - dupd = ef(a2, a1)(a2, a2, a1) - dupdd = ef(a3, a2, a1)(a3, a3, a2, a1) - first = ef((a1, s1),)(a1,) - over = ef(a2, a1)(a2, a1, a2) - pop = ef(a1)() - popd = ef(a2, a1,)(a1) - popdd = ef(a3, a2, a1,)(a2, a1,) - popop = ef(a2, a1,)() - popopd = ef(a3, a2, a1,)(a1) - popopdd = ef(a4, a3, a2, a1,)(a2, a1) - rest = ef((a1, s0),)(s0,) - rolldown = ef(a1, a2, a3)(a2, a3, a1) - rollup = ef(a1, a2, a3)(a3, a1, a2) - rrest = compose(rest, rest) - second = compose(rest, first) - stack = s0, (s0, s0) - swaack = (s1, s0), (s0, s1) - swap = ef(a1, a2)(a2, a1) - swons = compose(swap, cons) - third = compose(rest, second) - tuck = ef(a2, a1)(a1, a2, a1) - uncons = ef((a1, s0),)(a1, s0) - unswons = compose(uncons, swap) - stuncons = compose(stack, uncons) - stununcons = compose(stack, uncons, uncons) - unit = ef(a1)((a1, ())) + "Yin" functions are those that only rearrange items in stacks and + can be defined completely by their stack effects. This means they + can be auto-compiled. + ''' + # pylint: disable=unused-variable + cons = ef(a1, s0)((a1, s0)) + ccons = compose(cons, cons) + dup = ef(a1)(a1, a1) + dupd = ef(a2, a1)(a2, a2, a1) + dupdd = ef(a3, a2, a1)(a3, a3, a2, a1) + first = ef((a1, s1),)(a1,) + over = ef(a2, a1)(a2, a1, a2) + pop = ef(a1)() + popd = ef(a2, a1,)(a1) + popdd = ef(a3, a2, a1,)(a2, a1,) + popop = ef(a2, a1,)() + popopd = ef(a3, a2, a1,)(a1) + popopdd = ef(a4, a3, a2, a1,)(a2, a1) + rest = ef((a1, s0),)(s0,) + rolldown = ef(a1, a2, a3)(a2, a3, a1) + rollup = ef(a1, a2, a3)(a3, a1, a2) + rrest = compose(rest, rest) + second = compose(rest, first) + stack = s0, (s0, s0) + swaack = (s1, s0), (s0, s1) + swap = ef(a1, a2)(a2, a1) + swons = compose(swap, cons) + third = compose(rest, second) + tuck = ef(a2, a1)(a1, a2, a1) + uncons = ef((a1, s0),)(a1, s0) + unswons = compose(uncons, swap) + stuncons = compose(stack, uncons) + stununcons = compose(stack, uncons, uncons) + unit = ef(a1)((a1, ())) - first_two = compose(uncons, uncons, pop) - fourth = compose(rest, third) + first_two = compose(uncons, uncons, pop) + fourth = compose(rest, third) - _Tree_add_Ee = compose(pop, swap, rolldown, rrest, ccons) - _Tree_get_E = compose(popop, second) - _Tree_delete_clear_stuff = compose(rollup, popop, rest) - _Tree_delete_R0 = compose(over, first, swap, dup) + _Tree_add_Ee = compose(pop, swap, rolldown, rrest, ccons) + _Tree_get_E = compose(popop, second) + _Tree_delete_clear_stuff = compose(rollup, popop, rest) + _Tree_delete_R0 = compose(over, first, swap, dup) - return locals() + return locals() definitions = ('''\ @@ -293,110 +293,110 @@ ifte == [nullary not] dipd branch def FunctionWrapper(f): - '''Set name attribute.''' - if not f.__doc__: - raise ValueError('Function %s must have doc string.' % f.__name__) - f.name = f.__name__.rstrip('_') # Don't shadow builtins. - return f + '''Set name attribute.''' + if not f.__doc__: + raise ValueError('Function %s must have doc string.' % f.__name__) + f.name = f.__name__.rstrip('_') # Don't shadow builtins. + return f def SimpleFunctionWrapper(f): - ''' - Wrap functions that take and return just a stack. - ''' - @FunctionWrapper - @wraps(f) - @rename_code_object(f.__name__) - def inner(stack, expression, dictionary): - return f(stack), expression, dictionary - return inner + ''' + Wrap functions that take and return just a stack. + ''' + @FunctionWrapper + @wraps(f) + @rename_code_object(f.__name__) + def inner(stack, expression, dictionary): + return f(stack), expression, dictionary + return inner def BinaryBuiltinWrapper(f): - ''' - Wrap functions that take two arguments and return a single result. - ''' - @FunctionWrapper - @wraps(f) - @rename_code_object(f.__name__) - def inner(stack, expression, dictionary): - (a, (b, stack)) = stack - result = f(b, a) - return (result, stack), expression, dictionary - return inner + ''' + Wrap functions that take two arguments and return a single result. + ''' + @FunctionWrapper + @wraps(f) + @rename_code_object(f.__name__) + def inner(stack, expression, dictionary): + (a, (b, stack)) = stack + result = f(b, a) + return (result, stack), expression, dictionary + return inner def UnaryBuiltinWrapper(f): - ''' - Wrap functions that take one argument and return a single result. - ''' - @FunctionWrapper - @wraps(f) - @rename_code_object(f.__name__) - def inner(stack, expression, dictionary): - (a, stack) = stack - result = f(a) - return (result, stack), expression, dictionary - return inner + ''' + Wrap functions that take one argument and return a single result. + ''' + @FunctionWrapper + @wraps(f) + @rename_code_object(f.__name__) + def inner(stack, expression, dictionary): + (a, stack) = stack + result = f(a) + return (result, stack), expression, dictionary + return inner class DefinitionWrapper(object): - ''' - Provide implementation of defined functions, and some helper methods. - ''' + ''' + Provide implementation of defined functions, and some helper methods. + ''' - def __init__(self, name, body_text, doc=None): - self.name = self.__name__ = name - self.body = text_to_expression(body_text) - self._body = tuple(iter_stack(self.body)) - self.__doc__ = doc or body_text - self._compiled = None + def __init__(self, name, body_text, doc=None): + self.name = self.__name__ = name + self.body = text_to_expression(body_text) + self._body = tuple(iter_stack(self.body)) + self.__doc__ = doc or body_text + self._compiled = None - def __call__(self, stack, expression, dictionary): - if self._compiled: - return self._compiled(stack, expression, dictionary) # pylint: disable=E1102 - expression = list_to_stack(self._body, expression) - return stack, expression, dictionary + def __call__(self, stack, expression, dictionary): + if self._compiled: + return self._compiled(stack, expression, dictionary) # pylint: disable=E1102 + expression = list_to_stack(self._body, expression) + return stack, expression, dictionary - @classmethod - def parse_definition(class_, defi): - ''' - Given some text describing a Joy function definition parse it and - return a DefinitionWrapper. - ''' - name, proper, body_text = (n.strip() for n in defi.partition('==')) - if not proper: - raise ValueError('Definition %r failed' % (defi,)) - return class_(name, body_text) + @classmethod + def parse_definition(class_, defi): + ''' + Given some text describing a Joy function definition parse it and + return a DefinitionWrapper. + ''' + name, proper, body_text = (n.strip() for n in defi.partition('==')) + if not proper: + raise ValueError('Definition %r failed' % (defi,)) + return class_(name, body_text) - @classmethod - def add_definitions(class_, defs, dictionary): - ''' - Scan multi-line string defs for definitions and add them to the - dictionary. - ''' - for definition in _text_to_defs(defs): - class_.add_def(definition, dictionary) + @classmethod + def add_definitions(class_, defs, dictionary): + ''' + Scan multi-line string defs for definitions and add them to the + dictionary. + ''' + for definition in _text_to_defs(defs): + class_.add_def(definition, dictionary) - @classmethod - def add_def(class_, definition, dictionary, fail_fails=False): - ''' - Add the definition to the dictionary. - ''' - F = class_.parse_definition(definition) - _log.info('Adding definition %s := %s', F.name, expression_to_string(F.body)) - dictionary[F.name] = F + @classmethod + def add_def(class_, definition, dictionary, fail_fails=False): + ''' + Add the definition to the dictionary. + ''' + F = class_.parse_definition(definition) + _log.info('Adding definition %s := %s', F.name, expression_to_string(F.body)) + dictionary[F.name] = F - @classmethod - def load_definitions(class_, filename, dictionary): - with open(filename) as f: - lines = [line for line in f if '==' in line] - for line in lines: - class_.add_def(line, dictionary) + @classmethod + def load_definitions(class_, filename, dictionary): + with open(filename) as f: + lines = [line for line in f if '==' in line] + for line in lines: + class_.add_def(line, dictionary) def _text_to_defs(text): - return (line.strip() for line in text.splitlines() if '==' in line) + return (line.strip() for line in text.splitlines() if '==' in line) # @@ -408,360 +408,360 @@ def _text_to_defs(text): @sec0 @FunctionWrapper def inscribe_(stack, expression, dictionary): - ''' - Create a new Joy function definition in the Joy dictionary. A - definition is given as a string with a name followed by a double - equal sign then one or more Joy functions, the body. for example: + ''' + Create a new Joy function definition in the Joy dictionary. A + definition is given as a string with a name followed by a double + equal sign then one or more Joy functions, the body. for example: - sqr == dup mul + sqr == dup mul - If you want the definition to persist over restarts, enter it into - the definitions.txt resource. - ''' - definition, stack = stack - DefinitionWrapper.add_def(definition, dictionary, fail_fails=True) - return stack, expression, dictionary + If you want the definition to persist over restarts, enter it into + the definitions.txt resource. + ''' + definition, stack = stack + DefinitionWrapper.add_def(definition, dictionary, fail_fails=True) + return stack, expression, dictionary @inscribe @SimpleFunctionWrapper def parse(stack): - '''Parse the string on the stack to a Joy expression.''' - text, stack = stack - expression = text_to_expression(text) - return expression, stack + '''Parse the string on the stack to a Joy expression.''' + text, stack = stack + expression = text_to_expression(text) + return expression, stack @inscribe @SimpleFunctionWrapper def infer_(stack): - '''Attempt to infer the stack effect of a Joy expression.''' - E, stack = stack - effects = infer_expression(E) - e = list_to_stack([(fi, (fo, ())) for fi, fo in effects]) - return e, stack + '''Attempt to infer the stack effect of a Joy expression.''' + E, stack = stack + effects = infer_expression(E) + e = list_to_stack([(fi, (fo, ())) for fi, fo in effects]) + return e, stack @inscribe @sec2 @SimpleFunctionWrapper def getitem(stack): - ''' - :: + ''' + :: - getitem == drop first + getitem == drop first - Expects an integer and a quote on the stack and returns the item at the - nth position in the quote counting from 0. - :: + Expects an integer and a quote on the stack and returns the item at the + nth position in the quote counting from 0. + :: - [a b c d] 0 getitem - ------------------------- - a + [a b c d] 0 getitem + ------------------------- + a - ''' - n, (Q, stack) = stack - return pick(Q, n), stack + ''' + n, (Q, stack) = stack + return pick(Q, n), stack @inscribe @sec1 @SimpleFunctionWrapper def drop(stack): - ''' - :: + ''' + :: - drop == [rest] times + drop == [rest] times - Expects an integer and a quote on the stack and returns the quote with - n items removed off the top. - :: + Expects an integer and a quote on the stack and returns the quote with + n items removed off the top. + :: - [a b c d] 2 drop - ---------------------- - [c d] + [a b c d] 2 drop + ---------------------- + [c d] - ''' - n, (Q, stack) = stack - while n > 0: - try: - _, Q = Q - except ValueError: - raise IndexError - n -= 1 - return Q, stack + ''' + n, (Q, stack) = stack + while n > 0: + try: + _, Q = Q + except ValueError: + raise IndexError + n -= 1 + return Q, stack @inscribe @sec1 @SimpleFunctionWrapper def take(stack): - ''' - Expects an integer and a quote on the stack and returns the quote with - just the top n items in reverse order (because that's easier and you can - use reverse if needed.) - :: + ''' + Expects an integer and a quote on the stack and returns the quote with + just the top n items in reverse order (because that's easier and you can + use reverse if needed.) + :: - [a b c d] 2 take - ---------------------- - [b a] + [a b c d] 2 take + ---------------------- + [b a] - ''' - n, (Q, stack) = stack - x = () - while n > 0: - try: - item, Q = Q - except ValueError: - raise IndexError - x = item, x - n -= 1 - return x, stack + ''' + n, (Q, stack) = stack + x = () + while n > 0: + try: + item, Q = Q + except ValueError: + raise IndexError + x = item, x + n -= 1 + return x, stack @inscribe @SimpleFunctionWrapper def choice(stack): - ''' - Use a Boolean value to select one of two items. - :: + ''' + Use a Boolean value to select one of two items. + :: - A B False choice - ---------------------- - A + A B False choice + ---------------------- + A - A B True choice - --------------------- - B + A B True choice + --------------------- + B - Currently Python semantics are used to evaluate the "truthiness" of the - Boolean value (so empty string, zero, etc. are counted as false, etc.) - ''' - (if_, (then, (else_, stack))) = stack - return then if if_ else else_, stack + Currently Python semantics are used to evaluate the "truthiness" of the + Boolean value (so empty string, zero, etc. are counted as false, etc.) + ''' + (if_, (then, (else_, stack))) = stack + return then if if_ else else_, stack @inscribe @SimpleFunctionWrapper def select(stack): - ''' - Use a Boolean value to select one of two items from a sequence. - :: + ''' + Use a Boolean value to select one of two items from a sequence. + :: - [A B] False select - ------------------------ - A + [A B] False select + ------------------------ + A - [A B] True select - ----------------------- - B + [A B] True select + ----------------------- + B - The sequence can contain more than two items but not fewer. - Currently Python semantics are used to evaluate the "truthiness" of the - Boolean value (so empty string, zero, etc. are counted as false, etc.) - ''' - (flag, (choices, stack)) = stack - (else_, (then, _)) = choices - return then if flag else else_, stack + The sequence can contain more than two items but not fewer. + Currently Python semantics are used to evaluate the "truthiness" of the + Boolean value (so empty string, zero, etc. are counted as false, etc.) + ''' + (flag, (choices, stack)) = stack + (else_, (then, _)) = choices + return then if flag else else_, stack @inscribe @sec_Ns_math @SimpleFunctionWrapper def max_(S): - '''Given a list find the maximum.''' - tos, stack = S - return max(iter_stack(tos)), stack + '''Given a list find the maximum.''' + tos, stack = S + return max(iter_stack(tos)), stack @inscribe @sec_Ns_math @SimpleFunctionWrapper def min_(S): - '''Given a list find the minimum.''' - tos, stack = S - return min(iter_stack(tos)), stack + '''Given a list find the minimum.''' + tos, stack = S + return min(iter_stack(tos)), stack @inscribe @sec_Ns_math @SimpleFunctionWrapper def sum_(S): - '''Given a quoted sequence of numbers return the sum. + '''Given a quoted sequence of numbers return the sum. - sum == 0 swap [+] step - ''' - tos, stack = S - return sum(iter_stack(tos)), stack + sum == 0 swap [+] step + ''' + tos, stack = S + return sum(iter_stack(tos)), stack @inscribe @SimpleFunctionWrapper def remove(S): - ''' - Expects an item on the stack and a quote under it and removes that item - from the the quote. The item is only removed once. - :: + ''' + Expects an item on the stack and a quote under it and removes that item + from the the quote. The item is only removed once. + :: - [1 2 3 1] 1 remove - ------------------------ - [2 3 1] + [1 2 3 1] 1 remove + ------------------------ + [2 3 1] - ''' - (tos, (second, stack)) = S - l = list(iter_stack(second)) - l.remove(tos) - return list_to_stack(l), stack + ''' + (tos, (second, stack)) = S + l = list(iter_stack(second)) + l.remove(tos) + return list_to_stack(l), stack @inscribe @SimpleFunctionWrapper def unique(S): - '''Given a list remove duplicate items.''' - tos, stack = S - I = list(iter_stack(tos)) - return list_to_stack(sorted(set(I), key=I.index)), stack + '''Given a list remove duplicate items.''' + tos, stack = S + I = list(iter_stack(tos)) + return list_to_stack(sorted(set(I), key=I.index)), stack @inscribe @SimpleFunctionWrapper def sort_(S): - '''Given a list return it sorted.''' - tos, stack = S - return list_to_stack(sorted(iter_stack(tos))), stack + '''Given a list return it sorted.''' + tos, stack = S + return list_to_stack(sorted(iter_stack(tos))), stack _functions['clear'] = s0, s1 @inscribe @SimpleFunctionWrapper def clear(stack): - '''Clear everything from the stack. - :: + '''Clear everything from the stack. + :: - clear == stack [pop stack] loop + clear == stack [pop stack] loop - ... clear - --------------- + ... clear + --------------- - ''' - return () + ''' + return () @inscribe @SimpleFunctionWrapper def unstack(stack): - ''' - The unstack operator expects a list on top of the stack and makes that - the stack discarding the rest of the stack. - ''' - return stack[0] + ''' + The unstack operator expects a list on top of the stack and makes that + the stack discarding the rest of the stack. + ''' + return stack[0] @inscribe @SimpleFunctionWrapper def reverse(S): - '''Reverse the list on the top of the stack. - :: + '''Reverse the list on the top of the stack. + :: - reverse == [] swap shunt - ''' - (tos, stack) = S - res = () - for term in iter_stack(tos): - res = term, res - return res, stack + reverse == [] swap shunt + ''' + (tos, stack) = S + res = () + for term in iter_stack(tos): + res = term, res + return res, stack @inscribe @combinator_effect(_COMB_NUMS(), s7, s6) @SimpleFunctionWrapper def concat_(S): - '''Concatinate the two lists on the top of the stack. - :: + '''Concatinate the two lists on the top of the stack. + :: - [a b c] [d e f] concat - ---------------------------- - [a b c d e f] + [a b c] [d e f] concat + ---------------------------- + [a b c d e f] ''' - (tos, (second, stack)) = S - return concat(second, tos), stack + (tos, (second, stack)) = S + return concat(second, tos), stack @inscribe @SimpleFunctionWrapper def shunt(stack): - '''Like concat but reverses the top list into the second. - :: + '''Like concat but reverses the top list into the second. + :: - shunt == [swons] step == reverse swap concat + shunt == [swons] step == reverse swap concat - [a b c] [d e f] shunt - --------------------------- - [f e d a b c] + [a b c] [d e f] shunt + --------------------------- + [f e d a b c] - ''' - (tos, (second, stack)) = stack - while tos: - term, tos = tos - second = term, second - return second, stack + ''' + (tos, (second, stack)) = stack + while tos: + term, tos = tos + second = term, second + return second, stack @inscribe @SimpleFunctionWrapper def zip_(S): - ''' - Replace the two lists on the top of the stack with a list of the pairs - from each list. The smallest list sets the length of the result list. - ''' - (tos, (second, stack)) = S - accumulator = [ - (a, (b, ())) - for a, b in zip(iter_stack(tos), iter_stack(second)) - ] - return list_to_stack(accumulator), stack + ''' + Replace the two lists on the top of the stack with a list of the pairs + from each list. The smallest list sets the length of the result list. + ''' + (tos, (second, stack)) = S + accumulator = [ + (a, (b, ())) + for a, b in zip(iter_stack(tos), iter_stack(second)) + ] + return list_to_stack(accumulator), stack @inscribe @sec_unary_math @SimpleFunctionWrapper def succ(S): - '''Increment TOS.''' - (tos, stack) = S - return tos + 1, stack + '''Increment TOS.''' + (tos, stack) = S + return tos + 1, stack @inscribe @sec_unary_math @SimpleFunctionWrapper def pred(S): - '''Decrement TOS.''' - (tos, stack) = S - return tos - 1, stack + '''Decrement TOS.''' + (tos, stack) = S + return tos - 1, stack @inscribe @SimpleFunctionWrapper def pm(stack): - ''' - Plus or minus - :: + ''' + Plus or minus + :: - a b pm - ------------- - a+b a-b + a b pm + ------------- + a+b a-b - ''' - a, (b, stack) = stack - p, m, = b + a, b - a - return m, (p, stack) + ''' + a, (b, stack) = stack + p, m, = b + a, b - a + return m, (p, stack) def floor(n): - return int(math.floor(n)) + return int(math.floor(n)) floor.__doc__ = math.floor.__doc__ @@ -769,27 +769,27 @@ floor.__doc__ = math.floor.__doc__ @inscribe @SimpleFunctionWrapper def divmod_(S): - ''' - divmod(x, y) -> (quotient, remainder) + ''' + divmod(x, y) -> (quotient, remainder) - Return the tuple (x//y, x%y). Invariant: div*y + mod == x. - ''' - a, (b, stack) = S - d, m = divmod(a, b) - return d, (m, stack) + Return the tuple (x//y, x%y). Invariant: div*y + mod == x. + ''' + a, (b, stack) = S + d, m = divmod(a, b) + return d, (m, stack) def sqrt(a): - ''' - Return the square root of the number a. - Negative numbers return complex roots. - ''' - try: - r = math.sqrt(a) - except ValueError: - assert a < 0, repr(a) - r = math.sqrt(-a) * 1j - return r + ''' + Return the square root of the number a. + Negative numbers return complex roots. + ''' + try: + r = math.sqrt(a) + except ValueError: + assert a < 0, repr(a) + r = math.sqrt(-a) * 1j + return r #def execute(S): @@ -802,20 +802,20 @@ def sqrt(a): @inscribe @SimpleFunctionWrapper def id_(stack): - '''The identity function.''' - return stack + '''The identity function.''' + return stack @inscribe @SimpleFunctionWrapper def void(stack): - '''True if the form on TOS is void otherwise False.''' - form, stack = stack - return _void(form), stack + '''True if the form on TOS is void otherwise False.''' + form, stack = stack + return _void(form), stack def _void(form): - return any(not _void(i) for i in iter_stack(form)) + return any(not _void(i) for i in iter_stack(form)) @@ -827,42 +827,42 @@ def _void(form): @inscribe @FunctionWrapper def words(stack, expression, dictionary): - '''Print all the words in alphabetical order.''' - print(' '.join(sorted(dictionary))) - return stack, expression, dictionary + '''Print all the words in alphabetical order.''' + print(' '.join(sorted(dictionary))) + return stack, expression, dictionary @inscribe @FunctionWrapper def sharing(stack, expression, dictionary): - '''Print redistribution information.''' - print("You may convey verbatim copies of the Program's source code as" - ' you receive it, in any medium, provided that you conspicuously' - ' and appropriately publish on each copy an appropriate copyright' - ' notice; keep intact all notices stating that this License and' - ' any non-permissive terms added in accord with section 7 apply' - ' to the code; keep intact all notices of the absence of any' - ' warranty; and give all recipients a copy of this License along' - ' with the Program.' - ' You should have received a copy of the GNU General Public License' - ' along with Thun. If not see .') - return stack, expression, dictionary + '''Print redistribution information.''' + print("You may convey verbatim copies of the Program's source code as" + ' you receive it, in any medium, provided that you conspicuously' + ' and appropriately publish on each copy an appropriate copyright' + ' notice; keep intact all notices stating that this License and' + ' any non-permissive terms added in accord with section 7 apply' + ' to the code; keep intact all notices of the absence of any' + ' warranty; and give all recipients a copy of this License along' + ' with the Program.' + ' You should have received a copy of the GNU General Public License' + ' along with Thun. If not see .') + return stack, expression, dictionary @inscribe @FunctionWrapper def warranty(stack, expression, dictionary): - '''Print warranty information.''' - print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY' - ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE' - ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM' - ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR' - ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES' - ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE' - ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS' - ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE' - ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.') - return stack, expression, dictionary + '''Print warranty information.''' + print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY' + ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE' + ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM' + ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR' + ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES' + ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE' + ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS' + ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE' + ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.') + return stack, expression, dictionary # def simple_manual(stack): @@ -887,11 +887,11 @@ def warranty(stack, expression, dictionary): @inscribe @FunctionWrapper def help_(S, expression, dictionary): - '''Accepts a quoted symbol on the top of the stack and prints its docs.''' - ((symbol, _), stack) = S - word = dictionary[symbol] - print(getdoc(word)) - return stack, expression, dictionary + '''Accepts a quoted symbol on the top of the stack and prints its docs.''' + ((symbol, _), stack) = S + word = dictionary[symbol] + print(getdoc(word)) + return stack, expression, dictionary # @@ -920,178 +920,178 @@ S_swaack = Symbol('swaack') @combinator_effect(_COMB_NUMS(), s1) @FunctionWrapper def i(stack, expression, dictionary): - ''' - The i combinator expects a quoted program on the stack and unpacks it - onto the pending expression for evaluation. - :: + ''' + The i combinator expects a quoted program on the stack and unpacks it + onto the pending expression for evaluation. + :: - [Q] i - ----------- - Q + [Q] i + ----------- + Q - ''' - quote, stack = stack - return stack, concat(quote, expression), dictionary + ''' + quote, stack = stack + return stack, concat(quote, expression), dictionary @inscribe @combinator_effect(_COMB_NUMS(), s1) @FunctionWrapper def x(stack, expression, dictionary): - ''' - :: + ''' + :: - x == dup i + x == dup i - ... [Q] x = ... [Q] dup i - ... [Q] x = ... [Q] [Q] i - ... [Q] x = ... [Q] Q + ... [Q] x = ... [Q] dup i + ... [Q] x = ... [Q] [Q] i + ... [Q] x = ... [Q] Q - ''' - quote, _ = stack - return stack, concat(quote, expression), dictionary + ''' + quote, _ = stack + return stack, concat(quote, expression), dictionary @inscribe @combinator_effect(_COMB_NUMS(), s7, s6) @FunctionWrapper def b(stack, expression, dictionary): - ''' - :: + ''' + :: - b == [i] dip i + b == [i] dip i - ... [P] [Q] b == ... [P] i [Q] i - ... [P] [Q] b == ... P Q + ... [P] [Q] b == ... [P] i [Q] i + ... [P] [Q] b == ... P Q - ''' - q, (p, (stack)) = stack - return stack, concat(p, concat(q, expression)), dictionary + ''' + q, (p, (stack)) = stack + return stack, concat(p, concat(q, expression)), dictionary @inscribe @combinator_effect(_COMB_NUMS(), a1, s1) @FunctionWrapper def dupdip(stack, expression, dictionary): - ''' - :: + ''' + :: - [F] dupdip == dup [F] dip + [F] dupdip == dup [F] dip - ... a [F] dupdip - ... a dup [F] dip - ... a a [F] dip - ... a F a + ... a [F] dupdip + ... a dup [F] dip + ... a a [F] dip + ... a F a - ''' - F, stack = stack - a = stack[0] - return stack, concat(F, (a, expression)), dictionary + ''' + F, stack = stack + a = stack[0] + return stack, concat(F, (a, expression)), dictionary @inscribe @combinator_effect(_COMB_NUMS(), s7, s6) @FunctionWrapper def infra(stack, expression, dictionary): - ''' - Accept a quoted program and a list on the stack and run the program - with the list as its stack. Does not affect the rest of the stack. - :: + ''' + Accept a quoted program and a list on the stack and run the program + with the list as its stack. Does not affect the rest of the stack. + :: - ... [a b c] [Q] . infra - ----------------------------- - c b a . Q [...] swaack + ... [a b c] [Q] . infra + ----------------------------- + c b a . Q [...] swaack - ''' - (quote, (aggregate, stack)) = stack - return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary + ''' + (quote, (aggregate, stack)) = stack + return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary @inscribe #@combinator_effect(_COMB_NUMS(), s7, s6, s5, s4) @FunctionWrapper def genrec(stack, expression, dictionary): - ''' - General Recursion Combinator. - :: + ''' + General Recursion Combinator. + :: - [if] [then] [rec1] [rec2] genrec - --------------------------------------------------------------------- - [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte + [if] [then] [rec1] [rec2] genrec + --------------------------------------------------------------------- + [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte - From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun: - "The genrec combinator takes four program parameters in addition to - whatever data parameters it needs. Fourth from the top is an if-part, - followed by a then-part. If the if-part yields true, then the then-part - is executed and the combinator terminates. The other two parameters are - the rec1-part and the rec2-part. If the if-part yields false, the - rec1-part is executed. Following that the four program parameters and - the combinator are again pushed onto the stack bundled up in a quoted - form. Then the rec2-part is executed, where it will find the bundled - form. Typically it will then execute the bundled form, either with i or - with app2, or some other combinator." + From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun: + "The genrec combinator takes four program parameters in addition to + whatever data parameters it needs. Fourth from the top is an if-part, + followed by a then-part. If the if-part yields true, then the then-part + is executed and the combinator terminates. The other two parameters are + the rec1-part and the rec2-part. If the if-part yields false, the + rec1-part is executed. Following that the four program parameters and + the combinator are again pushed onto the stack bundled up in a quoted + form. Then the rec2-part is executed, where it will find the bundled + form. Typically it will then execute the bundled form, either with i or + with app2, or some other combinator." - The way to design one of these is to fix your base case [then] and the - test [if], and then treat rec1 and rec2 as an else-part "sandwiching" - a quotation of the whole function. + The way to design one of these is to fix your base case [then] and the + test [if], and then treat rec1 and rec2 as an else-part "sandwiching" + a quotation of the whole function. - For example, given a (general recursive) function 'F': - :: + For example, given a (general recursive) function 'F': + :: - F == [I] [T] [R1] [R2] genrec + F == [I] [T] [R1] [R2] genrec - If the [I] if-part fails you must derive R1 and R2 from: - :: + If the [I] if-part fails you must derive R1 and R2 from: + :: - ... R1 [F] R2 + ... R1 [F] R2 - Just set the stack arguments in front, and figure out what R1 and R2 - have to do to apply the quoted [F] in the proper way. In effect, the - genrec combinator turns into an ifte combinator with a quoted copy of - the original definition in the else-part: - :: + Just set the stack arguments in front, and figure out what R1 and R2 + have to do to apply the quoted [F] in the proper way. In effect, the + genrec combinator turns into an ifte combinator with a quoted copy of + the original definition in the else-part: + :: - F == [I] [T] [R1] [R2] genrec - == [I] [T] [R1 [F] R2] ifte + F == [I] [T] [R1] [R2] genrec + == [I] [T] [R1 [F] R2] ifte - Primitive recursive functions are those where R2 == i. - :: + Primitive recursive functions are those where R2 == i. + :: - P == [I] [T] [R] primrec - == [I] [T] [R [P] i] ifte - == [I] [T] [R P] ifte + P == [I] [T] [R] primrec + == [I] [T] [R [P] i] ifte + == [I] [T] [R P] ifte - ''' - (rec2, (rec1, stack)) = stack - (then, (if_, _)) = stack - F = (if_, (then, (rec1, (rec2, (S_genrec, ()))))) - else_ = concat(rec1, (F, rec2)) - return (else_, stack), (S_ifte, expression), dictionary + ''' + (rec2, (rec1, stack)) = stack + (then, (if_, _)) = stack + F = (if_, (then, (rec1, (rec2, (S_genrec, ()))))) + else_ = concat(rec1, (F, rec2)) + return (else_, stack), (S_ifte, expression), dictionary @inscribe @combinator_effect(_COMB_NUMS(), s7, s6) @FunctionWrapper def map_(S, expression, dictionary): - ''' - Run the quoted program on TOS on the items in the list under it, push a - new list with the results in place of the program and original list. - ''' + ''' + Run the quoted program on TOS on the items in the list under it, push a + new list with the results in place of the program and original list. + ''' # (quote, (aggregate, stack)) = S # results = list_to_stack([ # joy((term, stack), quote, dictionary)[0][0] # for term in iter_stack(aggregate) # ]) # return (results, stack), expression, dictionary - (quote, (aggregate, stack)) = S - if not aggregate: - return (aggregate, stack), expression, dictionary - batch = () - for term in iter_stack(aggregate): - s = term, stack - batch = (s, (quote, (S_infra, (S_first, batch)))) - stack = (batch, ((), stack)) - return stack, (S_infra, expression), dictionary + (quote, (aggregate, stack)) = S + if not aggregate: + return (aggregate, stack), expression, dictionary + batch = () + for term in iter_stack(aggregate): + s = term, stack + batch = (s, (quote, (S_infra, (S_first, batch)))) + stack = (batch, ((), stack)) + return stack, (S_infra, expression), dictionary #def cleave(S, expression, dictionary): @@ -1109,41 +1109,41 @@ def map_(S, expression, dictionary): def branch_true(stack, expression, dictionary): - # pylint: disable=unused-variable - (then, (else_, (flag, stack))) = stack - return stack, concat(then, expression), dictionary + # pylint: disable=unused-variable + (then, (else_, (flag, stack))) = stack + return stack, concat(then, expression), dictionary def branch_false(stack, expression, dictionary): - # pylint: disable=unused-variable - (then, (else_, (flag, stack))) = stack - return stack, concat(else_, expression), dictionary + # pylint: disable=unused-variable + (then, (else_, (flag, stack))) = stack + return stack, concat(else_, expression), dictionary @inscribe @poly_combinator_effect(_COMB_NUMS(), [branch_true, branch_false], b1, s7, s6) @FunctionWrapper def branch(stack, expression, dictionary): - ''' - Use a Boolean value to select one of two quoted programs to run. + ''' + Use a Boolean value to select one of two quoted programs to run. - :: + :: - branch == roll< choice i + branch == roll< choice i - :: + :: - False [F] [T] branch - -------------------------- - F + False [F] [T] branch + -------------------------- + F - True [F] [T] branch - ------------------------- - T + True [F] [T] branch + ------------------------- + T - ''' - (then, (else_, (flag, stack))) = stack - return stack, concat(then if flag else else_, expression), dictionary + ''' + (then, (else_, (flag, stack))) = stack + return stack, concat(then if flag else else_, expression), dictionary #FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100) @@ -1180,231 +1180,231 @@ def branch(stack, expression, dictionary): @inscribe @FunctionWrapper def cond(stack, expression, dictionary): - ''' - This combinator works like a case statement. It expects a single quote - on the stack that must contain zero or more condition quotes and a - default quote. Each condition clause should contain a quoted predicate - followed by the function expression to run if that predicate returns - true. If no predicates return true the default function runs. + ''' + This combinator works like a case statement. It expects a single quote + on the stack that must contain zero or more condition quotes and a + default quote. Each condition clause should contain a quoted predicate + followed by the function expression to run if that predicate returns + true. If no predicates return true the default function runs. - It works by rewriting into a chain of nested `ifte` expressions, e.g.:: + It works by rewriting into a chain of nested `ifte` expressions, e.g.:: - [[[B0] T0] [[B1] T1] [D]] cond - ----------------------------------------- - [B0] [T0] [[B1] [T1] [D] ifte] ifte + [[[B0] T0] [[B1] T1] [D]] cond + ----------------------------------------- + [B0] [T0] [[B1] [T1] [D] ifte] ifte - ''' - conditions, stack = stack - if conditions: - expression = _cond(conditions, expression) - try: - # Attempt to preload the args to first ifte. - (P, (T, (E, expression))) = expression - except ValueError: - # If, for any reason, the argument to cond should happen to contain - # only the default clause then this optimization will fail. - pass - else: - stack = (E, (T, (P, stack))) - return stack, expression, dictionary + ''' + conditions, stack = stack + if conditions: + expression = _cond(conditions, expression) + try: + # Attempt to preload the args to first ifte. + (P, (T, (E, expression))) = expression + except ValueError: + # If, for any reason, the argument to cond should happen to contain + # only the default clause then this optimization will fail. + pass + else: + stack = (E, (T, (P, stack))) + return stack, expression, dictionary def _cond(conditions, expression): - (clause, rest) = conditions - if not rest: # clause is [D] - return clause - P, T = clause - return (P, (T, (_cond(rest, ()), (S_ifte, expression)))) + (clause, rest) = conditions + if not rest: # clause is [D] + return clause + P, T = clause + return (P, (T, (_cond(rest, ()), (S_ifte, expression)))) @inscribe @combinator_effect(_COMB_NUMS(), a1, s1) @FunctionWrapper def dip(stack, expression, dictionary): - ''' - The dip combinator expects a quoted program on the stack and below it - some item, it hoists the item into the expression and runs the program - on the rest of the stack. - :: + ''' + The dip combinator expects a quoted program on the stack and below it + some item, it hoists the item into the expression and runs the program + on the rest of the stack. + :: - ... x [Q] dip - ------------------- - ... Q x + ... x [Q] dip + ------------------- + ... Q x - ''' - (quote, (x, stack)) = stack - expression = (x, expression) - return stack, concat(quote, expression), dictionary + ''' + (quote, (x, stack)) = stack + expression = (x, expression) + return stack, concat(quote, expression), dictionary @inscribe @combinator_effect(_COMB_NUMS(), a2, a1, s1) @FunctionWrapper def dipd(S, expression, dictionary): - ''' - Like dip but expects two items. - :: + ''' + Like dip but expects two items. + :: - ... y x [Q] dip - --------------------- - ... Q y x + ... y x [Q] dip + --------------------- + ... Q y x - ''' - (quote, (x, (y, stack))) = S - expression = (y, (x, expression)) - return stack, concat(quote, expression), dictionary + ''' + (quote, (x, (y, stack))) = S + expression = (y, (x, expression)) + return stack, concat(quote, expression), dictionary @inscribe @combinator_effect(_COMB_NUMS(), a3, a2, a1, s1) @FunctionWrapper def dipdd(S, expression, dictionary): - ''' - Like dip but expects three items. - :: + ''' + Like dip but expects three items. + :: - ... z y x [Q] dip - ----------------------- - ... Q z y x + ... z y x [Q] dip + ----------------------- + ... Q z y x - ''' - (quote, (x, (y, (z, stack)))) = S - expression = (z, (y, (x, expression))) - return stack, concat(quote, expression), dictionary + ''' + (quote, (x, (y, (z, stack)))) = S + expression = (z, (y, (x, expression))) + return stack, concat(quote, expression), dictionary @inscribe @combinator_effect(_COMB_NUMS(), a1, s1) @FunctionWrapper def app1(S, expression, dictionary): - ''' - Given a quoted program on TOS and anything as the second stack item run - the program and replace the two args with the first result of the - program. - :: + ''' + Given a quoted program on TOS and anything as the second stack item run + the program and replace the two args with the first result of the + program. + :: - ... x [Q] . app1 - ----------------------------------- - ... [x ...] [Q] . infra first - ''' - (quote, (x, stack)) = S - stack = (quote, ((x, stack), stack)) - expression = (S_infra, (S_first, expression)) - return stack, expression, dictionary + ... x [Q] . app1 + ----------------------------------- + ... [x ...] [Q] . infra first + ''' + (quote, (x, stack)) = S + stack = (quote, ((x, stack), stack)) + expression = (S_infra, (S_first, expression)) + return stack, expression, dictionary @inscribe @combinator_effect(_COMB_NUMS(), a2, a1, s1) @FunctionWrapper def app2(S, expression, dictionary): - '''Like app1 with two items. - :: + '''Like app1 with two items. + :: - ... y x [Q] . app2 - ----------------------------------- - ... [y ...] [Q] . infra first - [x ...] [Q] infra first + ... y x [Q] . app2 + ----------------------------------- + ... [y ...] [Q] . infra first + [x ...] [Q] infra first - ''' - (quote, (x, (y, stack))) = S - expression = (S_infra, (S_first, - ((x, stack), (quote, (S_infra, (S_first, - expression)))))) - stack = (quote, ((y, stack), stack)) - return stack, expression, dictionary + ''' + (quote, (x, (y, stack))) = S + expression = (S_infra, (S_first, + ((x, stack), (quote, (S_infra, (S_first, + expression)))))) + stack = (quote, ((y, stack), stack)) + return stack, expression, dictionary @inscribe @combinator_effect(_COMB_NUMS(), a3, a2, a1, s1) @FunctionWrapper def app3(S, expression, dictionary): - '''Like app1 with three items. - :: + '''Like app1 with three items. + :: - ... z y x [Q] . app3 - ----------------------------------- - ... [z ...] [Q] . infra first - [y ...] [Q] infra first - [x ...] [Q] infra first + ... z y x [Q] . app3 + ----------------------------------- + ... [z ...] [Q] . infra first + [y ...] [Q] infra first + [x ...] [Q] infra first - ''' - (quote, (x, (y, (z, stack)))) = S - expression = (S_infra, (S_first, - ((y, stack), (quote, (S_infra, (S_first, - ((x, stack), (quote, (S_infra, (S_first, - expression)))))))))) - stack = (quote, ((z, stack), stack)) - return stack, expression, dictionary + ''' + (quote, (x, (y, (z, stack)))) = S + expression = (S_infra, (S_first, + ((y, stack), (quote, (S_infra, (S_first, + ((x, stack), (quote, (S_infra, (S_first, + expression)))))))))) + stack = (quote, ((z, stack), stack)) + return stack, expression, dictionary @inscribe @combinator_effect(_COMB_NUMS(), s7, s6) @FunctionWrapper def step(S, expression, dictionary): - ''' - Run a quoted program on each item in a sequence. - :: + ''' + Run a quoted program on each item in a sequence. + :: - ... [] [Q] . step - ----------------------- - ... . + ... [] [Q] . step + ----------------------- + ... . - ... [a] [Q] . step - ------------------------ - ... a . Q + ... [a] [Q] . step + ------------------------ + ... a . Q - ... [a b c] [Q] . step - ---------------------------------------- - ... a . Q [b c] [Q] step + ... [a b c] [Q] . step + ---------------------------------------- + ... a . Q [b c] [Q] step - The step combinator executes the quotation on each member of the list - on top of the stack. - ''' - (quote, (aggregate, stack)) = S - if not aggregate: - return stack, expression, dictionary - head, tail = aggregate - stack = quote, (head, stack) - if tail: - expression = tail, (quote, (S_step, expression)) - expression = S_i, expression - return stack, expression, dictionary + The step combinator executes the quotation on each member of the list + on top of the stack. + ''' + (quote, (aggregate, stack)) = S + if not aggregate: + return stack, expression, dictionary + head, tail = aggregate + stack = quote, (head, stack) + if tail: + expression = tail, (quote, (S_step, expression)) + expression = S_i, expression + return stack, expression, dictionary @inscribe @combinator_effect(_COMB_NUMS(), i1, s6) @FunctionWrapper def times(stack, expression, dictionary): - ''' - times == [-- dip] cons [swap] infra [0 >] swap while pop - :: + ''' + times == [-- dip] cons [swap] infra [0 >] swap while pop + :: - ... n [Q] . times - --------------------- w/ n <= 0 - ... . + ... n [Q] . times + --------------------- w/ n <= 0 + ... . - ... 1 [Q] . times - --------------------------------- - ... . Q + ... 1 [Q] . times + --------------------------------- + ... . Q - ... n [Q] . times - --------------------------------- w/ n > 1 - ... . Q (n - 1) [Q] times + ... n [Q] . times + --------------------------------- w/ n > 1 + ... . Q (n - 1) [Q] times - ''' - # times == [-- dip] cons [swap] infra [0 >] swap while pop - (quote, (n, stack)) = stack - if n <= 0: - return stack, expression, dictionary - n -= 1 - if n: - expression = n, (quote, (S_times, expression)) - expression = concat(quote, expression) - return stack, expression, dictionary + ''' + # times == [-- dip] cons [swap] infra [0 >] swap while pop + (quote, (n, stack)) = stack + if n <= 0: + return stack, expression, dictionary + n -= 1 + if n: + expression = n, (quote, (S_times, expression)) + expression = concat(quote, expression) + return stack, expression, dictionary # The current definition above works like this: @@ -1424,65 +1424,65 @@ def times(stack, expression, dictionary): def loop_true(stack, expression, dictionary): - quote, (flag, stack) = stack # pylint: disable=unused-variable - return stack, concat(quote, (S_pop, expression)), dictionary + quote, (flag, stack) = stack # pylint: disable=unused-variable + return stack, concat(quote, (S_pop, expression)), dictionary def loop_two_true(stack, expression, dictionary): - quote, (flag, stack) = stack # pylint: disable=unused-variable - return stack, concat(quote, (S_pop, concat(quote, (S_pop, expression)))), dictionary + quote, (flag, stack) = stack # pylint: disable=unused-variable + return stack, concat(quote, (S_pop, concat(quote, (S_pop, expression)))), dictionary def loop_false(stack, expression, dictionary): - quote, (flag, stack) = stack # pylint: disable=unused-variable - return stack, expression, dictionary + quote, (flag, stack) = stack # pylint: disable=unused-variable + return stack, expression, dictionary @inscribe @poly_combinator_effect(_COMB_NUMS(), [loop_two_true, loop_true, loop_false], b1, s6) @FunctionWrapper def loop(stack, expression, dictionary): - ''' - Basic loop combinator. - :: + ''' + Basic loop combinator. + :: - ... True [Q] loop - ----------------------- - ... Q [Q] loop + ... True [Q] loop + ----------------------- + ... Q [Q] loop - ... False [Q] loop - ------------------------ - ... + ... False [Q] loop + ------------------------ + ... - ''' - quote, (flag, stack) = stack - if flag: - expression = concat(quote, (quote, (S_loop, expression))) - return stack, expression, dictionary + ''' + quote, (flag, stack) = stack + if flag: + expression = concat(quote, (quote, (S_loop, expression))) + return stack, expression, dictionary @inscribe @combinator_effect(_COMB_NUMS(), a1, a2, s6, s7, s8) @FunctionWrapper def cmp_(stack, expression, dictionary): - ''' - cmp takes two values and three quoted programs on the stack and runs - one of the three depending on the results of comparing the two values: - :: + ''' + cmp takes two values and three quoted programs on the stack and runs + one of the three depending on the results of comparing the two values: + :: - a b [G] [E] [L] cmp - ------------------------- a > b - G + a b [G] [E] [L] cmp + ------------------------- a > b + G - a b [G] [E] [L] cmp - ------------------------- a = b - E + a b [G] [E] [L] cmp + ------------------------- a = b + E - a b [G] [E] [L] cmp - ------------------------- a < b - L - ''' - L, (E, (G, (b, (a, stack)))) = stack - expression = concat(G if a > b else L if a < b else E, expression) - return stack, expression, dictionary + a b [G] [E] [L] cmp + ------------------------- a < b + L + ''' + L, (E, (G, (b, (a, stack)))) = stack + expression = concat(G if a > b else L if a < b else E, expression) + return stack, expression, dictionary # FunctionWrapper(cleave), @@ -1491,40 +1491,40 @@ def cmp_(stack, expression, dictionary): for F in ( - #divmod_ = pm = __(n2, n1), __(n4, n3) + #divmod_ = pm = __(n2, n1), __(n4, n3) - sec_binary_cmp(BinaryBuiltinWrapper(operator.eq)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.ge)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.gt)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.le)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.lt)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.ne)), + sec_binary_cmp(BinaryBuiltinWrapper(operator.eq)), + sec_binary_cmp(BinaryBuiltinWrapper(operator.ge)), + sec_binary_cmp(BinaryBuiltinWrapper(operator.gt)), + sec_binary_cmp(BinaryBuiltinWrapper(operator.le)), + sec_binary_cmp(BinaryBuiltinWrapper(operator.lt)), + sec_binary_cmp(BinaryBuiltinWrapper(operator.ne)), - sec_binary_ints(BinaryBuiltinWrapper(operator.xor)), - sec_binary_ints(BinaryBuiltinWrapper(operator.lshift)), - sec_binary_ints(BinaryBuiltinWrapper(operator.rshift)), + sec_binary_ints(BinaryBuiltinWrapper(operator.xor)), + sec_binary_ints(BinaryBuiltinWrapper(operator.lshift)), + sec_binary_ints(BinaryBuiltinWrapper(operator.rshift)), - sec_binary_logic(BinaryBuiltinWrapper(operator.and_)), - sec_binary_logic(BinaryBuiltinWrapper(operator.or_)), + sec_binary_logic(BinaryBuiltinWrapper(operator.and_)), + sec_binary_logic(BinaryBuiltinWrapper(operator.or_)), - sec_binary_math(BinaryBuiltinWrapper(operator.add)), - sec_binary_math(BinaryBuiltinWrapper(operator.floordiv)), - sec_binary_math(BinaryBuiltinWrapper(operator.mod)), - sec_binary_math(BinaryBuiltinWrapper(operator.mul)), - sec_binary_math(BinaryBuiltinWrapper(operator.pow)), - sec_binary_math(BinaryBuiltinWrapper(operator.sub)), - sec_binary_math(BinaryBuiltinWrapper(operator.truediv)), + sec_binary_math(BinaryBuiltinWrapper(operator.add)), + sec_binary_math(BinaryBuiltinWrapper(operator.floordiv)), + sec_binary_math(BinaryBuiltinWrapper(operator.mod)), + sec_binary_math(BinaryBuiltinWrapper(operator.mul)), + sec_binary_math(BinaryBuiltinWrapper(operator.pow)), + sec_binary_math(BinaryBuiltinWrapper(operator.sub)), + sec_binary_math(BinaryBuiltinWrapper(operator.truediv)), - sec_unary_logic(UnaryBuiltinWrapper(bool)), - sec_unary_logic(UnaryBuiltinWrapper(operator.not_)), + sec_unary_logic(UnaryBuiltinWrapper(bool)), + sec_unary_logic(UnaryBuiltinWrapper(operator.not_)), - sec_unary_math(UnaryBuiltinWrapper(abs)), - sec_unary_math(UnaryBuiltinWrapper(operator.neg)), - sec_unary_math(UnaryBuiltinWrapper(sqrt)), + sec_unary_math(UnaryBuiltinWrapper(abs)), + sec_unary_math(UnaryBuiltinWrapper(operator.neg)), + sec_unary_math(UnaryBuiltinWrapper(sqrt)), - stack_effect(n1)(i1)(UnaryBuiltinWrapper(floor)), - ): - inscribe(F) + stack_effect(n1)(i1)(UnaryBuiltinWrapper(floor)), + ): + inscribe(F) del F # Otherwise Sphinx autodoc will pick it up. @@ -1541,13 +1541,13 @@ _functions.update(YIN_STACK_EFFECTS) # ''' in dict(compose=compose), _functions for name in sorted(_functions): - sec = _functions[name] - F = FUNCTIONS[name] = SymbolJoyType(name, [sec], _SYM_NUMS()) - if name in YIN_STACK_EFFECTS: - _log.info('Setting stack effect for Yin function %s := %s', F.name, doc_from_stack_effect(*sec)) + sec = _functions[name] + F = FUNCTIONS[name] = SymbolJoyType(name, [sec], _SYM_NUMS()) + if name in YIN_STACK_EFFECTS: + _log.info('Setting stack effect for Yin function %s := %s', F.name, doc_from_stack_effect(*sec)) for name, primitive in getmembers(genlib, isfunction): - inscribe(SimpleFunctionWrapper(primitive)) + inscribe(SimpleFunctionWrapper(primitive)) add_aliases(_dictionary, ALIASES) @@ -1559,45 +1559,45 @@ DefinitionWrapper.add_definitions(definitions, _dictionary) EXPECTATIONS = dict( - ifte=(s7, (s6, (s5, s4))), - nullary=(s7, s6), - run=(s7, s6), + ifte=(s7, (s6, (s5, s4))), + nullary=(s7, s6), + run=(s7, s6), ) EXPECTATIONS['while'] = (s7, (s6, s5)) for name in ''' - dinfrirst - nullary - ifte - run - dupdipd codireco - while - '''.split(): - C = _dictionary[name] - expect = EXPECTATIONS.get(name) - if expect: - sec = doc_from_stack_effect(expect) - _log.info('Setting stack EXPECT for combinator %s := %s', C.name, sec) - else: - _log.info('combinator %s', C.name) - FUNCTIONS[name] = CombinatorJoyType(name, [C], _COMB_NUMS(), expect) + dinfrirst + nullary + ifte + run + dupdipd codireco + while + '''.split(): + C = _dictionary[name] + expect = EXPECTATIONS.get(name) + if expect: + sec = doc_from_stack_effect(expect) + _log.info('Setting stack EXPECT for combinator %s := %s', C.name, sec) + else: + _log.info('combinator %s', C.name) + FUNCTIONS[name] = CombinatorJoyType(name, [C], _COMB_NUMS(), expect) for name in (''' - of quoted enstacken ? - unary binary ternary - sqr unquoted - '''.split()): - of_ = _dictionary[name] - secs = infer_expression(of_.body) - assert len(secs) == 1, repr(secs) - _log.info( - 'Setting stack effect for definition %s := %s', - name, - doc_from_stack_effect(*secs[0]), - ) - FUNCTIONS[name] = SymbolJoyType(name, infer_expression(of_.body), _SYM_NUMS()) + of quoted enstacken ? + unary binary ternary + sqr unquoted + '''.split()): + of_ = _dictionary[name] + secs = infer_expression(of_.body) + assert len(secs) == 1, repr(secs) + _log.info( + 'Setting stack effect for definition %s := %s', + name, + doc_from_stack_effect(*secs[0]), + ) + FUNCTIONS[name] = SymbolJoyType(name, infer_expression(of_.body), _SYM_NUMS()) #sec_Ns_math(_dictionary['product']) diff --git a/joy/parser.py b/joy/parser.py index 3d92f7a..f92bbcf 100644 --- a/joy/parser.py +++ b/joy/parser.py @@ -51,74 +51,74 @@ BLANKS = r'\s+' class Symbol(str): - '''A string class that represents Joy function names.''' - __repr__ = str.__str__ + '''A string class that represents Joy function names.''' + __repr__ = str.__str__ def text_to_expression(text): - '''Convert a string to a Joy expression. + '''Convert a string to a Joy expression. - When supplied with a string this function returns a Python datastructure - that represents the Joy datastructure described by the text expression. - Any unbalanced square brackets will raise a ParseError. + When supplied with a string this function returns a Python datastructure + that represents the Joy datastructure described by the text expression. + Any unbalanced square brackets will raise a ParseError. - :param str text: Text to convert. - :rtype: stack - :raises ParseError: if the parse fails. - ''' - return _parse(_tokenize(text)) + :param str text: Text to convert. + :rtype: stack + :raises ParseError: if the parse fails. + ''' + return _parse(_tokenize(text)) class ParseError(ValueError): - '''Raised when there is a error while parsing text.''' + '''Raised when there is a error while parsing text.''' def _tokenize(text): - '''Convert a text into a stream of tokens. + '''Convert a text into a stream of tokens. - Converts function names to Symbols. + Converts function names to Symbols. - Raise ParseError (with some of the failing text) if the scan fails. - ''' - tokens, rest = _scanner.scan(text) - if rest: - raise ParseError( - 'Scan failed at position %i, %r' - % (len(text) - len(rest), rest[:10]) - ) - return tokens + Raise ParseError (with some of the failing text) if the scan fails. + ''' + tokens, rest = _scanner.scan(text) + if rest: + raise ParseError( + 'Scan failed at position %i, %r' + % (len(text) - len(rest), rest[:10]) + ) + return tokens def _parse(tokens): - ''' - Return a stack/list expression of the tokens. - ''' - frame = [] - stack = [] - for tok in tokens: - if tok == '[': - stack.append(frame) - frame = [] - stack[-1].append(frame) - elif tok == ']': - try: - frame = stack.pop() - except IndexError: - raise ParseError('Extra closing bracket.') - frame[-1] = list_to_stack(frame[-1]) - else: - frame.append(tok) - if stack: - raise ParseError('Unclosed bracket.') - return list_to_stack(frame) + ''' + Return a stack/list expression of the tokens. + ''' + frame = [] + stack = [] + for tok in tokens: + if tok == '[': + stack.append(frame) + frame = [] + stack[-1].append(frame) + elif tok == ']': + try: + frame = stack.pop() + except IndexError: + raise ParseError('Extra closing bracket.') + frame[-1] = list_to_stack(frame[-1]) + else: + frame.append(tok) + if stack: + raise ParseError('Unclosed bracket.') + return list_to_stack(frame) _scanner = Scanner([ - (FLOAT, lambda _, token: float(token)), - (INT, lambda _, token: int(token)), - (SYMBOL, lambda _, token: Symbol(token)), - (BRACKETS, lambda _, token: token), - (STRING_DOUBLE_QUOTED, lambda _, token: token[1:-1].replace('\\"', '"')), - (STRING_SINGLE_QUOTED, lambda _, token: token[1:-1].replace("\\'", "'")), - (BLANKS, None), - ]) + (FLOAT, lambda _, token: float(token)), + (INT, lambda _, token: int(token)), + (SYMBOL, lambda _, token: Symbol(token)), + (BRACKETS, lambda _, token: token), + (STRING_DOUBLE_QUOTED, lambda _, token: token[1:-1].replace('\\"', '"')), + (STRING_SINGLE_QUOTED, lambda _, token: token[1:-1].replace("\\'", "'")), + (BLANKS, None), + ]) diff --git a/joy/utils/brutal_hackery.py b/joy/utils/brutal_hackery.py index ebfa48a..85fb05b 100644 --- a/joy/utils/brutal_hackery.py +++ b/joy/utils/brutal_hackery.py @@ -54,43 +54,43 @@ function name! Hopefully they will discover this documentation. def rename_code_object(new_name): - ''' - If you want to wrap a function in another function and have the wrapped - function's name show up in the traceback when an exception occurs in - the wrapper function, you must do this brutal hackery to change the - func.__code__.co_name attribute. Just functools.wraps() is not enough. + ''' + If you want to wrap a function in another function and have the wrapped + function's name show up in the traceback when an exception occurs in + the wrapper function, you must do this brutal hackery to change the + func.__code__.co_name attribute. Just functools.wraps() is not enough. - See: + See: - https://stackoverflow.com/questions/29919804/function-decorated-using-functools-wraps-raises-typeerror-with-the-name-of-the-w + https://stackoverflow.com/questions/29919804/function-decorated-using-functools-wraps-raises-typeerror-with-the-name-of-the-w - https://stackoverflow.com/questions/29488327/changing-the-name-of-a-generator/29488561#29488561 + https://stackoverflow.com/questions/29488327/changing-the-name-of-a-generator/29488561#29488561 - I'm just glad it's possible. - ''' - def inner(func): - name = new_name + ':' + func.__name__ - code_object = func.__code__ - return type(func)( - type(code_object)( - code_object.co_argcount, - code_object.co_nlocals, - code_object.co_stacksize, - code_object.co_flags, - code_object.co_code, - code_object.co_consts, - code_object.co_names, - code_object.co_varnames, - code_object.co_filename, - name, - code_object.co_firstlineno, - code_object.co_lnotab, - code_object.co_freevars, - code_object.co_cellvars - ), - func.__globals__, - name, - func.__defaults__, - func.__closure__ - ) - return inner + I'm just glad it's possible. + ''' + def inner(func): + name = new_name + ':' + func.__name__ + code_object = func.__code__ + return type(func)( + type(code_object)( + code_object.co_argcount, + code_object.co_nlocals, + code_object.co_stacksize, + code_object.co_flags, + code_object.co_code, + code_object.co_consts, + code_object.co_names, + code_object.co_varnames, + code_object.co_filename, + name, + code_object.co_firstlineno, + code_object.co_lnotab, + code_object.co_freevars, + code_object.co_cellvars + ), + func.__globals__, + name, + func.__defaults__, + func.__closure__ + ) + return inner diff --git a/joy/utils/compiler.py b/joy/utils/compiler.py index d152743..de442d8 100644 --- a/joy/utils/compiler.py +++ b/joy/utils/compiler.py @@ -21,49 +21,49 @@ from functools import reduce def import_yin(): - from joy.utils.generated_library import * - return locals() + from joy.utils.generated_library import * + return locals() class InfiniteStack(tuple): - def _names(): - n = 0 - while True: - m = yield Symbol('a' + str(n)) - n = n + 1 if m is None else m + def _names(): + n = 0 + while True: + m = yield Symbol('a' + str(n)) + n = n + 1 if m is None else m - _NAMES = _names() - next(_NAMES) + _NAMES = _names() + next(_NAMES) - names = lambda: next(_NAMES) - reset = lambda _self, _n=_NAMES: _n.send(-1) + names = lambda: next(_NAMES) + reset = lambda _self, _n=_NAMES: _n.send(-1) - def __init__(self, code): - self.reset() - self.code = code + def __init__(self, code): + self.reset() + self.code = code - def __iter__(self): - if not self: - new_var = self.names() - self.code.append(('pop', new_var)) - return iter((new_var, self)) + def __iter__(self): + if not self: + new_var = self.names() + self.code.append(('pop', new_var)) + return iter((new_var, self)) def I(expression): - code = [] - stack = InfiniteStack(code) + code = [] + stack = InfiniteStack(code) - while expression: - term, expression = expression - if isinstance(term, Symbol): - func = D[term] - stack, expression, _ = func(stack, expression, code) - else: - stack = term, stack + while expression: + term, expression = expression + if isinstance(term, Symbol): + func = D[term] + stack, expression, _ = func(stack, expression, code) + else: + stack = term, stack - code.append(tuple(['ret'] + list(iter_stack(stack)))) - return code + code.append(tuple(['ret'] + list(iter_stack(stack)))) + return code strtup = lambda a, b: '(%s, %s)' % (b, a) @@ -71,123 +71,123 @@ strstk = lambda rest: reduce(strtup, rest, 'stack') def code_gen(code): - #for p in code: print p - coalesce_pops(code) - lines = [] - emit = lines.append - for t in code: - tag, rest = t[0], t[1:] - if tag == 'pop': emit(strstk(rest) + ' = stack') - elif tag == 'call': emit('%s = %s%s' % rest) - elif tag == 'ret': emit('return ' + strstk(rest[::-1])) - else: - raise ValueError(tag) - return '\n'.join(' ' + line for line in lines) + #for p in code: print p + coalesce_pops(code) + lines = [] + emit = lines.append + for t in code: + tag, rest = t[0], t[1:] + if tag == 'pop': emit(strstk(rest) + ' = stack') + elif tag == 'call': emit('%s = %s%s' % rest) + elif tag == 'ret': emit('return ' + strstk(rest[::-1])) + else: + raise ValueError(tag) + return '\n'.join(' ' + line for line in lines) def coalesce_pops(code): - code.sort(key=lambda p: p[0] != 'pop') # All pops to the front. - try: index = next((i for i, t in enumerate(code) if t[0] != 'pop')) - except StopIteration: return - code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])] + code.sort(key=lambda p: p[0] != 'pop') # All pops to the front. + try: index = next((i for i, t in enumerate(code) if t[0] != 'pop')) + except StopIteration: return + code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])] def compile_yinyang(name, text): - return ''' + return ''' def %s(stack): %s ''' % (name, code_gen(I(text_to_expression(text)))) def q(): - memo = {} - def bar(type_var): - try: - res = memo[type_var] - except KeyError: - res = memo[type_var] = InfiniteStack.names() - return res - return bar + memo = {} + def bar(type_var): + try: + res = memo[type_var] + except KeyError: + res = memo[type_var] = InfiniteStack.names() + return res + return bar def type_vars_to_labels(thing, map_): - if not thing: - return thing - if not isinstance(thing, tuple): - return map_(thing) - return tuple(type_vars_to_labels(inner, map_) for inner in thing) + if not thing: + return thing + if not isinstance(thing, tuple): + return map_(thing) + return tuple(type_vars_to_labels(inner, map_) for inner in thing) def remap_inputs(in_, stack, code): - map_ = q() - while in_: - term, in_ = in_ - arg0, stack = stack - term = type_vars_to_labels(term, map_) - code.append(('call', term, '', arg0)) - return stack, map_ + map_ = q() + while in_: + term, in_ = in_ + arg0, stack = stack + term = type_vars_to_labels(term, map_) + code.append(('call', term, '', arg0)) + return stack, map_ class BinaryBuiltin(object): - def __init__(self, name): - self.name = name + def __init__(self, name): + self.name = name - def __call__(self, stack, expression, code): - in1, (in0, stack) = stack - out = InfiniteStack.names() - code.append(('call', out, self.name, (in0, in1))) - return (out, stack), expression, code + def __call__(self, stack, expression, code): + in1, (in0, stack) = stack + out = InfiniteStack.names() + code.append(('call', out, self.name, (in0, in1))) + return (out, stack), expression, code YIN = import_yin() D = { - name: SimpleFunctionWrapper(YIN[name]) - for name in ''' - ccons - cons - dup - dupd - dupdd - over - pop - popd - popdd - popop - popopd - popopdd - rolldown - rollup - swap - swons - tuck - unit - '''.split() - } + name: SimpleFunctionWrapper(YIN[name]) + for name in ''' + ccons + cons + dup + dupd + dupdd + over + pop + popd + popdd + popop + popopd + popopdd + rolldown + rollup + swap + swons + tuck + unit + '''.split() + } for name in ''' - first - first_two - fourth - rest - rrest - second - third - uncons - unswons - '''.split(): + first + first_two + fourth + rest + rrest + second + third + uncons + unswons + '''.split(): - def foo(stack, expression, code, name=name): - in_, out = YIN_STACK_EFFECTS[name] - stack, map_ = remap_inputs(in_, stack, code) - out = type_vars_to_labels(out, map_) - return concat(out, stack), expression, code + def foo(stack, expression, code, name=name): + in_, out = YIN_STACK_EFFECTS[name] + stack, map_ = remap_inputs(in_, stack, code) + out = type_vars_to_labels(out, map_) + return concat(out, stack), expression, code - foo.__name__ = name - D[name] = foo + foo.__name__ = name + D[name] = foo for name in ''' @@ -210,18 +210,18 @@ for name in ''' sub truediv '''.split(): - D[name.rstrip('-')] = BinaryBuiltin(name) + D[name.rstrip('-')] = BinaryBuiltin(name) ''' - stack - stuncons - stununcons - swaack + stack + stuncons + stununcons + swaack ''' for name in sorted(D): - print(name, end=' ') + print(name, end=' ') ## print compile_yinyang(name, name) print('-' * 100) diff --git a/joy/utils/infinite_stack.py b/joy/utils/infinite_stack.py index 979be90..3697939 100644 --- a/joy/utils/infinite_stack.py +++ b/joy/utils/infinite_stack.py @@ -3,19 +3,19 @@ from joy.parser import Symbol def _names(): - n = 0 - while True: - yield Symbol('a' + str(n)) - n += 1 + n = 0 + while True: + yield Symbol('a' + str(n)) + n += 1 class InfiniteStack(tuple): - names = lambda n=_names(): next(n) + names = lambda n=_names(): next(n) - def __iter__(self): - if not self: - return iter((self.names(), self)) + def __iter__(self): + if not self: + return iter((self.names(), self)) i = InfiniteStack() @@ -23,9 +23,9 @@ i = InfiniteStack() a, b = i lambda u: (lambda fu, u: fu * fu * u)( - (lambda u: (lambda fu, u: fu * fu)( - (lambda u: (lambda fu, u: fu * fu * u)( - (lambda u: 1)(u), u))(u), u))(u), - u) + (lambda u: (lambda fu, u: fu * fu)( + (lambda u: (lambda fu, u: fu * fu * u)( + (lambda u: 1)(u), u))(u), u))(u), + u) lambda u: (lambda fu, u: fu * fu * u)((lambda u: (lambda fu, u: fu * fu)((lambda u: (lambda fu, u: fu * fu * u)((lambda u: 1)(u), u))(u), u))(u), u) diff --git a/joy/utils/pretty_print.py b/joy/utils/pretty_print.py index 6844c64..1d636be 100644 --- a/joy/utils/pretty_print.py +++ b/joy/utils/pretty_print.py @@ -46,54 +46,54 @@ from .stack import expression_to_string, stack_to_string class TracePrinter(object): - ''' - This is what does the formatting. You instantiate it and pass the ``viewer()`` - method to the :py:func:`joy.joy.joy` function, then print it to see the - trace. - ''' + ''' + This is what does the formatting. You instantiate it and pass the ``viewer()`` + method to the :py:func:`joy.joy.joy` function, then print it to see the + trace. + ''' - def __init__(self): - self.history = [] + def __init__(self): + self.history = [] - def viewer(self, stack, expression): - ''' - Record the current stack and expression in the TracePrinter's history. - Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function. + def viewer(self, stack, expression): + ''' + Record the current stack and expression in the TracePrinter's history. + Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function. - :param stack quote: A stack. - :param stack expression: A stack. - ''' - self.history.append((stack, expression)) + :param stack quote: A stack. + :param stack expression: A stack. + ''' + self.history.append((stack, expression)) - def __str__(self): - return '\n'.join(self.go()) + def __str__(self): + return '\n'.join(self.go()) - def go(self): - ''' - Return a list of strings, one for each entry in the history, prefixed - with enough spaces to align all the interpreter dots. + def go(self): + ''' + Return a list of strings, one for each entry in the history, prefixed + with enough spaces to align all the interpreter dots. - This method is called internally by the ``__str__()`` method. + This method is called internally by the ``__str__()`` method. - :rtype: list(str) - ''' - max_stack_length = 0 - lines = [] - for stack, expression in self.history: - stack = stack_to_string(stack) - expression = expression_to_string(expression) - n = len(stack) - if n > max_stack_length: - max_stack_length = n - lines.append((n, '%s . %s' % (stack, expression))) - return [ # Prefix spaces to line up '.'s. - (' ' * (max_stack_length - length) + line) - for length, line in lines - ] + :rtype: list(str) + ''' + max_stack_length = 0 + lines = [] + for stack, expression in self.history: + stack = stack_to_string(stack) + expression = expression_to_string(expression) + n = len(stack) + if n > max_stack_length: + max_stack_length = n + lines.append((n, '%s . %s' % (stack, expression))) + return [ # Prefix spaces to line up '.'s. + (' ' * (max_stack_length - length) + line) + for length, line in lines + ] - def print_(self): - try: - print(self) - except: - print_exc() - print('Exception while printing viewer.') + def print_(self): + try: + print(self) + except: + print_exc() + print('Exception while printing viewer.') diff --git a/joy/utils/stack.py b/joy/utils/stack.py index f998e6c..34e4066 100644 --- a/joy/utils/stack.py +++ b/joy/utils/stack.py @@ -43,8 +43,8 @@ means we can directly "unpack" the expected arguments to a Joy function. For example:: - def dup((head, tail)): - return head, (head, tail) + def dup((head, tail)): + return head, (head, tail) We replace the argument "stack" by the expected structure of the stack, in this case "(head, tail)", and Python takes care of unpacking the @@ -56,9 +56,9 @@ Unfortunately, the Sphinx documentation generator, which is used to generate thi web page, doesn't handle tuples in the function parameters. And in Python 3, this syntax was removed entirely. Instead you would have to write:: - def dup(stack): - head, tail = stack - return head, (head, tail) + def dup(stack): + head, tail = stack + return head, (head, tail) We have two very simple functions, one to build up a stack from a Python @@ -74,95 +74,95 @@ printed left-to-right. These functions are written to support :doc:`../pretty`. from builtins import map def list_to_stack(el, stack=()): - '''Convert a Python list (or other sequence) to a Joy stack:: + '''Convert a Python list (or other sequence) to a Joy stack:: - [1, 2, 3] -> (1, (2, (3, ()))) + [1, 2, 3] -> (1, (2, (3, ()))) - :param list el: A Python list or other sequence (iterators and generators - won't work because ``reverse()`` is called on ``el``.) - :param stack stack: A stack, optional, defaults to the empty stack. - :rtype: stack + :param list el: A Python list or other sequence (iterators and generators + won't work because ``reverse()`` is called on ``el``.) + :param stack stack: A stack, optional, defaults to the empty stack. + :rtype: stack - ''' - for item in reversed(el): - stack = item, stack - return stack + ''' + for item in reversed(el): + stack = item, stack + return stack def iter_stack(stack): - '''Iterate through the items on the stack. + '''Iterate through the items on the stack. - :param stack stack: A stack. - :rtype: iterator - ''' - while stack: - item, stack = stack - yield item + :param stack stack: A stack. + :rtype: iterator + ''' + while stack: + item, stack = stack + yield item def stack_to_string(stack): - ''' - Return a "pretty print" string for a stack. + ''' + Return a "pretty print" string for a stack. - The items are written right-to-left:: + The items are written right-to-left:: - (top, (second, ...)) -> '... second top' + (top, (second, ...)) -> '... second top' - :param stack stack: A stack. - :rtype: str - ''' - f = lambda stack: reversed(list(iter_stack(stack))) - return _to_string(stack, f) + :param stack stack: A stack. + :rtype: str + ''' + f = lambda stack: reversed(list(iter_stack(stack))) + return _to_string(stack, f) def expression_to_string(expression): - ''' - Return a "pretty print" string for a expression. + ''' + Return a "pretty print" string for a expression. - The items are written left-to-right:: + The items are written left-to-right:: - (top, (second, ...)) -> 'top second ...' + (top, (second, ...)) -> 'top second ...' - :param stack expression: A stack. - :rtype: str - ''' - return _to_string(expression, iter_stack) + :param stack expression: A stack. + :rtype: str + ''' + return _to_string(expression, iter_stack) def _to_string(stack, f): - if not isinstance(stack, tuple): return repr(stack) - if not stack: return '' # shortcut - return ' '.join(map(_s, f(stack))) + if not isinstance(stack, tuple): return repr(stack) + if not stack: return '' # shortcut + return ' '.join(map(_s, f(stack))) _s = lambda s: ( - '[%s]' % expression_to_string(s) if isinstance(s, tuple) - else repr(s) - ) + '[%s]' % expression_to_string(s) if isinstance(s, tuple) + else repr(s) + ) def concat(quote, expression): - '''Concatinate quote onto expression. + '''Concatinate quote onto expression. - In joy [1 2] [3 4] would become [1 2 3 4]. + In joy [1 2] [3 4] would become [1 2 3 4]. - :param stack quote: A stack. - :param stack expression: A stack. - :raises RuntimeError: if quote is larger than sys.getrecursionlimit(). - :rtype: stack - ''' - # This is the fastest implementation, but will trigger - # RuntimeError: maximum recursion depth exceeded - # on quotes longer than sys.getrecursionlimit(). + :param stack quote: A stack. + :param stack expression: A stack. + :raises RuntimeError: if quote is larger than sys.getrecursionlimit(). + :rtype: stack + ''' + # This is the fastest implementation, but will trigger + # RuntimeError: maximum recursion depth exceeded + # on quotes longer than sys.getrecursionlimit(). - return (quote[0], concat(quote[1], expression)) if quote else expression + return (quote[0], concat(quote[1], expression)) if quote else expression - # Original implementation. + # Original implementation. ## return list_to_stack(list(iter_stack(quote)), expression) - # In-lining is slightly faster (and won't break the - # recursion limit on long quotes.) + # In-lining is slightly faster (and won't break the + # recursion limit on long quotes.) ## temp = [] ## while quote: @@ -175,23 +175,23 @@ def concat(quote, expression): def pick(stack, n): - ''' - Return the nth item on the stack. + ''' + Return the nth item on the stack. - :param stack stack: A stack. - :param int n: An index into the stack. - :raises ValueError: if ``n`` is less than zero. - :raises IndexError: if ``n`` is equal to or greater than the length of ``stack``. - :rtype: whatever - ''' - if n < 0: - raise ValueError - while True: - try: - item, stack = stack - except ValueError: - raise IndexError - n -= 1 - if n < 0: - break - return item + :param stack stack: A stack. + :param int n: An index into the stack. + :raises ValueError: if ``n`` is less than zero. + :raises IndexError: if ``n`` is equal to or greater than the length of ``stack``. + :rtype: whatever + ''' + if n < 0: + raise ValueError + while True: + try: + item, stack = stack + except ValueError: + raise IndexError + n -= 1 + if n < 0: + break + return item diff --git a/joy/utils/types.py b/joy/utils/types.py index e30be43..c22cc3b 100644 --- a/joy/utils/types.py +++ b/joy/utils/types.py @@ -32,137 +32,137 @@ from collections import Counter from itertools import chain, product from inspect import stack as inspect_stack from joy.utils.stack import ( - concat, - expression_to_string, - list_to_stack, - stack_to_string, - ) + concat, + expression_to_string, + list_to_stack, + stack_to_string, + ) from joy.parser import Symbol, text_to_expression class AnyJoyType(object): - ''' - Joy type variable. Represents any Joy value. - ''' + ''' + Joy type variable. Represents any Joy value. + ''' - accept = tuple, int, float, int, complex, str, bool, Symbol - prefix = 'a' + accept = tuple, int, float, int, complex, str, bool, Symbol + prefix = 'a' - def __init__(self, number): - self.number = number + def __init__(self, number): + self.number = number - def __repr__(self): - return self.prefix + str(self.number) + def __repr__(self): + return self.prefix + str(self.number) - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and other.prefix == self.prefix - and other.number == self.number - ) + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and other.prefix == self.prefix + and other.number == self.number + ) - def __ge__(self, other): - return ( - issubclass(other.__class__, self.__class__) - or isinstance(other, self.accept) - ) + def __ge__(self, other): + return ( + issubclass(other.__class__, self.__class__) + or isinstance(other, self.accept) + ) - def __le__(self, other): - # 'a string' >= AnyJoyType() should be False. - return issubclass(self.__class__, other.__class__) + def __le__(self, other): + # 'a string' >= AnyJoyType() should be False. + return issubclass(self.__class__, other.__class__) - def __add__(self, other): - return self.__class__(self.number + other) - __radd__ = __add__ + def __add__(self, other): + return self.__class__(self.number + other) + __radd__ = __add__ - def __hash__(self): - return hash(repr(self)) + def __hash__(self): + return hash(repr(self)) class BooleanJoyType(AnyJoyType): - accept = bool - prefix = 'b' + accept = bool + prefix = 'b' class NumberJoyType(AnyJoyType): - accept = bool, int, float, complex - prefix = 'n' + accept = bool, int, float, complex + prefix = 'n' class FloatJoyType(NumberJoyType): - accept = float - prefix = 'f' + accept = float + prefix = 'f' class IntJoyType(FloatJoyType): - accept = int - prefix = 'i' + accept = int + prefix = 'i' class TextJoyType(AnyJoyType): - accept = basestring - prefix = 't' + accept = basestring + prefix = 't' class StackJoyType(AnyJoyType): - accept = tuple - prefix = 's' + accept = tuple + prefix = 's' - def __bool__(self): - # Imitate () at the end of cons list. - return False + def __bool__(self): + # Imitate () at the end of cons list. + return False class KleeneStar(object): - u''' - A sequence of zero or more `AnyJoyType` variables would be: + u''' + A sequence of zero or more `AnyJoyType` variables would be: - A* + A* - The `A*` works by splitting the universe into two alternate histories: + The `A*` works by splitting the universe into two alternate histories: - A* → ∅ + A* → ∅ - A* → A A* + A* → A A* - The Kleene star variable disappears in one universe, and in the other - it turns into an `AnyJoyType` variable followed by itself again. + The Kleene star variable disappears in one universe, and in the other + it turns into an `AnyJoyType` variable followed by itself again. - We have to return all universes (represented by their substitution - dicts, the "unifiers") that don't lead to type conflicts. - ''' + We have to return all universes (represented by their substitution + dicts, the "unifiers") that don't lead to type conflicts. + ''' - kind = AnyJoyType + kind = AnyJoyType - def __init__(self, number): - assert number - self.number = number - self.count = 0 - self.prefix = repr(self) + def __init__(self, number): + assert number + self.number = number + self.count = 0 + self.prefix = repr(self) - def __repr__(self): - return '%s%i*' % (self.kind.prefix, self.number) + def __repr__(self): + return '%s%i*' % (self.kind.prefix, self.number) - def another(self): - self.count += 1 - return self.kind(10000 * self.number + self.count) + def another(self): + self.count += 1 + return self.kind(10000 * self.number + self.count) - def __eq__(self, other): - return ( - isinstance(other, self.__class__) - and other.number == self.number - ) + def __eq__(self, other): + return ( + isinstance(other, self.__class__) + and other.number == self.number + ) - def __ge__(self, other): - return self.kind >= other.kind + def __ge__(self, other): + return self.kind >= other.kind - def __add__(self, other): - return self.__class__(self.number + other) - __radd__ = __add__ - - def __hash__(self): - return hash(repr(self)) + def __add__(self, other): + return self.__class__(self.number + other) + __radd__ = __add__ + + def __hash__(self): + return hash(repr(self)) class AnyStarJoyType(KleeneStar): kind = AnyJoyType @@ -175,395 +175,395 @@ class TextStarJoyType(KleeneStar): kind = TextJoyType class FunctionJoyType(AnyJoyType): - def __init__(self, name, sec, number): - self.name = name - self.stack_effects = sec - self.number = number + def __init__(self, name, sec, number): + self.name = name + self.stack_effects = sec + self.number = number - def __add__(self, other): - return self - __radd__ = __add__ + def __add__(self, other): + return self + __radd__ = __add__ - def __repr__(self): - return self.name + def __repr__(self): + return self.name class SymbolJoyType(FunctionJoyType): - ''' - Represent non-combinator functions. + ''' + Represent non-combinator functions. - These type variables carry the stack effect comments and can - appear in expressions (as in quoted programs.) - ''' - prefix = 'F' + These type variables carry the stack effect comments and can + appear in expressions (as in quoted programs.) + ''' + prefix = 'F' class CombinatorJoyType(FunctionJoyType): - ''' - Represent combinators. - - These type variables carry Joy functions that implement the - behaviour of Joy combinators and they can appear in expressions. - For simple combinators the implementation functions can be the - combinators themselves. + ''' + Represent combinators. + + These type variables carry Joy functions that implement the + behaviour of Joy combinators and they can appear in expressions. + For simple combinators the implementation functions can be the + combinators themselves. - These types can also specify a stack effect (input side only) to - guard against being used on invalid types. - ''' + These types can also specify a stack effect (input side only) to + guard against being used on invalid types. + ''' - prefix = 'C' + prefix = 'C' - def __init__(self, name, sec, number, expect=None): - super(CombinatorJoyType, self).__init__(name, sec, number) - self.expect = expect + def __init__(self, name, sec, number, expect=None): + super(CombinatorJoyType, self).__init__(name, sec, number) + self.expect = expect - def enter_guard(self, f): - if self.expect is None: - return f - g = self.expect, self.expect - new_f = list(poly_compose(f, g, ())) - assert len(new_f) == 1, repr(new_f) - return new_f[0][1] + def enter_guard(self, f): + if self.expect is None: + return f + g = self.expect, self.expect + new_f = list(poly_compose(f, g, ())) + assert len(new_f) == 1, repr(new_f) + return new_f[0][1] class JoyTypeError(Exception): pass def reify(meaning, name, seen=None): - ''' - Apply substitution dict to term, returning new term. - ''' - if isinstance(name, tuple): - return tuple(reify(meaning, inner) for inner in name) - safety = 101 - while name in meaning and safety: - safety -= 1 - name = meaning[name] - if not safety: - raise ValueError('Cycle in substitution dict: %s' % (meaning,)) - return name + ''' + Apply substitution dict to term, returning new term. + ''' + if isinstance(name, tuple): + return tuple(reify(meaning, inner) for inner in name) + safety = 101 + while name in meaning and safety: + safety -= 1 + name = meaning[name] + if not safety: + raise ValueError('Cycle in substitution dict: %s' % (meaning,)) + return name def relabel(left, right): - ''' - Re-number type variables to avoid collisions between stack effects. - ''' - return left, _1000(right) + ''' + Re-number type variables to avoid collisions between stack effects. + ''' + return left, _1000(right) def _1000(right): - if isinstance(right, Symbol): - return right - if not isinstance(right, tuple): - return 1000 + right - return tuple(_1000(n) for n in right) + if isinstance(right, Symbol): + return right + if not isinstance(right, tuple): + return 1000 + right + return tuple(_1000(n) for n in right) def delabel(f, seen=None, c=None): - ''' - Fix up type variable numbers after relabel(). - ''' - if seen is None: - assert c is None - seen, c = {}, Counter() + ''' + Fix up type variable numbers after relabel(). + ''' + if seen is None: + assert c is None + seen, c = {}, Counter() - try: - return seen[f] - except KeyError: - pass + try: + return seen[f] + except KeyError: + pass - if not isinstance(f, tuple): - try: - seen[f] = f.__class__(c[f.prefix] + 1) - except (TypeError, # FunctionJoyTypes break this. - AttributeError): # Symbol - seen[f] = f - else: - c[f.prefix] += 1 - return seen[f] + if not isinstance(f, tuple): + try: + seen[f] = f.__class__(c[f.prefix] + 1) + except (TypeError, # FunctionJoyTypes break this. + AttributeError): # Symbol + seen[f] = f + else: + c[f.prefix] += 1 + return seen[f] - return tuple(delabel(inner, seen, c) for inner in f) + return tuple(delabel(inner, seen, c) for inner in f) def uni_unify(u, v, s=None): - ''' - Return a substitution dict representing a unifier for u and v. - ''' - if s is None: - s = {} - elif s: - u = reify(s, u) - v = reify(s, v) + ''' + Return a substitution dict representing a unifier for u and v. + ''' + if s is None: + s = {} + elif s: + u = reify(s, u) + v = reify(s, v) - if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType): - if u >= v: - s[u] = v - elif v >= u: - s[v] = u - else: - raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) + if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType): + if u >= v: + s[u] = v + elif v >= u: + s[v] = u + else: + raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) - elif isinstance(u, tuple) and isinstance(v, tuple): - if len(u) != len(v) != 2: - raise ValueError(repr((u, v))) # Bad input. - (a, b), (c, d) = u, v - s = uni_unify(b, d, uni_unify(a, c, s)) + elif isinstance(u, tuple) and isinstance(v, tuple): + if len(u) != len(v) != 2: + raise ValueError(repr((u, v))) # Bad input. + (a, b), (c, d) = u, v + s = uni_unify(b, d, uni_unify(a, c, s)) - elif isinstance(v, tuple): - if not _stacky(u): - raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) - s[u] = v + elif isinstance(v, tuple): + if not _stacky(u): + raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) + s[u] = v - elif isinstance(u, tuple): - if not _stacky(v): - raise JoyTypeError('Cannot unify %r and %r.' % (v, u)) - s[v] = u + elif isinstance(u, tuple): + if not _stacky(v): + raise JoyTypeError('Cannot unify %r and %r.' % (v, u)) + s[v] = u - else: - raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) + else: + raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) - return s + return s def _log_uni(U): - def inner(u, v, s=None): - _log.debug( - '%3i %s U %s w/ %s', - len(inspect_stack()), u, v, s, - ) - res = U(u, v, s) - _log.debug( - '%3i %s U %s w/ %s => %s', - len(inspect_stack()), u, v, s, res, - ) - return res - return inner + def inner(u, v, s=None): + _log.debug( + '%3i %s U %s w/ %s', + len(inspect_stack()), u, v, s, + ) + res = U(u, v, s) + _log.debug( + '%3i %s U %s w/ %s => %s', + len(inspect_stack()), u, v, s, res, + ) + return res + return inner @_log_uni def unify(u, v, s=None): - ''' - Return a tuple of substitution dicts representing unifiers for u and v. - ''' - if s is None: - s = {} - elif s: - u = reify(s, u) - v = reify(s, v) + ''' + Return a tuple of substitution dicts representing unifiers for u and v. + ''' + if s is None: + s = {} + elif s: + u = reify(s, u) + v = reify(s, v) - if u == v: - res = s, + if u == v: + res = s, - elif isinstance(u, tuple) and isinstance(v, tuple): - if len(u) != 2 or len(v) != 2: - if _that_one_special_case(u, v): - return s, - raise ValueError(repr((u, v))) # Bad input. - + elif isinstance(u, tuple) and isinstance(v, tuple): + if len(u) != 2 or len(v) != 2: + if _that_one_special_case(u, v): + return s, + raise ValueError(repr((u, v))) # Bad input. + - (a, b), (c, d) = v, u - if isinstance(a, KleeneStar): - if isinstance(c, KleeneStar): - s = _lil_uni(a, c, s) # Attempt to unify the two K-stars. - res = unify(d, b, s[0]) + (a, b), (c, d) = v, u + if isinstance(a, KleeneStar): + if isinstance(c, KleeneStar): + s = _lil_uni(a, c, s) # Attempt to unify the two K-stars. + res = unify(d, b, s[0]) - else: - # Two universes, in one the Kleene star disappears and - # unification continues without it... - s0 = unify(u, b) - - # In the other it spawns a new variable. - s1 = unify(u, (a.another(), v)) - - res = s0 + s1 - for sn in res: - sn.update(s) + else: + # Two universes, in one the Kleene star disappears and + # unification continues without it... + s0 = unify(u, b) + + # In the other it spawns a new variable. + s1 = unify(u, (a.another(), v)) + + res = s0 + s1 + for sn in res: + sn.update(s) - elif isinstance(c, KleeneStar): - res = unify(v, d) + unify(v, (c.another(), u)) - for sn in res: - sn.update(s) + elif isinstance(c, KleeneStar): + res = unify(v, d) + unify(v, (c.another(), u)) + for sn in res: + sn.update(s) - else: - res = tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s))) + else: + res = tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s))) - elif isinstance(v, tuple): - if not _stacky(u): - raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) - s[u] = v - res = s, + elif isinstance(v, tuple): + if not _stacky(u): + raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) + s[u] = v + res = s, - elif isinstance(u, tuple): - if not _stacky(v): - raise JoyTypeError('Cannot unify %r and %r.' % (v, u)) - s[v] = u - res = s, + elif isinstance(u, tuple): + if not _stacky(v): + raise JoyTypeError('Cannot unify %r and %r.' % (v, u)) + s[v] = u + res = s, - else: - res = _lil_uni(u, v, s) + else: + res = _lil_uni(u, v, s) - return res + return res def _that_one_special_case(u, v): - ''' - Handle e.g. ((), (n1*, s1)) when type-checking sum, product, etc... - ''' - return ( - u == () - and len(v) == 2 - and isinstance(v[0], KleeneStar) - and isinstance(v[1], StackJoyType) - ) + ''' + Handle e.g. ((), (n1*, s1)) when type-checking sum, product, etc... + ''' + return ( + u == () + and len(v) == 2 + and isinstance(v[0], KleeneStar) + and isinstance(v[1], StackJoyType) + ) def flatten(g): - return list(chain.from_iterable(g)) + return list(chain.from_iterable(g)) def _lil_uni(u, v, s): - if u >= v: - s[u] = v - return s, - if v >= u: - s[v] = u - return s, - raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) + if u >= v: + s[u] = v + return s, + if v >= u: + s[v] = u + return s, + raise JoyTypeError('Cannot unify %r and %r.' % (u, v)) def _stacky(thing): - return thing.__class__ in {AnyJoyType, StackJoyType} + return thing.__class__ in {AnyJoyType, StackJoyType} def _compose(f, g): - ''' - Return the stack effect of the composition of two stack effects. - ''' - # Relabel, unify, update, delabel. - (f_in, f_out), (g_in, g_out) = relabel(f, g) - fg = reify(uni_unify(g_in, f_out), (f_in, g_out)) - return delabel(fg) + ''' + Return the stack effect of the composition of two stack effects. + ''' + # Relabel, unify, update, delabel. + (f_in, f_out), (g_in, g_out) = relabel(f, g) + fg = reify(uni_unify(g_in, f_out), (f_in, g_out)) + return delabel(fg) def compose(*functions): - ''' - Return the stack effect of the composition of some of stack effects. - ''' - return reduce(_compose, functions) + ''' + Return the stack effect of the composition of some of stack effects. + ''' + return reduce(_compose, functions) def compilable(f): - ''' - Return True if a stack effect represents a function that can be - automatically compiled (to Python), False otherwise. - ''' - return isinstance(f, tuple) and all(map(compilable, f)) or _stacky(f) + ''' + Return True if a stack effect represents a function that can be + automatically compiled (to Python), False otherwise. + ''' + return isinstance(f, tuple) and all(map(compilable, f)) or _stacky(f) def doc_from_stack_effect(inputs, outputs=('??', ())): - ''' - Return a crude string representation of a stack effect. - ''' - switch = [False] # Do we need to display the '...' for the rest of the main stack? - i, o = _f(inputs, switch), _f(outputs, switch) - if switch[0]: - i.append('...') - o.append('...') - return '(%s--%s)' % ( - ' '.join(reversed([''] + i)), - ' '.join(reversed(o + [''])), - ) + ''' + Return a crude string representation of a stack effect. + ''' + switch = [False] # Do we need to display the '...' for the rest of the main stack? + i, o = _f(inputs, switch), _f(outputs, switch) + if switch[0]: + i.append('...') + o.append('...') + return '(%s--%s)' % ( + ' '.join(reversed([''] + i)), + ' '.join(reversed(o + [''])), + ) def _f(term, switch): - a = [] - while term and isinstance(term, tuple): - item, term = term - a.append(item) - assert isinstance(term, (tuple, StackJoyType)), repr(term) - a = [_to_str(i, term, switch) for i in a] - return a + a = [] + while term and isinstance(term, tuple): + item, term = term + a.append(item) + assert isinstance(term, (tuple, StackJoyType)), repr(term) + a = [_to_str(i, term, switch) for i in a] + return a def _to_str(term, stack, switch): - if not isinstance(term, tuple): - if term == stack: - switch[0] = True - return '[...]' - return ( - '[...%i]' % term.number - if isinstance(term, StackJoyType) - else str(term) - ) + if not isinstance(term, tuple): + if term == stack: + switch[0] = True + return '[...]' + return ( + '[...%i]' % term.number + if isinstance(term, StackJoyType) + else str(term) + ) - a = [] - while term and isinstance(term, tuple): - item, term = term - a.append(_to_str(item, stack, switch)) - assert isinstance(term, (tuple, StackJoyType)), repr(term) - if term == stack: - switch[0] = True - end = '' if term == () else '...' - #end = '...' - else: - end = '' if term == () else '...%i' % term.number - a.append(end) - return '[%s]' % ' '.join(a) + a = [] + while term and isinstance(term, tuple): + item, term = term + a.append(_to_str(item, stack, switch)) + assert isinstance(term, (tuple, StackJoyType)), repr(term) + if term == stack: + switch[0] = True + end = '' if term == () else '...' + #end = '...' + else: + end = '' if term == () else '...%i' % term.number + a.append(end) + return '[%s]' % ' '.join(a) def compile_(name, f, doc=None): - ''' - Return a string of Python code implementing the function described - by the stack effect. If no doc string is passed doc_from_stack_effect() - is used to generate one. - ''' - i, o = f - if doc is None: - doc = doc_from_stack_effect(i, o) - return '''def %s(stack): - """ - :: + ''' + Return a string of Python code implementing the function described + by the stack effect. If no doc string is passed doc_from_stack_effect() + is used to generate one. + ''' + i, o = f + if doc is None: + doc = doc_from_stack_effect(i, o) + return '''def %s(stack): + """ + :: - %s + %s - """ - %s = stack - return %s''' % (name, doc, i, o) + """ + %s = stack + return %s''' % (name, doc, i, o) def _poly_compose(f, g, e): - (f_in, f_out), (g_in, g_out) = f, g - for s in unify(g_in, f_out): - yield reify(s, (e, (f_in, g_out))) + (f_in, f_out), (g_in, g_out) = f, g + for s in unify(g_in, f_out): + yield reify(s, (e, (f_in, g_out))) def poly_compose(f, g, e): - ''' - Yield the stack effects of the composition of two stack effects. An - expression is carried along and updated and yielded. - ''' - f, g = relabel(f, g) - for fg in _poly_compose(f, g, e): - yield delabel(fg) + ''' + Yield the stack effects of the composition of two stack effects. An + expression is carried along and updated and yielded. + ''' + f, g = relabel(f, g) + for fg in _poly_compose(f, g, e): + yield delabel(fg) def _meta_compose(F, G, e): - for f, g in product(F, G): - try: - for result in poly_compose(f, g, e): yield result - except JoyTypeError: - pass + for f, g in product(F, G): + try: + for result in poly_compose(f, g, e): yield result + except JoyTypeError: + pass def meta_compose(F, G, e): - ''' - Yield the stack effects of the composition of two lists of stack - effects. An expression is carried along and updated and yielded. - ''' - res = sorted(set(_meta_compose(F, G, e))) - if not res: - raise JoyTypeError('Cannot unify %r and %r.' % (F, G)) - return res + ''' + Yield the stack effects of the composition of two lists of stack + effects. An expression is carried along and updated and yielded. + ''' + res = sorted(set(_meta_compose(F, G, e))) + if not res: + raise JoyTypeError('Cannot unify %r and %r.' % (F, G)) + return res _S0 = StackJoyType(0) @@ -571,115 +571,115 @@ ID = _S0, _S0 # Identity function. def _infer(e, F=ID): - if __debug__: - _log_it(e, F) - if not e: - return [F] + if __debug__: + _log_it(e, F) + if not e: + return [F] - n, e = e + n, e = e - if isinstance(n, SymbolJoyType): - eFG = meta_compose([F], n.stack_effects, e) - res = flatten(_infer(e, Fn) for e, Fn in eFG) + if isinstance(n, SymbolJoyType): + eFG = meta_compose([F], n.stack_effects, e) + res = flatten(_infer(e, Fn) for e, Fn in eFG) - elif isinstance(n, CombinatorJoyType): - fi, fo = n.enter_guard(F) - res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects) + elif isinstance(n, CombinatorJoyType): + fi, fo = n.enter_guard(F) + res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects) - elif isinstance(n, Symbol): - if n in FUNCTIONS: - res =_infer((FUNCTIONS[n], e), F) - else: - raise JoyTypeError(n) - # print n - # func = joy.library._dictionary[n] - # res = _interpret(func, F[0], F[1], e) + elif isinstance(n, Symbol): + if n in FUNCTIONS: + res =_infer((FUNCTIONS[n], e), F) + else: + raise JoyTypeError(n) + # print n + # func = joy.library._dictionary[n] + # res = _interpret(func, F[0], F[1], e) - else: - fi, fo = F - res = _infer(e, (fi, (n, fo))) + else: + fi, fo = F + res = _infer(e, (fi, (n, fo))) - return res + return res def _interpret(f, fi, fo, e): - new_fo, ee, _ = f(fo, e, {}) - ee = reify(FUNCTIONS, ee) # Fix Symbols. - new_F = fi, new_fo - return _infer(ee, new_F) + new_fo, ee, _ = f(fo, e, {}) + ee = reify(FUNCTIONS, ee) # Fix Symbols. + new_F = fi, new_fo + return _infer(ee, new_F) def _log_it(e, F): - _log.log( - 15, - u'%3i %s ∘ %s', - len(inspect_stack()), - doc_from_stack_effect(*F), - expression_to_string(e), - ) + _log.log( + 15, + u'%3i %s ∘ %s', + len(inspect_stack()), + doc_from_stack_effect(*F), + expression_to_string(e), + ) def infer(*expression): - ''' - Return a list of stack effects for a Joy expression. + ''' + Return a list of stack effects for a Joy expression. - For example:: + For example:: - h = infer(pop, swap, rolldown, rest, rest, cons, cons) - for fi, fo in h: - print doc_from_stack_effect(fi, fo) + h = infer(pop, swap, rolldown, rest, rest, cons, cons) + for fi, fo in h: + print doc_from_stack_effect(fi, fo) - Prints:: + Prints:: - ([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1]) + ([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1]) - ''' - return sorted(set(_infer(list_to_stack(expression)))) + ''' + return sorted(set(_infer(list_to_stack(expression)))) def infer_string(string): - e = reify(FUNCTIONS, text_to_expression(string)) # Fix Symbols. - return sorted(set(_infer(e))) + e = reify(FUNCTIONS, text_to_expression(string)) # Fix Symbols. + return sorted(set(_infer(e))) def infer_expression(expression): - e = reify(FUNCTIONS, expression) # Fix Symbols. - return sorted(set(_infer(e))) + e = reify(FUNCTIONS, expression) # Fix Symbols. + return sorted(set(_infer(e))) def type_check(name, stack): - ''' - Trinary predicate. True if named function type-checks, False if it - fails, None if it's indeterminate (because I haven't entered it into - the FUNCTIONS dict yet.) - ''' - try: - func = FUNCTIONS[name] - except KeyError: - return # None, indicating unknown - if isinstance(func, SymbolJoyType): - secs = func.stack_effects - elif isinstance(func, CombinatorJoyType): - if func.expect is None: - return # None, indicating unknown - secs = [(func.expect, ())] - else: - raise TypeError(repr(func)) # wtf? - for fi, fo in secs: - try: - unify(fi, stack) - except (JoyTypeError, ValueError): - continue - except: - _log.exception( - 'Type-checking %s %s against %s', - name, - doc_from_stack_effect(fi, fo), - stack_to_string(stack), - ) - continue - return True - return False + ''' + Trinary predicate. True if named function type-checks, False if it + fails, None if it's indeterminate (because I haven't entered it into + the FUNCTIONS dict yet.) + ''' + try: + func = FUNCTIONS[name] + except KeyError: + return # None, indicating unknown + if isinstance(func, SymbolJoyType): + secs = func.stack_effects + elif isinstance(func, CombinatorJoyType): + if func.expect is None: + return # None, indicating unknown + secs = [(func.expect, ())] + else: + raise TypeError(repr(func)) # wtf? + for fi, fo in secs: + try: + unify(fi, stack) + except (JoyTypeError, ValueError): + continue + except: + _log.exception( + 'Type-checking %s %s against %s', + name, + doc_from_stack_effect(fi, fo), + stack_to_string(stack), + ) + continue + return True + return False FUNCTIONS = {} # Polytypes (lists of stack effects.) @@ -687,70 +687,70 @@ _functions = {} # plain ol' stack effects. def __(*seq): - stack = StackJoyType(23) - for item in seq: stack = item, stack - return stack + stack = StackJoyType(23) + for item in seq: stack = item, stack + return stack def stack_effect(*inputs): - def _stack_effect(*outputs): - def _apply_to(function): - i, o = _functions[function.name] = __(*inputs), __(*outputs) - d = doc_from_stack_effect(i, o) - function.__doc__ += ( - '\nStack effect::\n\n ' # '::' for Sphinx docs. - + d - ) - _log.info('Setting stack effect for %s := %s', function.name, d) - return function - return _apply_to - return _stack_effect + def _stack_effect(*outputs): + def _apply_to(function): + i, o = _functions[function.name] = __(*inputs), __(*outputs) + d = doc_from_stack_effect(i, o) + function.__doc__ += ( + '\nStack effect::\n\n ' # '::' for Sphinx docs. + + d + ) + _log.info('Setting stack effect for %s := %s', function.name, d) + return function + return _apply_to + return _stack_effect def ef(*inputs): - def _ef(*outputs): - return __(*inputs), __(*outputs) - return _ef + def _ef(*outputs): + return __(*inputs), __(*outputs) + return _ef def combinator_effect(number, *expect): - def _combinator_effect(c): - e = __(*expect) if expect else None - FUNCTIONS[c.name] = CombinatorJoyType(c.name, [c], number, e) - if e: - sec = doc_from_stack_effect(e) - _log.info('Setting stack EXPECT for combinator %s := %s', c.name, sec) - return c - return _combinator_effect + def _combinator_effect(c): + e = __(*expect) if expect else None + FUNCTIONS[c.name] = CombinatorJoyType(c.name, [c], number, e) + if e: + sec = doc_from_stack_effect(e) + _log.info('Setting stack EXPECT for combinator %s := %s', c.name, sec) + return c + return _combinator_effect def show(DEFS): - for name, stack_effect_comment in sorted(DEFS.items()): - t = ' *'[compilable(stack_effect_comment)] - print(name, '=', doc_from_stack_effect(*stack_effect_comment), t) + for name, stack_effect_comment in sorted(DEFS.items()): + t = ' *'[compilable(stack_effect_comment)] + print(name, '=', doc_from_stack_effect(*stack_effect_comment), t) def generate_library_code(DEFS, f=None): - if f is None: - import sys - f = sys.stdout - print('# GENERATED FILE. DO NOT EDIT.\n', file=f) - for name, stack_effect_comment in sorted(DEFS.items()): - if not compilable(stack_effect_comment): - continue - print(file=f) - print(compile_(name, stack_effect_comment), file=f) - print(file=f) + if f is None: + import sys + f = sys.stdout + print('# GENERATED FILE. DO NOT EDIT.\n', file=f) + for name, stack_effect_comment in sorted(DEFS.items()): + if not compilable(stack_effect_comment): + continue + print(file=f) + print(compile_(name, stack_effect_comment), file=f) + print(file=f) def poly_combinator_effect(number, effect_funcs, *expect): - def _poly_combinator_effect(c): - e = __(*expect) if expect else None - FUNCTIONS[c.name] = CombinatorJoyType(c.name, effect_funcs, number, e) - if e: - _log.info('Setting stack EXPECT for combinator %s := %s', c.name, e) - return c - return _poly_combinator_effect + def _poly_combinator_effect(c): + e = __(*expect) if expect else None + FUNCTIONS[c.name] = CombinatorJoyType(c.name, effect_funcs, number, e) + if e: + _log.info('Setting stack EXPECT for combinator %s := %s', c.name, e) + return c + return _poly_combinator_effect #FUNCTIONS['branch'].expect = s7, (s6, (b1, s5)) diff --git a/joy/vui/core.py b/joy/vui/core.py index 1a87588..ca61961 100644 --- a/joy/vui/core.py +++ b/joy/vui/core.py @@ -48,18 +48,18 @@ GREEN = 70, 200, 70 MOUSE_EVENTS = frozenset({ - pygame.MOUSEMOTION, - pygame.MOUSEBUTTONDOWN, - pygame.MOUSEBUTTONUP - }) + pygame.MOUSEMOTION, + pygame.MOUSEBUTTONDOWN, + pygame.MOUSEBUTTONUP + }) 'PyGame mouse events.' ARROW_KEYS = frozenset({ - pygame.K_UP, - pygame.K_DOWN, - pygame.K_LEFT, - pygame.K_RIGHT - }) + pygame.K_UP, + pygame.K_DOWN, + pygame.K_LEFT, + pygame.K_RIGHT + }) 'PyGame arrow key events.' @@ -82,201 +82,201 @@ SUCCESS = 1 class Message(object): - '''Message base class. Contains ``sender`` field.''' - def __init__(self, sender): - self.sender = sender + '''Message base class. Contains ``sender`` field.''' + def __init__(self, sender): + self.sender = sender class CommandMessage(Message): - '''For commands, adds ``command`` field.''' - def __init__(self, sender, command): - Message.__init__(self, sender) - self.command = command + '''For commands, adds ``command`` field.''' + def __init__(self, sender, command): + Message.__init__(self, sender) + self.command = command class ModifyMessage(Message): - ''' - For when resources are modified, adds ``subject`` and ``details`` - fields. - ''' - def __init__(self, sender, subject, **details): - Message.__init__(self, sender) - self.subject = subject - self.details = details + ''' + For when resources are modified, adds ``subject`` and ``details`` + fields. + ''' + def __init__(self, sender, subject, **details): + Message.__init__(self, sender) + self.subject = subject + self.details = details class OpenMessage(Message): - ''' - For when resources are modified, adds ``name``, content_id``, - ``status``, and ``traceback`` fields. - ''' - def __init__(self, sender, name): - Message.__init__(self, sender) - self.name = name - self.content_id = self.thing = None - self.status = PENDING - self.traceback = None + ''' + For when resources are modified, adds ``name``, content_id``, + ``status``, and ``traceback`` fields. + ''' + def __init__(self, sender, name): + Message.__init__(self, sender) + self.name = name + self.content_id = self.thing = None + self.status = PENDING + self.traceback = None class PersistMessage(Message): - ''' - For when resources are modified, adds ``content_id`` and ``details`` - fields. - ''' - def __init__(self, sender, content_id, **details): - Message.__init__(self, sender) - self.content_id = content_id - self.details = details + ''' + For when resources are modified, adds ``content_id`` and ``details`` + fields. + ''' + def __init__(self, sender, content_id, **details): + Message.__init__(self, sender) + self.content_id = content_id + self.details = details class ShutdownMessage(Message): - '''Signals that the system is shutting down.''' + '''Signals that the system is shutting down.''' # Joy Interpreter & Context class World(object): - ''' - This object contains the system context, the stack, dictionary, a - reference to the display broadcast method, and the log. - ''' + ''' + This object contains the system context, the stack, dictionary, a + reference to the display broadcast method, and the log. + ''' - def __init__(self, stack_id, stack_holder, dictionary, notify, log): - self.stack_holder = stack_holder - self.dictionary = dictionary - self.notify = notify - self.stack_id = stack_id - self.log = log.lines - self.log_id = log.content_id + def __init__(self, stack_id, stack_holder, dictionary, notify, log): + self.stack_holder = stack_holder + self.dictionary = dictionary + self.notify = notify + self.stack_id = stack_id + self.log = log.lines + self.log_id = log.content_id - def handle(self, message): - ''' - Deal with updates to the stack and commands. - ''' - if (isinstance(message, ModifyMessage) - and message.subject is self.stack_holder - ): - self._log_lines('', '%s <-' % self.format_stack()) - if not isinstance(message, CommandMessage): - return - c, s, d = message.command, self.stack_holder[0], self.dictionary - self._log_lines('', '-> %s' % (c,)) - self.stack_holder[0], _, self.dictionary = run(c, s, d) - mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id) - self.notify(mm) + def handle(self, message): + ''' + Deal with updates to the stack and commands. + ''' + if (isinstance(message, ModifyMessage) + and message.subject is self.stack_holder + ): + self._log_lines('', '%s <-' % self.format_stack()) + if not isinstance(message, CommandMessage): + return + c, s, d = message.command, self.stack_holder[0], self.dictionary + self._log_lines('', '-> %s' % (c,)) + self.stack_holder[0], _, self.dictionary = run(c, s, d) + mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id) + self.notify(mm) - def _log_lines(self, *lines): - self.log.extend(lines) - self.notify(ModifyMessage(self, self.log, content_id=self.log_id)) + def _log_lines(self, *lines): + self.log.extend(lines) + self.notify(ModifyMessage(self, self.log, content_id=self.log_id)) - def format_stack(self): - try: - return stack_to_string(self.stack_holder[0]) - except: - print(format_exc(), file=stderr) - return str(self.stack_holder[0]) + def format_stack(self): + try: + return stack_to_string(self.stack_holder[0]) + except: + print(format_exc(), file=stderr) + return str(self.stack_holder[0]) def push(sender, item, notify, stack_name='stack.pickle'): - ''' - Helper function to push an item onto the system stack with message. - ''' - om = OpenMessage(sender, stack_name) - notify(om) - if om.status == SUCCESS: - om.thing[0] = item, om.thing[0] - notify(ModifyMessage(sender, om.thing, content_id=om.content_id)) - return om.status + ''' + Helper function to push an item onto the system stack with message. + ''' + om = OpenMessage(sender, stack_name) + notify(om) + if om.status == SUCCESS: + om.thing[0] = item, om.thing[0] + notify(ModifyMessage(sender, om.thing, content_id=om.content_id)) + return om.status def open_viewer_on_string(sender, content, notify): - ''' - Helper function to open a text viewer on a string. - Typically used to show tracebacks. - ''' - push(sender, content, notify) - notify(CommandMessage(sender, 'good_viewer_location open_viewer')) + ''' + Helper function to open a text viewer on a string. + Typically used to show tracebacks. + ''' + push(sender, content, notify) + notify(CommandMessage(sender, 'good_viewer_location open_viewer')) # main loop class TheLoop(object): - ''' - The main loop manages tasks and the PyGame event queue - and framerate clock. - ''' + ''' + The main loop manages tasks and the PyGame event queue + and framerate clock. + ''' - FRAME_RATE = 24 + FRAME_RATE = 24 - def __init__(self, display, clock): - self.display = display - self.clock = clock - self.tasks = {} - self.running = False + def __init__(self, display, clock): + self.display = display + self.clock = clock + self.tasks = {} + self.running = False - def install_task(self, F, milliseconds): - ''' - Install a task to run every so many milliseconds. - ''' - try: - task_event_id = AVAILABLE_TASK_EVENTS.pop() - except KeyError: - raise RuntimeError('out of task ids') - self.tasks[task_event_id] = F - pygame.time.set_timer(task_event_id, milliseconds) - return task_event_id + def install_task(self, F, milliseconds): + ''' + Install a task to run every so many milliseconds. + ''' + try: + task_event_id = AVAILABLE_TASK_EVENTS.pop() + except KeyError: + raise RuntimeError('out of task ids') + self.tasks[task_event_id] = F + pygame.time.set_timer(task_event_id, milliseconds) + return task_event_id - def remove_task(self, task_event_id): - ''' - Remove an installed task. - ''' - assert task_event_id in self.tasks, repr(task_event_id) - pygame.time.set_timer(task_event_id, 0) - del self.tasks[task_event_id] - AVAILABLE_TASK_EVENTS.add(task_event_id) + def remove_task(self, task_event_id): + ''' + Remove an installed task. + ''' + assert task_event_id in self.tasks, repr(task_event_id) + pygame.time.set_timer(task_event_id, 0) + del self.tasks[task_event_id] + AVAILABLE_TASK_EVENTS.add(task_event_id) - def __del__(self): - # Best effort to cancel all running tasks. - for task_event_id in self.tasks: - pygame.time.set_timer(task_event_id, 0) + def __del__(self): + # Best effort to cancel all running tasks. + for task_event_id in self.tasks: + pygame.time.set_timer(task_event_id, 0) - def run_task(self, task_event_id): - ''' - Give a task its time to shine. - ''' - task = self.tasks[task_event_id] - try: - task() - except: - traceback = format_exc() - self.remove_task(task_event_id) - print(traceback, file=stderr) - print('TASK removed due to ERROR', task, file=stderr) - open_viewer_on_string(self, traceback, self.display.broadcast) + def run_task(self, task_event_id): + ''' + Give a task its time to shine. + ''' + task = self.tasks[task_event_id] + try: + task() + except: + traceback = format_exc() + self.remove_task(task_event_id) + print(traceback, file=stderr) + print('TASK removed due to ERROR', task, file=stderr) + open_viewer_on_string(self, traceback, self.display.broadcast) - def loop(self): - ''' - The actual main loop machinery. + def loop(self): + ''' + The actual main loop machinery. - Maintain a ``running`` flag, pump the PyGame event queue and - handle the events (dispatching to the display), tick the clock. + Maintain a ``running`` flag, pump the PyGame event queue and + handle the events (dispatching to the display), tick the clock. - When the loop is exited (by clicking the window close button or - pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``. - ''' - self.running = True - while self.running: - for event in pygame.event.get(): - if event.type == pygame.QUIT: - self.running = False - elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE: - self.running = False - elif event.type in self.tasks: - self.run_task(event.type) - else: - self.display.dispatch_event(event) - pygame.display.update() - self.clock.tick(self.FRAME_RATE) - self.display.broadcast(ShutdownMessage(self)) + When the loop is exited (by clicking the window close button or + pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``. + ''' + self.running = True + while self.running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE: + self.running = False + elif event.type in self.tasks: + self.run_task(event.type) + else: + self.display.dispatch_event(event) + pygame.display.update() + self.clock.tick(self.FRAME_RATE) + self.display.broadcast(ShutdownMessage(self)) diff --git a/joy/vui/debug_main.py b/joy/vui/debug_main.py index aeda7b3..3235b35 100644 --- a/joy/vui/debug_main.py +++ b/joy/vui/debug_main.py @@ -3,17 +3,17 @@ import sys, traceback # To enable "hot" reloading in the IDLE shell. for name in 'core main display viewer text_viewer stack_viewer persist_task'.split(): - try: - del sys.modules[name] - except KeyError: - pass + try: + del sys.modules[name] + except KeyError: + pass from . import main try: - A = A # (screen, clock, pt), three things that we DON'T want to recreate - # each time we restart main(). + A = A # (screen, clock, pt), three things that we DON'T want to recreate + # each time we restart main(). except NameError: - A = main.init() + A = main.init() d = main.main(*A) diff --git a/joy/vui/display.py b/joy/vui/display.py index 7c442c2..fefc5dd 100644 --- a/joy/vui/display.py +++ b/joy/vui/display.py @@ -38,473 +38,473 @@ from sys import stderr from traceback import format_exc import pygame from .core import ( - open_viewer_on_string, - GREY, - MOUSE_EVENTS, - ) + open_viewer_on_string, + GREY, + MOUSE_EVENTS, + ) from .viewer import Viewer from joy.vui import text_viewer class Display(object): - ''' - Manage tracks and viewers on a screen (Pygame surface.) + ''' + Manage tracks and viewers on a screen (Pygame surface.) - The size and number of tracks are defined by passing in at least two - ratios, e.g. Display(screen, 1, 4, 4) would create three tracks, one - small one on the left and two larger ones of the same size, each four - times wider than the left one. + The size and number of tracks are defined by passing in at least two + ratios, e.g. Display(screen, 1, 4, 4) would create three tracks, one + small one on the left and two larger ones of the same size, each four + times wider than the left one. - All tracks take up the whole height of the display screen. Tracks - manage zero or more Viewers. When you "grow" a viewer a new track is - created that overlays or hides one or two existing tracks, and when - the last viewer in an overlay track is closed the track closes too - and reveals the hidden tracks (and their viewers, if any.) + All tracks take up the whole height of the display screen. Tracks + manage zero or more Viewers. When you "grow" a viewer a new track is + created that overlays or hides one or two existing tracks, and when + the last viewer in an overlay track is closed the track closes too + and reveals the hidden tracks (and their viewers, if any.) - In order to facilitate command underlining while mouse dragging the - lookup parameter must be a function that accepts a string and returns - a Boolean indicating whether that string is a valid Joy function name. - Typically you pass in the __contains__ method of the Joy dict. This - is a case of breaking "loose coupling" to gain efficiency, as otherwise - we would have to e.g. send some sort of lookup message to the - World context object, going through the whole Display.broadcast() - machinery, etc. Not something you want to do on each MOUSEMOTION - event. - ''' + In order to facilitate command underlining while mouse dragging the + lookup parameter must be a function that accepts a string and returns + a Boolean indicating whether that string is a valid Joy function name. + Typically you pass in the __contains__ method of the Joy dict. This + is a case of breaking "loose coupling" to gain efficiency, as otherwise + we would have to e.g. send some sort of lookup message to the + World context object, going through the whole Display.broadcast() + machinery, etc. Not something you want to do on each MOUSEMOTION + event. + ''' - def __init__(self, screen, lookup, *track_ratios): - self.screen = screen - self.w, self.h = screen.get_width(), screen.get_height() - self.lookup = lookup - self.focused_viewer = None - self.tracks = [] # (x, track) - self.handlers = [] # Non-viewers that should receive messages. - # Create the tracks. - if not track_ratios: track_ratios = 1, 4 - x, total = 0, sum(track_ratios) - for ratio in track_ratios[:-1]: - track_width = old_div(self.w * ratio, total) - assert track_width >= 10 # minimum width 10 pixels - self._open_track(x, track_width) - x += track_width - self._open_track(x, self.w - x) + def __init__(self, screen, lookup, *track_ratios): + self.screen = screen + self.w, self.h = screen.get_width(), screen.get_height() + self.lookup = lookup + self.focused_viewer = None + self.tracks = [] # (x, track) + self.handlers = [] # Non-viewers that should receive messages. + # Create the tracks. + if not track_ratios: track_ratios = 1, 4 + x, total = 0, sum(track_ratios) + for ratio in track_ratios[:-1]: + track_width = old_div(self.w * ratio, total) + assert track_width >= 10 # minimum width 10 pixels + self._open_track(x, track_width) + x += track_width + self._open_track(x, self.w - x) - def _open_track(self, x, w): - '''Helper function to create the pygame surface and Track.''' - track_surface = self.screen.subsurface((x, 0, w, self.h)) - self.tracks.append((x, Track(track_surface))) + def _open_track(self, x, w): + '''Helper function to create the pygame surface and Track.''' + track_surface = self.screen.subsurface((x, 0, w, self.h)) + self.tracks.append((x, Track(track_surface))) - def open_viewer(self, x, y, class_): - ''' - Open a viewer of class_ at the x, y location on the display, - return the viewer. - ''' - track = self._track_at(x)[0] - V = track.open_viewer(y, class_) - V.focus(self) - return V + def open_viewer(self, x, y, class_): + ''' + Open a viewer of class_ at the x, y location on the display, + return the viewer. + ''' + track = self._track_at(x)[0] + V = track.open_viewer(y, class_) + V.focus(self) + return V - def close_viewer(self, viewer): - '''Close the viewer.''' - for x, track in self.tracks: - if track.close_viewer(viewer): - if not track.viewers and track.hiding: - i = self.tracks.index((x, track)) - self.tracks[i:i + 1] = track.hiding - assert sorted(self.tracks) == self.tracks - for _, exposed_track in track.hiding: - exposed_track.redraw() - if viewer is self.focused_viewer: - self.focused_viewer = None - break + def close_viewer(self, viewer): + '''Close the viewer.''' + for x, track in self.tracks: + if track.close_viewer(viewer): + if not track.viewers and track.hiding: + i = self.tracks.index((x, track)) + self.tracks[i:i + 1] = track.hiding + assert sorted(self.tracks) == self.tracks + for _, exposed_track in track.hiding: + exposed_track.redraw() + if viewer is self.focused_viewer: + self.focused_viewer = None + break - def change_viewer(self, viewer, y, relative=False): - ''' - Adjust the top of the viewer to a new y within the boundaries of - its neighbors. + def change_viewer(self, viewer, y, relative=False): + ''' + Adjust the top of the viewer to a new y within the boundaries of + its neighbors. - If relative is False new_y should be in screen coords, else new_y - should be relative to the top of the viewer. - ''' - for _, track in self.tracks: - if track.change_viewer(viewer, y, relative): - break + If relative is False new_y should be in screen coords, else new_y + should be relative to the top of the viewer. + ''' + for _, track in self.tracks: + if track.change_viewer(viewer, y, relative): + break - def grow_viewer(self, viewer): - ''' - Cause the viewer to take up its whole track or, if it does - already, take up another track, up to the whole screen. + def grow_viewer(self, viewer): + ''' + Cause the viewer to take up its whole track or, if it does + already, take up another track, up to the whole screen. - This is the inverse of closing a viewer. "Growing" a viewer - actually creates a new copy and a new track to hold it. The old - tracks and viewers are retained, and they get restored when the - covering track closes, which happens automatically when the last - viewer in the covering track is closed. - ''' - for x, track in self.tracks: - for _, V in track.viewers: - if V is viewer: - return self._grow_viewer(x, track, viewer) + This is the inverse of closing a viewer. "Growing" a viewer + actually creates a new copy and a new track to hold it. The old + tracks and viewers are retained, and they get restored when the + covering track closes, which happens automatically when the last + viewer in the covering track is closed. + ''' + for x, track in self.tracks: + for _, V in track.viewers: + if V is viewer: + return self._grow_viewer(x, track, viewer) - def _grow_viewer(self, x, track, viewer): - '''Helper function to "grow" a viewer.''' - new_viewer = None + def _grow_viewer(self, x, track, viewer): + '''Helper function to "grow" a viewer.''' + new_viewer = None - if viewer.h < self.h: - # replace the track with a new track that contains - # a copy of the viewer at full height. - new_track = Track(track.surface) # Reuse it, why not? - new_viewer = copy(viewer) - new_track._grow_by(new_viewer, 0, self.h - viewer.h) - new_track.viewers.append((0, new_viewer)) - new_track.hiding = [(x, track)] - self.tracks[self.tracks.index((x, track))] = x, new_track + if viewer.h < self.h: + # replace the track with a new track that contains + # a copy of the viewer at full height. + new_track = Track(track.surface) # Reuse it, why not? + new_viewer = copy(viewer) + new_track._grow_by(new_viewer, 0, self.h - viewer.h) + new_track.viewers.append((0, new_viewer)) + new_track.hiding = [(x, track)] + self.tracks[self.tracks.index((x, track))] = x, new_track - elif viewer.w < self.w: - # replace two tracks - i = self.tracks.index((x, track)) - try: # prefer the one on the right - xx, xtrack = self.tracks[i + 1] - except IndexError: - i -= 1 # okay, the one on the left - xx, xtrack = self.tracks[i] - hiding = [(xx, xtrack), (x, track)] - else: - hiding = [(x, track), (xx, xtrack)] - # We know there has to be at least one other track because it - # there weren't then that implies that the one track takes up - # the whole display screen (the only way you can get just one - # track is by growing a viewer to cover the whole screen.) - # Ergo, viewer.w == self.w, so this branch doesn't run. - new_x = min(x, xx) - new_w = track.w + xtrack.w - r = new_x, 0, new_w, self.h - new_track = Track(self.screen.subsurface(r)) - new_viewer = copy(viewer) - r = 0, 0, new_w, self.h - new_viewer.resurface(new_track.surface.subsurface(r)) - new_track.viewers.append((0, new_viewer)) - new_track.hiding = hiding - self.tracks[i:i + 2] = [(new_x, new_track)] - new_viewer.draw() + elif viewer.w < self.w: + # replace two tracks + i = self.tracks.index((x, track)) + try: # prefer the one on the right + xx, xtrack = self.tracks[i + 1] + except IndexError: + i -= 1 # okay, the one on the left + xx, xtrack = self.tracks[i] + hiding = [(xx, xtrack), (x, track)] + else: + hiding = [(x, track), (xx, xtrack)] + # We know there has to be at least one other track because it + # there weren't then that implies that the one track takes up + # the whole display screen (the only way you can get just one + # track is by growing a viewer to cover the whole screen.) + # Ergo, viewer.w == self.w, so this branch doesn't run. + new_x = min(x, xx) + new_w = track.w + xtrack.w + r = new_x, 0, new_w, self.h + new_track = Track(self.screen.subsurface(r)) + new_viewer = copy(viewer) + r = 0, 0, new_w, self.h + new_viewer.resurface(new_track.surface.subsurface(r)) + new_track.viewers.append((0, new_viewer)) + new_track.hiding = hiding + self.tracks[i:i + 2] = [(new_x, new_track)] + new_viewer.draw() - return new_viewer + return new_viewer - def _move_viewer(self, to, rel_y, viewer, _x, y): - ''' - Helper function to move (really copy) a viewer to a new location. - ''' - h = to.split(rel_y) - new_viewer = copy(viewer) - if not isinstance(to, Track): - to = next(T for _, T in self.tracks - for _, V in T.viewers - if V is to) - new_viewer.resurface(to.surface.subsurface((0, y, to.w, h))) - to.viewers.append((y, new_viewer)) - to.viewers.sort() # bisect.insort() would be overkill here. - new_viewer.draw() - self.close_viewer(viewer) + def _move_viewer(self, to, rel_y, viewer, _x, y): + ''' + Helper function to move (really copy) a viewer to a new location. + ''' + h = to.split(rel_y) + new_viewer = copy(viewer) + if not isinstance(to, Track): + to = next(T for _, T in self.tracks + for _, V in T.viewers + if V is to) + new_viewer.resurface(to.surface.subsurface((0, y, to.w, h))) + to.viewers.append((y, new_viewer)) + to.viewers.sort() # bisect.insort() would be overkill here. + new_viewer.draw() + self.close_viewer(viewer) - def _track_at(self, x): - ''' - Return the track at x along with the track-relative x coordinate, - raise ValueError if x is off-screen. - ''' - for track_x, track in self.tracks: - if x < track_x + track.w: - return track, x - track_x - raise ValueError('x outside display: %r' % (x,)) + def _track_at(self, x): + ''' + Return the track at x along with the track-relative x coordinate, + raise ValueError if x is off-screen. + ''' + for track_x, track in self.tracks: + if x < track_x + track.w: + return track, x - track_x + raise ValueError('x outside display: %r' % (x,)) - def at(self, x, y): - ''' - Return the viewer (which can be a Track) at the x, y location, - along with the relative-to-viewer-surface x and y coordinates. - If there is no viewer at the location the Track will be returned - instead. - ''' - track, x = self._track_at(x) - viewer, y = track.viewer_at(y) - return viewer, x, y + def at(self, x, y): + ''' + Return the viewer (which can be a Track) at the x, y location, + along with the relative-to-viewer-surface x and y coordinates. + If there is no viewer at the location the Track will be returned + instead. + ''' + track, x = self._track_at(x) + viewer, y = track.viewer_at(y) + return viewer, x, y - def iter_viewers(self): - ''' - Iterate through all viewers yielding (viewer, x, y) three-tuples. - The x and y coordinates are screen pixels of the top-left corner - of the viewer. - ''' - for x, T in self.tracks: - for y, V in T.viewers: - yield V, x, y + def iter_viewers(self): + ''' + Iterate through all viewers yielding (viewer, x, y) three-tuples. + The x and y coordinates are screen pixels of the top-left corner + of the viewer. + ''' + for x, T in self.tracks: + for y, V in T.viewers: + yield V, x, y - def done_resizing(self): - ''' - Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily) - update the display when done resizing a viewer. - ''' - for _, track in self.tracks: # This should be done by a Message? - if track.resizing_viewer: - track.resizing_viewer.draw() - track.resizing_viewer = None - break + def done_resizing(self): + ''' + Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily) + update the display when done resizing a viewer. + ''' + for _, track in self.tracks: # This should be done by a Message? + if track.resizing_viewer: + track.resizing_viewer.draw() + track.resizing_viewer = None + break - def broadcast(self, message): - ''' - Broadcast a message to all viewers (except the sender) and all - registered handlers. - ''' - for _, track in self.tracks: - track.broadcast(message) - for handler in self.handlers: - handler(message) + def broadcast(self, message): + ''' + Broadcast a message to all viewers (except the sender) and all + registered handlers. + ''' + for _, track in self.tracks: + track.broadcast(message) + for handler in self.handlers: + handler(message) - def redraw(self): - ''' - Redraw all tracks (which will redraw all viewers.) - ''' - for _, track in self.tracks: - track.redraw() + def redraw(self): + ''' + Redraw all tracks (which will redraw all viewers.) + ''' + for _, track in self.tracks: + track.redraw() - def focus(self, viewer): - ''' - Set system focus to a given viewer (or no viewer if a track.) - ''' - if isinstance(viewer, Track): - if self.focused_viewer: self.focused_viewer.unfocus() - self.focused_viewer = None - elif viewer is not self.focused_viewer: - if self.focused_viewer: self.focused_viewer.unfocus() - self.focused_viewer = viewer - viewer.focus(self) + def focus(self, viewer): + ''' + Set system focus to a given viewer (or no viewer if a track.) + ''' + if isinstance(viewer, Track): + if self.focused_viewer: self.focused_viewer.unfocus() + self.focused_viewer = None + elif viewer is not self.focused_viewer: + if self.focused_viewer: self.focused_viewer.unfocus() + self.focused_viewer = viewer + viewer.focus(self) - def dispatch_event(self, event): - ''' - Display event handling. - ''' - try: - if event.type in {pygame.KEYUP, pygame.KEYDOWN}: - self._keyboard_event(event) - elif event.type in MOUSE_EVENTS: - self._mouse_event(event) - else: - print(( - 'received event %s Use pygame.event.set_allowed().' - % pygame.event.event_name(event.type) - ), file=stderr) - # Catch all exceptions and open a viewer. - except: - err = format_exc() - print(err, file=stderr) # To be safe just print it right away. - open_viewer_on_string(self, err, self.broadcast) + def dispatch_event(self, event): + ''' + Display event handling. + ''' + try: + if event.type in {pygame.KEYUP, pygame.KEYDOWN}: + self._keyboard_event(event) + elif event.type in MOUSE_EVENTS: + self._mouse_event(event) + else: + print(( + 'received event %s Use pygame.event.set_allowed().' + % pygame.event.event_name(event.type) + ), file=stderr) + # Catch all exceptions and open a viewer. + except: + err = format_exc() + print(err, file=stderr) # To be safe just print it right away. + open_viewer_on_string(self, err, self.broadcast) - def _keyboard_event(self, event): - if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP: - # At least on my keyboard the break/pause key sends K_PAUSE. - # The main use of this is to open a TextViewer if you - # accidentally close all the viewers, so you can recover. - raise KeyboardInterrupt('break') - if not self.focused_viewer: - return - if event.type == pygame.KEYUP: - self.focused_viewer.key_up(self, event.key, event.mod) - elif event.type == pygame.KEYDOWN: - self.focused_viewer.key_down( - self, event.unicode, event.key, event.mod) - # This is not UnicodeType. TODO does this need to be fixed? - # self, event.str, event.key, event.mod) + def _keyboard_event(self, event): + if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP: + # At least on my keyboard the break/pause key sends K_PAUSE. + # The main use of this is to open a TextViewer if you + # accidentally close all the viewers, so you can recover. + raise KeyboardInterrupt('break') + if not self.focused_viewer: + return + if event.type == pygame.KEYUP: + self.focused_viewer.key_up(self, event.key, event.mod) + elif event.type == pygame.KEYDOWN: + self.focused_viewer.key_down( + self, event.unicode, event.key, event.mod) + # This is not UnicodeType. TODO does this need to be fixed? + # self, event.str, event.key, event.mod) - def _mouse_event(self, event): - V, x, y = self.at(*event.pos) + def _mouse_event(self, event): + V, x, y = self.at(*event.pos) - if event.type == pygame.MOUSEMOTION: - if not isinstance(V, Track): - V.mouse_motion(self, x, y, *(event.rel + event.buttons)) + if event.type == pygame.MOUSEMOTION: + if not isinstance(V, Track): + V.mouse_motion(self, x, y, *(event.rel + event.buttons)) - elif event.type == pygame.MOUSEBUTTONDOWN: - if event.button == 1: - self.focus(V) - V.mouse_down(self, x, y, event.button) + elif event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1: + self.focus(V) + V.mouse_down(self, x, y, event.button) - else: - assert event.type == pygame.MOUSEBUTTONUP + else: + assert event.type == pygame.MOUSEBUTTONUP - # Check for moving viewer. - if (event.button == 2 - and self.focused_viewer - and V is not self.focused_viewer - and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT - ): - self._move_viewer(V, y, self.focused_viewer, *event.pos) + # Check for moving viewer. + if (event.button == 2 + and self.focused_viewer + and V is not self.focused_viewer + and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT + ): + self._move_viewer(V, y, self.focused_viewer, *event.pos) - else: - V.mouse_up(self, x, y, event.button) + else: + V.mouse_up(self, x, y, event.button) - def init_text(self, pt, x, y, filename): - ''' - Open and return a ``TextViewer`` on a given file (which must be present - in the ``JOYHOME`` directory.) - ''' - viewer = self.open_viewer(x, y, text_viewer.TextViewer) - viewer.content_id, viewer.lines = pt.open(filename) - viewer.draw() - return viewer + def init_text(self, pt, x, y, filename): + ''' + Open and return a ``TextViewer`` on a given file (which must be present + in the ``JOYHOME`` directory.) + ''' + viewer = self.open_viewer(x, y, text_viewer.TextViewer) + viewer.content_id, viewer.lines = pt.open(filename) + viewer.draw() + return viewer class Track(Viewer): - ''' - Manage a vertical strip of the display, and the viewers on it. - ''' + ''' + Manage a vertical strip of the display, and the viewers on it. + ''' - def __init__(self, surface): - Viewer.__init__(self, surface) - self.viewers = [] # (y, viewer) - self.hiding = None - self.resizing_viewer = None - self.draw() + def __init__(self, surface): + Viewer.__init__(self, surface) + self.viewers = [] # (y, viewer) + self.hiding = None + self.resizing_viewer = None + self.draw() - def split(self, y): - ''' - Split the Track at the y coordinate and return the height - available for a new viewer. Tracks manage a vertical strip of - the display screen so they don't resize their surface when split. - ''' - h = self.viewers[0][0] if self.viewers else self.h - assert h > y - return h - y + def split(self, y): + ''' + Split the Track at the y coordinate and return the height + available for a new viewer. Tracks manage a vertical strip of + the display screen so they don't resize their surface when split. + ''' + h = self.viewers[0][0] if self.viewers else self.h + assert h > y + return h - y - def draw(self, rect=None): - '''Draw the track onto its surface, clearing all content. + def draw(self, rect=None): + '''Draw the track onto its surface, clearing all content. - If rect is passed only draw to that area. This supports e.g. - closing a viewer that then exposes part of the track. - ''' - self.surface.fill(GREY, rect=rect) + If rect is passed only draw to that area. This supports e.g. + closing a viewer that then exposes part of the track. + ''' + self.surface.fill(GREY, rect=rect) - def viewer_at(self, y): - ''' - Return the viewer at y along with the viewer-relative y coordinate, - if there's no viewer at y return this track and y. - ''' - for viewer_y, viewer in self.viewers: - if viewer_y < y <= viewer_y + viewer.h: - return viewer, y - viewer_y - return self, y + def viewer_at(self, y): + ''' + Return the viewer at y along with the viewer-relative y coordinate, + if there's no viewer at y return this track and y. + ''' + for viewer_y, viewer in self.viewers: + if viewer_y < y <= viewer_y + viewer.h: + return viewer, y - viewer_y + return self, y - def open_viewer(self, y, class_): - '''Open and return a viewer of class at y.''' - # Todo: if y coincides with some other viewer's y replace it. - viewer, viewer_y = self.viewer_at(y) - h = viewer.split(viewer_y) - new_viewer = class_(self.surface.subsurface((0, y, self.w, h))) - new_viewer.draw() - self.viewers.append((y, new_viewer)) - self.viewers.sort() # Could use bisect module but how many - # viewers will you ever have? - return new_viewer + def open_viewer(self, y, class_): + '''Open and return a viewer of class at y.''' + # Todo: if y coincides with some other viewer's y replace it. + viewer, viewer_y = self.viewer_at(y) + h = viewer.split(viewer_y) + new_viewer = class_(self.surface.subsurface((0, y, self.w, h))) + new_viewer.draw() + self.viewers.append((y, new_viewer)) + self.viewers.sort() # Could use bisect module but how many + # viewers will you ever have? + return new_viewer - def close_viewer(self, viewer): - '''Close the viewer, reuse the freed space.''' - for y, V in self.viewers: - if V is viewer: - self._close_viewer(y, V) - return True - return False + def close_viewer(self, viewer): + '''Close the viewer, reuse the freed space.''' + for y, V in self.viewers: + if V is viewer: + self._close_viewer(y, V) + return True + return False - def _close_viewer(self, y, viewer): - '''Helper function to do the actual closing.''' - i = self.viewers.index((y, viewer)) - del self.viewers[i] - if i: # The previous viewer gets the space. - previous_y, previous_viewer = self.viewers[i - 1] - self._grow_by(previous_viewer, previous_y, viewer.h) - else: # This track gets the space. - self.draw((0, y, self.w, viewer.surface.get_height())) - viewer.close() + def _close_viewer(self, y, viewer): + '''Helper function to do the actual closing.''' + i = self.viewers.index((y, viewer)) + del self.viewers[i] + if i: # The previous viewer gets the space. + previous_y, previous_viewer = self.viewers[i - 1] + self._grow_by(previous_viewer, previous_y, viewer.h) + else: # This track gets the space. + self.draw((0, y, self.w, viewer.surface.get_height())) + viewer.close() - def _grow_by(self, viewer, y, h): - '''Grow a viewer (located at y) by height h. + def _grow_by(self, viewer, y, h): + '''Grow a viewer (located at y) by height h. - This might seem like it should be a method of the viewer, but - the viewer knows nothing of its own y location on the screen nor - the parent track's surface (to make a new subsurface) so it has - to be a method of the track, which has both. - ''' - h = viewer.surface.get_height() + h - try: - surface = self.surface.subsurface((0, y, self.w, h)) - except ValueError: # subsurface rectangle outside surface area - pass - else: - viewer.resurface(surface) - if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0 - viewer.draw() + This might seem like it should be a method of the viewer, but + the viewer knows nothing of its own y location on the screen nor + the parent track's surface (to make a new subsurface) so it has + to be a method of the track, which has both. + ''' + h = viewer.surface.get_height() + h + try: + surface = self.surface.subsurface((0, y, self.w, h)) + except ValueError: # subsurface rectangle outside surface area + pass + else: + viewer.resurface(surface) + if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0 + viewer.draw() - def change_viewer(self, viewer, new_y, relative=False): - ''' - Adjust the top of the viewer to a new y within the boundaries of - its neighbors. + def change_viewer(self, viewer, new_y, relative=False): + ''' + Adjust the top of the viewer to a new y within the boundaries of + its neighbors. - If relative is False new_y should be in screen coords, else new_y - should be relative to the top of the viewer. - ''' - for old_y, V in self.viewers: - if V is viewer: - if relative: new_y += old_y - if new_y != old_y: self._change_viewer(new_y, old_y, V) - return True - return False + If relative is False new_y should be in screen coords, else new_y + should be relative to the top of the viewer. + ''' + for old_y, V in self.viewers: + if V is viewer: + if relative: new_y += old_y + if new_y != old_y: self._change_viewer(new_y, old_y, V) + return True + return False - def _change_viewer(self, new_y, old_y, viewer): - new_y = max(0, min(self.h, new_y)) - i = self.viewers.index((old_y, viewer)) - if new_y < old_y: # Enlarge self, shrink upper neighbor. - if i: - previous_y, previous_viewer = self.viewers[i - 1] - if new_y - previous_y < self.MINIMUM_HEIGHT: - return - previous_viewer.resizing = 1 - h = previous_viewer.split(new_y - previous_y) - previous_viewer.resizing = 0 - self.resizing_viewer = previous_viewer - else: - h = old_y - new_y - self._grow_by(viewer, new_y, h) + def _change_viewer(self, new_y, old_y, viewer): + new_y = max(0, min(self.h, new_y)) + i = self.viewers.index((old_y, viewer)) + if new_y < old_y: # Enlarge self, shrink upper neighbor. + if i: + previous_y, previous_viewer = self.viewers[i - 1] + if new_y - previous_y < self.MINIMUM_HEIGHT: + return + previous_viewer.resizing = 1 + h = previous_viewer.split(new_y - previous_y) + previous_viewer.resizing = 0 + self.resizing_viewer = previous_viewer + else: + h = old_y - new_y + self._grow_by(viewer, new_y, h) - else: # Shink self, enlarge upper neighbor. - # Enforce invariant. - try: - h, _ = self.viewers[i + 1] - except IndexError: # No next viewer. - h = self.h - if h - new_y < self.MINIMUM_HEIGHT: - return + else: # Shink self, enlarge upper neighbor. + # Enforce invariant. + try: + h, _ = self.viewers[i + 1] + except IndexError: # No next viewer. + h = self.h + if h - new_y < self.MINIMUM_HEIGHT: + return - # Change the viewer and adjust the upper viewer or track. - h = new_y - old_y - self._grow_by(viewer, new_y, -h) # grow by negative height! - if i: - previous_y, previous_viewer = self.viewers[i - 1] - previous_viewer.resizing = 1 - self._grow_by(previous_viewer, previous_y, h) - previous_viewer.resizing = 0 - self.resizing_viewer = previous_viewer - else: - self.draw((0, old_y, self.w, h)) + # Change the viewer and adjust the upper viewer or track. + h = new_y - old_y + self._grow_by(viewer, new_y, -h) # grow by negative height! + if i: + previous_y, previous_viewer = self.viewers[i - 1] + previous_viewer.resizing = 1 + self._grow_by(previous_viewer, previous_y, h) + previous_viewer.resizing = 0 + self.resizing_viewer = previous_viewer + else: + self.draw((0, old_y, self.w, h)) - self.viewers[i] = new_y, viewer - # self.viewers.sort() # Not necessary, invariant holds. - assert sorted(self.viewers) == self.viewers + self.viewers[i] = new_y, viewer + # self.viewers.sort() # Not necessary, invariant holds. + assert sorted(self.viewers) == self.viewers - def broadcast(self, message): - ''' - Broadcast a message to all viewers on this track (except the sender.) - ''' - for _, viewer in self.viewers: - if viewer is not message.sender: - viewer.handle(message) + def broadcast(self, message): + ''' + Broadcast a message to all viewers on this track (except the sender.) + ''' + for _, viewer in self.viewers: + if viewer is not message.sender: + viewer.handle(message) - def redraw(self): - '''Redraw the track and all of its viewers.''' - self.draw() - for _, viewer in self.viewers: - viewer.draw() + def redraw(self): + '''Redraw the track and all of its viewers.''' + self.draw() + for _, viewer in self.viewers: + viewer.draw() diff --git a/joy/vui/font_data.py b/joy/vui/font_data.py index 2fd9fa2..a719b2b 100644 --- a/joy/vui/font_data.py +++ b/joy/vui/font_data.py @@ -25,9 +25,9 @@ import base64, zlib def create(fn='Iosevka12.BMP'): - with open(fn, 'rb') as f: - data = f.read() - return base64.encodestring(zlib.compress(data)) + with open(fn, 'rb') as f: + data = f.read() + return base64.encodestring(zlib.compress(data)) data = StringIO(zlib.decompress(base64.decodestring('''\ @@ -186,4 +186,4 @@ lnalXc/9SsNb2vUirzS8pV0v8gJv/w/2vRht'''))) if __name__ == '__main__': - print(create()) + print(create()) diff --git a/joy/vui/init_joy_home.py b/joy/vui/init_joy_home.py index 9e4f45f..1dd23c5 100644 --- a/joy/vui/init_joy_home.py +++ b/joy/vui/init_joy_home.py @@ -24,8 +24,8 @@ JOY_HOME directory. These contents are kept in this Python module as a base64-encoded zip file, so you can just do, e.g.: - import init_joy_home - init_joy_home.initialize(JOY_HOME) + import init_joy_home + init_joy_home.initialize(JOY_HOME) ''' from __future__ import print_function @@ -35,17 +35,17 @@ import base64, os, io, zipfile def initialize(joy_home): - Z.extractall(joy_home) + Z.extractall(joy_home) def create_data(from_dir='./default_joy_home'): - f = io.StringIO() - z = zipfile.ZipFile(f, mode='w') - for fn in os.listdir(from_dir): - from_fn = os.path.join(from_dir, fn) - z.write(from_fn, fn) - z.close() - return base64.encodestring(f.getvalue()) + f = io.StringIO() + z = zipfile.ZipFile(f, mode='w') + for fn in os.listdir(from_dir): + from_fn = os.path.join(from_dir, fn) + z.write(from_fn, fn) + z.close() + return base64.encodestring(f.getvalue()) Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\ @@ -275,4 +275,4 @@ c3RhY2sucGlja2xlUEsFBgAAAAAGAAYAUwEAACcwAAAAAA=='''))) if __name__ == '__main__': - print(create_data()) + print(create_data()) diff --git a/joy/vui/main.py b/joy/vui/main.py index baeb1ad..76daad6 100644 --- a/joy/vui/main.py +++ b/joy/vui/main.py @@ -41,139 +41,139 @@ FULLSCREEN = '-f' in sys.argv JOY_HOME = os.environ.get('JOY_HOME') if JOY_HOME is None: - JOY_HOME = os.path.expanduser('~/.thun') - if not os.path.isabs(JOY_HOME): - raise ValueError('what directory?') + JOY_HOME = os.path.expanduser('~/.thun') + if not os.path.isabs(JOY_HOME): + raise ValueError('what directory?') def load_definitions(pt, dictionary): - '''Load definitions from ``definitions.txt``.''' - lines = pt.open('definitions.txt')[1] - for line in lines: - if '==' in line: - DefinitionWrapper.add_def(line, dictionary) + '''Load definitions from ``definitions.txt``.''' + lines = pt.open('definitions.txt')[1] + for line in lines: + if '==' in line: + DefinitionWrapper.add_def(line, dictionary) def load_primitives(home, name_space): - '''Load primitives from ``library.py``.''' - fn = os.path.join(home, 'library.py') - if os.path.exists(fn): - execfile(fn, name_space) + '''Load primitives from ``library.py``.''' + fn = os.path.join(home, 'library.py') + if os.path.exists(fn): + execfile(fn, name_space) def init(): - ''' - Initialize the system. - - * Init PyGame - * Create main window - * Start the PyGame clock - * Set the event mask - * Create the PersistTask + ''' + Initialize the system. + + * Init PyGame + * Create main window + * Start the PyGame clock + * Set the event mask + * Create the PersistTask - ''' - print('Initializing Pygame...') - pygame.init() - print('Creating window...') - if FULLSCREEN: - screen = pygame.display.set_mode() - else: - screen = pygame.display.set_mode((1024, 768)) - clock = pygame.time.Clock() - pygame.event.set_allowed(None) - pygame.event.set_allowed(core.ALLOWED_EVENTS) - pt = persist_task.PersistTask(JOY_HOME) - return screen, clock, pt + ''' + print('Initializing Pygame...') + pygame.init() + print('Creating window...') + if FULLSCREEN: + screen = pygame.display.set_mode() + else: + screen = pygame.display.set_mode((1024, 768)) + clock = pygame.time.Clock() + pygame.event.set_allowed(None) + pygame.event.set_allowed(core.ALLOWED_EVENTS) + pt = persist_task.PersistTask(JOY_HOME) + return screen, clock, pt def init_context(screen, clock, pt): - ''' - More initialization + ''' + More initialization - * Create the Joy dictionary - * Create the Display - * Open the log, menu, and scratch text viewers, and the stack pickle - * Start the main loop - * Create the World object - * Register PersistTask and World message handlers with the Display - * Load user function definitions. + * Create the Joy dictionary + * Create the Display + * Open the log, menu, and scratch text viewers, and the stack pickle + * Start the main loop + * Create the World object + * Register PersistTask and World message handlers with the Display + * Load user function definitions. - ''' - D = initialize() - d = display.Display( - screen, - D.__contains__, - *((144 - 89, 144, 89) if FULLSCREEN else (89, 144)) - ) - log = d.init_text(pt, 0, 0, 'log.txt') - tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt') - t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt') - loop = core.TheLoop(d, clock) - stack_id, stack_holder = pt.open('stack.pickle') - world = core.World(stack_id, stack_holder, D, d.broadcast, log) - loop.install_task(pt.task_run, 10000) # save files every ten seconds - d.handlers.append(pt.handle) - d.handlers.append(world.handle) - load_definitions(pt, D) - return locals() + ''' + D = initialize() + d = display.Display( + screen, + D.__contains__, + *((144 - 89, 144, 89) if FULLSCREEN else (89, 144)) + ) + log = d.init_text(pt, 0, 0, 'log.txt') + tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt') + t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt') + loop = core.TheLoop(d, clock) + stack_id, stack_holder = pt.open('stack.pickle') + world = core.World(stack_id, stack_holder, D, d.broadcast, log) + loop.install_task(pt.task_run, 10000) # save files every ten seconds + d.handlers.append(pt.handle) + d.handlers.append(world.handle) + load_definitions(pt, D) + return locals() def error_guard(loop, n=10): - ''' - Run a loop function, retry for ``n`` exceptions. - Prints tracebacks on ``sys.stderr``. - ''' - error_count = 0 - while error_count < n: - try: - loop() - break - except: - traceback.print_exc(file=sys.stderr) - error_count += 1 + ''' + Run a loop function, retry for ``n`` exceptions. + Prints tracebacks on ``sys.stderr``. + ''' + error_count = 0 + while error_count < n: + try: + loop() + break + except: + traceback.print_exc(file=sys.stderr) + error_count += 1 class FileFaker(object): - '''Pretends to be a file object but writes to log instead.''' + '''Pretends to be a file object but writes to log instead.''' - def __init__(self, log): - self.log = log + def __init__(self, log): + self.log = log - def write(self, text): - '''Write text to log.''' - self.log.append(text) + def write(self, text): + '''Write text to log.''' + self.log.append(text) - def flush(self): - pass + def flush(self): + pass def main(screen, clock, pt): - ''' - Main function. + ''' + Main function. - * Call ``init_context()`` - * Load primitives - * Create an ``evaluate`` function that lets you just eval some Python code - * Redirect ``stdout`` to the log using a ``FileFaker`` object, and... - * Start the main loop. - ''' - name_space = init_context(screen, clock, pt) - load_primitives(pt.home, name_space.copy()) + * Call ``init_context()`` + * Load primitives + * Create an ``evaluate`` function that lets you just eval some Python code + * Redirect ``stdout`` to the log using a ``FileFaker`` object, and... + * Start the main loop. + ''' + name_space = init_context(screen, clock, pt) + load_primitives(pt.home, name_space.copy()) - @SimpleFunctionWrapper - def evaluate(stack): - '''Evaluate the Python code text on the top of the stack.''' - code, stack = stack - exec(code, name_space.copy()) - return stack + @SimpleFunctionWrapper + def evaluate(stack): + '''Evaluate the Python code text on the top of the stack.''' + code, stack = stack + exec(code, name_space.copy()) + return stack - name_space['D']['evaluate'] = evaluate + name_space['D']['evaluate'] = evaluate - sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout - try: - error_guard(name_space['loop'].loop) - finally: - sys.stdout = old_stdout + sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout + try: + error_guard(name_space['loop'].loop) + finally: + sys.stdout = old_stdout - return name_space['d'] + return name_space['d'] diff --git a/joy/vui/persist_task.py b/joy/vui/persist_task.py index a7e89f2..8fb11f4 100644 --- a/joy/vui/persist_task.py +++ b/joy/vui/persist_task.py @@ -36,239 +36,239 @@ from joy.vui import core, init_joy_home def open_repo(repo_dir=None, initialize=False): - ''' - Open, or create, and return a Dulwich git repo object for the given - directory. If the dir path doesn't exist it will be created. If it - does exist but isn't a repo the result depends on the ``initialize`` - argument. If it is ``False`` (the default) a ``NotGitRepository`` - exception is raised, otherwise ``git init`` is effected in the dir. - ''' - if not os.path.exists(repo_dir): - os.makedirs(repo_dir, 0o700) - return init_repo(repo_dir) - try: - return Repo(repo_dir) - except NotGitRepository: - if initialize: - return init_repo(repo_dir) - raise + ''' + Open, or create, and return a Dulwich git repo object for the given + directory. If the dir path doesn't exist it will be created. If it + does exist but isn't a repo the result depends on the ``initialize`` + argument. If it is ``False`` (the default) a ``NotGitRepository`` + exception is raised, otherwise ``git init`` is effected in the dir. + ''' + if not os.path.exists(repo_dir): + os.makedirs(repo_dir, 0o700) + return init_repo(repo_dir) + try: + return Repo(repo_dir) + except NotGitRepository: + if initialize: + return init_repo(repo_dir) + raise def init_repo(repo_dir): - ''' - Initialize a git repository in the directory. Stage and commit all - files (toplevel, not those in subdirectories if any) in the dir. - ''' - repo = Repo.init(repo_dir) - init_joy_home.initialize(repo_dir) - repo.stage([ - fn - for fn in os.listdir(repo_dir) - if os.path.isfile(os.path.join(repo_dir, fn)) - ]) - repo.do_commit('Initial commit.', committer=core.COMMITTER) - return repo + ''' + Initialize a git repository in the directory. Stage and commit all + files (toplevel, not those in subdirectories if any) in the dir. + ''' + repo = Repo.init(repo_dir) + init_joy_home.initialize(repo_dir) + repo.stage([ + fn + for fn in os.listdir(repo_dir) + if os.path.isfile(os.path.join(repo_dir, fn)) + ]) + repo.do_commit('Initial commit.', committer=core.COMMITTER) + return repo def make_repo_relative_path_maker(repo): - ''' - Helper function to return a function that returns a path given a path, - that's relative to the repository. - ''' - c = repo.controldir() - def repo_relative_path(path): - return os.path.relpath(path, os.path.commonprefix((c, path))) - return repo_relative_path + ''' + Helper function to return a function that returns a path given a path, + that's relative to the repository. + ''' + c = repo.controldir() + def repo_relative_path(path): + return os.path.relpath(path, os.path.commonprefix((c, path))) + return repo_relative_path class Resource(object): - ''' - Handle the content of a text files as a list of lines, deal with - saving it and staging the changes to a repo. - ''' + ''' + Handle the content of a text files as a list of lines, deal with + saving it and staging the changes to a repo. + ''' - def __init__(self, filename, repo_relative_filename, thing=None): - self.filename = filename - self.repo_relative_filename = repo_relative_filename - self.thing = thing or self._from_file(open(filename)) + def __init__(self, filename, repo_relative_filename, thing=None): + self.filename = filename + self.repo_relative_filename = repo_relative_filename + self.thing = thing or self._from_file(open(filename)) - def _from_file(self, f): - return f.read().splitlines() + def _from_file(self, f): + return f.read().splitlines() - def _to_file(self, f): - for line in self.thing: - print(line, file=f) + def _to_file(self, f): + for line in self.thing: + print(line, file=f) - def persist(self, repo): - ''' - Save the lines to the file and stage the file in the repo. - ''' - with open(self.filename, 'w') as f: - os.chmod(self.filename, 0o600) - self._to_file(f) - f.flush() - os.fsync(f.fileno()) - # For goodness's sake, write it to the disk already! - repo.stage([self.repo_relative_filename]) + def persist(self, repo): + ''' + Save the lines to the file and stage the file in the repo. + ''' + with open(self.filename, 'w') as f: + os.chmod(self.filename, 0o600) + self._to_file(f) + f.flush() + os.fsync(f.fileno()) + # For goodness's sake, write it to the disk already! + repo.stage([self.repo_relative_filename]) class PickledResource(Resource): - ''' - A ``Resource`` subclass that uses ``pickle`` on its file/thing. - ''' + ''' + A ``Resource`` subclass that uses ``pickle`` on its file/thing. + ''' - def _from_file(self, f): - return [pickle.load(f)] + def _from_file(self, f): + return [pickle.load(f)] - def _to_file(self, f): - pickle.dump(self.thing[0], f, protocol=2) + def _to_file(self, f): + pickle.dump(self.thing[0], f, protocol=2) class PersistTask(object): - ''' - This class deals with saving changes to the git repo. - ''' + ''' + This class deals with saving changes to the git repo. + ''' - LIMIT = 10 - MAX_SAVE = 10 + LIMIT = 10 + MAX_SAVE = 10 - def __init__(self, home): - self.home = home - self.repo = open_repo(home) - self._r = make_repo_relative_path_maker(self.repo) - self.counter = Counter() - self.store = {} + def __init__(self, home): + self.home = home + self.repo = open_repo(home) + self._r = make_repo_relative_path_maker(self.repo) + self.counter = Counter() + self.store = {} - def open(self, name): - ''' - Look up the named file in home and return its content_id and data. - ''' - fn = os.path.join(self.home, name) - content_id = name # hash(fn) - try: - resource = self.store[content_id] - except KeyError: - R = PickledResource if name.endswith('.pickle') else Resource - resource = self.store[content_id] = R(fn, self._r(fn)) - return content_id, resource.thing + def open(self, name): + ''' + Look up the named file in home and return its content_id and data. + ''' + fn = os.path.join(self.home, name) + content_id = name # hash(fn) + try: + resource = self.store[content_id] + except KeyError: + R = PickledResource if name.endswith('.pickle') else Resource + resource = self.store[content_id] = R(fn, self._r(fn)) + return content_id, resource.thing - def handle(self, message): - ''' - Handle messages, dispatch to ``handle_FOO()`` methods. - ''' - if isinstance(message, core.OpenMessage): - self.handle_open(message) - elif isinstance(message, core.ModifyMessage): - self.handle_modify(message) - elif isinstance(message, core.PersistMessage): - self.handle_persist(message) - elif isinstance(message, core.ShutdownMessage): - for content_id in self.counter: - self.store[content_id].persist(self.repo) - self.commit('shutdown') + def handle(self, message): + ''' + Handle messages, dispatch to ``handle_FOO()`` methods. + ''' + if isinstance(message, core.OpenMessage): + self.handle_open(message) + elif isinstance(message, core.ModifyMessage): + self.handle_modify(message) + elif isinstance(message, core.PersistMessage): + self.handle_persist(message) + elif isinstance(message, core.ShutdownMessage): + for content_id in self.counter: + self.store[content_id].persist(self.repo) + self.commit('shutdown') - def handle_open(self, message): - ''' - Foo. - ''' - try: - message.content_id, message.thing = self.open(message.name) - except: - message.traceback = traceback.format_exc() - message.status = core.ERROR - else: - message.status = core.SUCCESS + def handle_open(self, message): + ''' + Foo. + ''' + try: + message.content_id, message.thing = self.open(message.name) + except: + message.traceback = traceback.format_exc() + message.status = core.ERROR + else: + message.status = core.SUCCESS - def handle_modify(self, message): - ''' - Foo. - ''' - try: - content_id = message.details['content_id'] - except KeyError: - return - if not content_id: - return - self.counter[content_id] += 1 - if self.counter[content_id] > self.LIMIT: - self.persist(content_id) - self.commit('due to activity') + def handle_modify(self, message): + ''' + Foo. + ''' + try: + content_id = message.details['content_id'] + except KeyError: + return + if not content_id: + return + self.counter[content_id] += 1 + if self.counter[content_id] > self.LIMIT: + self.persist(content_id) + self.commit('due to activity') - def handle_persist(self, message): - ''' - Foo. - ''' - try: - resource = self.store[message.content_id] - except KeyError: - resource = self.handle_persist_new(message) - resource.persist(self.repo) - self.commit('by request from %r' % (message.sender,)) + def handle_persist(self, message): + ''' + Foo. + ''' + try: + resource = self.store[message.content_id] + except KeyError: + resource = self.handle_persist_new(message) + resource.persist(self.repo) + self.commit('by request from %r' % (message.sender,)) - def handle_persist_new(self, message): - ''' - Foo. - ''' - name = message.content_id - check_filename(name) - fn = os.path.join(self.home, name) - thing = message.details['thing'] - R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor! - resource = self.store[name] = R(fn, self._r(fn), thing) - return resource + def handle_persist_new(self, message): + ''' + Foo. + ''' + name = message.content_id + check_filename(name) + fn = os.path.join(self.home, name) + thing = message.details['thing'] + R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor! + resource = self.store[name] = R(fn, self._r(fn), thing) + return resource - def persist(self, content_id): - ''' - Persist a resource. - ''' - del self.counter[content_id] - self.store[content_id].persist(self.repo) + def persist(self, content_id): + ''' + Persist a resource. + ''' + del self.counter[content_id] + self.store[content_id].persist(self.repo) - def task_run(self): - ''' - Stage any outstanding changes. - ''' - if not self.counter: - return - for content_id, _ in self.counter.most_common(self.MAX_SAVE): - self.persist(content_id) - self.commit() + def task_run(self): + ''' + Stage any outstanding changes. + ''' + if not self.counter: + return + for content_id, _ in self.counter.most_common(self.MAX_SAVE): + self.persist(content_id) + self.commit() - def commit(self, message='auto-commit'): - ''' - Commit. - ''' - return self.repo.do_commit(message, committer=core.COMMITTER) + def commit(self, message='auto-commit'): + ''' + Commit. + ''' + return self.repo.do_commit(message, committer=core.COMMITTER) - def scan(self): - ''' - Return a sorted list of all the files in the home dir. - ''' - return sorted([ - fn - for fn in os.listdir(self.home) - if os.path.isfile(os.path.join(self.home, fn)) - ]) + def scan(self): + ''' + Return a sorted list of all the files in the home dir. + ''' + return sorted([ + fn + for fn in os.listdir(self.home) + if os.path.isfile(os.path.join(self.home, fn)) + ]) def check_filename(name): - ''' - Sanity checks for filename. - ''' - # TODO: improve this... - if len(name) > 64: - raise ValueError('bad name %r' % (name,)) - left, dot, right = name.partition('.') - if not left.isalnum() or dot and not right.isalnum(): - raise ValueError('bad name %r' % (name,)) + ''' + Sanity checks for filename. + ''' + # TODO: improve this... + if len(name) > 64: + raise ValueError('bad name %r' % (name,)) + left, dot, right = name.partition('.') + if not left.isalnum() or dot and not right.isalnum(): + raise ValueError('bad name %r' % (name,)) if __name__ == '__main__': - JOY_HOME = os.path.expanduser('~/.thun') - pt = PersistTask(JOY_HOME) - content_id, thing = pt.open('stack.pickle') - pt.persist(content_id) - print(pt.counter) - mm = core.ModifyMessage(None, None, content_id=content_id) - pt.handle(mm) - print(pt.counter) + JOY_HOME = os.path.expanduser('~/.thun') + pt = PersistTask(JOY_HOME) + content_id, thing = pt.open('stack.pickle') + pt.persist(content_id) + print(pt.counter) + mm = core.ModifyMessage(None, None, content_id=content_id) + pt.handle(mm) + print(pt.counter) diff --git a/joy/vui/stack_viewer.py b/joy/vui/stack_viewer.py index 25bdaa1..0ac7928 100644 --- a/joy/vui/stack_viewer.py +++ b/joy/vui/stack_viewer.py @@ -32,44 +32,44 @@ MAX_WIDTH = 64 def fsi(item): - '''Format Stack Item''' - if isinstance(item, tuple): - res = '[%s]' % expression_to_string(item) - elif isinstance(item, str): - res = '"%s"' % item - else: - res = str(item) - if len(res) > MAX_WIDTH: - return res[:MAX_WIDTH - 3] + '...' - return res + '''Format Stack Item''' + if isinstance(item, tuple): + res = '[%s]' % expression_to_string(item) + elif isinstance(item, str): + res = '"%s"' % item + else: + res = str(item) + if len(res) > MAX_WIDTH: + return res[:MAX_WIDTH - 3] + '...' + return res class StackViewer(text_viewer.TextViewer): - def __init__(self, surface): - super(StackViewer, self).__init__(surface) - self.stack_holder = None - self.content_id = 'stack viewer' + def __init__(self, surface): + super(StackViewer, self).__init__(surface) + self.stack_holder = None + self.content_id = 'stack viewer' - def _attach(self, display): - if self.stack_holder: - return - om = core.OpenMessage(self, 'stack.pickle') - display.broadcast(om) - if om.status != core.SUCCESS: - raise RuntimeError('stack unavailable') - self.stack_holder = om.thing + def _attach(self, display): + if self.stack_holder: + return + om = core.OpenMessage(self, 'stack.pickle') + display.broadcast(om) + if om.status != core.SUCCESS: + raise RuntimeError('stack unavailable') + self.stack_holder = om.thing - def _update(self): - self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or [''] + def _update(self): + self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or [''] - def focus(self, display): - self._attach(display) - super(StackViewer, self).focus(display) + def focus(self, display): + self._attach(display) + super(StackViewer, self).focus(display) - def handle(self, message): - if (isinstance(message, core.ModifyMessage) - and message.subject is self.stack_holder - ): - self._update() - self.draw_body() + def handle(self, message): + if (isinstance(message, core.ModifyMessage) + and message.subject is self.stack_holder + ): + self._update() + self.draw_body() diff --git a/joy/vui/text_viewer.py b/joy/vui/text_viewer.py index 763bfa5..70ebe90 100644 --- a/joy/vui/text_viewer.py +++ b/joy/vui/text_viewer.py @@ -32,15 +32,15 @@ import string import pygame from joy.utils.stack import expression_to_string from joy.vui.core import ( - ARROW_KEYS, - BACKGROUND as BG, - FOREGROUND as FG, - CommandMessage, - ModifyMessage, - OpenMessage, - SUCCESS, - push, - ) + ARROW_KEYS, + BACKGROUND as BG, + FOREGROUND as FG, + CommandMessage, + ModifyMessage, + OpenMessage, + SUCCESS, + push, + ) from joy.vui import viewer, font_data #reload(viewer) @@ -50,71 +50,71 @@ MenuViewer = viewer.MenuViewer SELECTION_COLOR = 235, 255, 0, 32 SELECTION_KEYS = { - pygame.K_F1, - pygame.K_F2, - pygame.K_F3, - pygame.K_F4, - } + pygame.K_F1, + pygame.K_F2, + pygame.K_F3, + pygame.K_F4, + } STACK_CHATTER_KEYS = { - pygame.K_F5, - pygame.K_F6, - pygame.K_F7, - pygame.K_F8, - } + pygame.K_F5, + pygame.K_F6, + pygame.K_F7, + pygame.K_F8, + } def _is_command(display, word): - return display.lookup(word) or word.isdigit() or all( - not s or s.isdigit() for s in word.split('.', 1) - ) and len(word) > 1 + return display.lookup(word) or word.isdigit() or all( + not s or s.isdigit() for s in word.split('.', 1) + ) and len(word) > 1 def format_stack_item(content): - if isinstance(content, tuple): - return '[%s]' % expression_to_string(content) - return str(content) + if isinstance(content, tuple): + return '[%s]' % expression_to_string(content) + return str(content) class Font(object): - IMAGE = pygame.image.load(font_data.data, 'Iosevka12.BMP') - LOOKUP = (string.ascii_letters + - string.digits + - '''@#$&_~|`'"%^=-+*/\\<>[]{}(),.;:!?''') + IMAGE = pygame.image.load(font_data.data, 'Iosevka12.BMP') + LOOKUP = (string.ascii_letters + + string.digits + + '''@#$&_~|`'"%^=-+*/\\<>[]{}(),.;:!?''') - def __init__(self, char_w=8, char_h=19, line_h=19): - self.char_w = char_w - self.char_h = char_h - self.line_h = line_h + def __init__(self, char_w=8, char_h=19, line_h=19): + self.char_w = char_w + self.char_h = char_h + self.line_h = line_h - def size(self, text): - return self.char_w * len(text), self.line_h + def size(self, text): + return self.char_w * len(text), self.line_h - def render(self, text): - surface = pygame.Surface(self.size(text)) - surface.fill(BG) - x = 0 - for ch in text: - if not ch.isspace(): - try: - i = self.LOOKUP.index(ch) - except ValueError: - # render a lil box... - r = (x + 1, old_div(self.line_h, 2) - 3, - self.char_w - 2, old_div(self.line_h, 2)) - pygame.draw.rect(surface, FG, r, 1) - else: - iy, ix = divmod(i, 26) - ix *= self.char_w - iy *= self.char_h - area = ix, iy, self.char_w, self.char_h - surface.blit(self.IMAGE, (x, 0), area) - x += self.char_w - return surface + def render(self, text): + surface = pygame.Surface(self.size(text)) + surface.fill(BG) + x = 0 + for ch in text: + if not ch.isspace(): + try: + i = self.LOOKUP.index(ch) + except ValueError: + # render a lil box... + r = (x + 1, old_div(self.line_h, 2) - 3, + self.char_w - 2, old_div(self.line_h, 2)) + pygame.draw.rect(surface, FG, r, 1) + else: + iy, ix = divmod(i, 26) + ix *= self.char_w + iy *= self.char_h + area = ix, iy, self.char_w, self.char_h + surface.blit(self.IMAGE, (x, 0), area) + x += self.char_w + return surface - def __contains__(self, char): - assert len(char) == 1, repr(char) - return char in self.LOOKUP + def __contains__(self, char): + assert len(char) == 1, repr(char) + return char in self.LOOKUP FONT = Font() @@ -122,373 +122,373 @@ FONT = Font() class TextViewer(MenuViewer): - MINIMUM_HEIGHT = FONT.line_h + 3 - CLOSE_TEXT = FONT.render('close') - GROW_TEXT = FONT.render('grow') + MINIMUM_HEIGHT = FONT.line_h + 3 + CLOSE_TEXT = FONT.render('close') + GROW_TEXT = FONT.render('grow') - class Cursor(object): + class Cursor(object): - def __init__(self, viewer): - self.v = viewer - self.x = self.y = 0 - self.w, self.h = 2, FONT.line_h - self.mem = pygame.Surface((self.w, self.h)) - self.can_fade = False + def __init__(self, viewer): + self.v = viewer + self.x = self.y = 0 + self.w, self.h = 2, FONT.line_h + self.mem = pygame.Surface((self.w, self.h)) + self.can_fade = False - def set_to(self, x, y): - self.fade() - self.x, self.y = x, y - self.draw() + def set_to(self, x, y): + self.fade() + self.x, self.y = x, y + self.draw() - def draw(self): - r = self.x * FONT.char_w, self.screen_y(), self.w, self.h - self.mem.blit(self.v.body_surface, (0, 0), r) - self.v.body_surface.fill(FG, r) - self.can_fade = True + def draw(self): + r = self.x * FONT.char_w, self.screen_y(), self.w, self.h + self.mem.blit(self.v.body_surface, (0, 0), r) + self.v.body_surface.fill(FG, r) + self.can_fade = True - def fade(self): - if self.can_fade: - dest = self.x * FONT.char_w, self.screen_y() - self.v.body_surface.blit(self.mem, dest) - self.can_fade = False + def fade(self): + if self.can_fade: + dest = self.x * FONT.char_w, self.screen_y() + self.v.body_surface.blit(self.mem, dest) + self.can_fade = False - def screen_y(self, row=None): - if row is None: row = self.y - return (row - self.v.at_line) * FONT.line_h + def screen_y(self, row=None): + if row is None: row = self.y + return (row - self.v.at_line) * FONT.line_h - def up(self, _mod): - if self.y: - self.fade() - self.y -= 1 - self.x = min(self.x, len(self.v.lines[self.y])) - self.draw() + def up(self, _mod): + if self.y: + self.fade() + self.y -= 1 + self.x = min(self.x, len(self.v.lines[self.y])) + self.draw() - def down(self, _mod): - if self.y < len(self.v.lines) - 1: - self.fade() - self.y += 1 - self.x = min(self.x, len(self.v.lines[self.y])) - self.draw() - self._check_scroll() + def down(self, _mod): + if self.y < len(self.v.lines) - 1: + self.fade() + self.y += 1 + self.x = min(self.x, len(self.v.lines[self.y])) + self.draw() + self._check_scroll() - def left(self, _mod): - if self.x: - self.fade() - self.x -= 1 - self.draw() - elif self.y: - self.fade() - self.y -= 1 - self.x = len(self.v.lines[self.y]) - self.draw() - self._check_scroll() + def left(self, _mod): + if self.x: + self.fade() + self.x -= 1 + self.draw() + elif self.y: + self.fade() + self.y -= 1 + self.x = len(self.v.lines[self.y]) + self.draw() + self._check_scroll() - def right(self, _mod): - if self.x < len(self.v.lines[self.y]): - self.fade() - self.x += 1 - self.draw() - elif self.y < len(self.v.lines) - 1: - self.fade() - self.y += 1 - self.x = 0 - self.draw() - self._check_scroll() + def right(self, _mod): + if self.x < len(self.v.lines[self.y]): + self.fade() + self.x += 1 + self.draw() + elif self.y < len(self.v.lines) - 1: + self.fade() + self.y += 1 + self.x = 0 + self.draw() + self._check_scroll() - def _check_scroll(self): - if self.y < self.v.at_line: - self.v.scroll_down() - elif self.y > self.v.at_line + self.v.h_in_lines: - self.v.scroll_up() + def _check_scroll(self): + if self.y < self.v.at_line: + self.v.scroll_down() + elif self.y > self.v.at_line + self.v.h_in_lines: + self.v.scroll_up() - def __init__(self, surface): - self.cursor = self.Cursor(self) - MenuViewer.__init__(self, surface) - self.lines = [''] - self.content_id = None - self.at_line = 0 - self.bg = BG - self.command = self.command_rect = None - self._sel_start = self._sel_end = None + def __init__(self, surface): + self.cursor = self.Cursor(self) + MenuViewer.__init__(self, surface) + self.lines = [''] + self.content_id = None + self.at_line = 0 + self.bg = BG + self.command = self.command_rect = None + self._sel_start = self._sel_end = None - def resurface(self, surface): - self.cursor.fade() - MenuViewer.resurface(self, surface) + def resurface(self, surface): + self.cursor.fade() + MenuViewer.resurface(self, surface) - w, h = self.CLOSE_TEXT.get_size() - self.close_rect = pygame.rect.Rect(self.w - 2 - w, 1, w, h) - w, h = self.GROW_TEXT.get_size() - self.grow_rect = pygame.rect.Rect(1, 1, w, h) + w, h = self.CLOSE_TEXT.get_size() + self.close_rect = pygame.rect.Rect(self.w - 2 - w, 1, w, h) + w, h = self.GROW_TEXT.get_size() + self.grow_rect = pygame.rect.Rect(1, 1, w, h) - self.body_surface = surface.subsurface(self.body_rect) - self.line_w = old_div(self.body_rect.w, FONT.char_w) + 1 - self.h_in_lines = old_div(self.body_rect.h, FONT.line_h) - 1 - self.command_rect = self.command = None - self._sel_start = self._sel_end = None + self.body_surface = surface.subsurface(self.body_rect) + self.line_w = old_div(self.body_rect.w, FONT.char_w) + 1 + self.h_in_lines = old_div(self.body_rect.h, FONT.line_h) - 1 + self.command_rect = self.command = None + self._sel_start = self._sel_end = None - def handle(self, message): - if super(TextViewer, self).handle(message): - return - if (isinstance(message, ModifyMessage) - and message.subject is self.lines - ): - # TODO: check self.at_line - self.draw_body() + def handle(self, message): + if super(TextViewer, self).handle(message): + return + if (isinstance(message, ModifyMessage) + and message.subject is self.lines + ): + # TODO: check self.at_line + self.draw_body() - # Drawing + # Drawing - def draw_menu(self): - #MenuViewer.draw_menu(self) - self.surface.blit(self.GROW_TEXT, (1, 1)) - self.surface.blit(self.CLOSE_TEXT, - (self.w - 2 - self.close_rect.w, 1)) - if self.content_id: - self.surface.blit(FONT.render('| ' + self.content_id), - (self.grow_rect.w + FONT.char_w + 3, 1)) - self.surface.fill( # light grey background - (196, 196, 196), - (0, 0, self.w - 1, self.MINIMUM_HEIGHT), - pygame.BLEND_MULT - ) + def draw_menu(self): + #MenuViewer.draw_menu(self) + self.surface.blit(self.GROW_TEXT, (1, 1)) + self.surface.blit(self.CLOSE_TEXT, + (self.w - 2 - self.close_rect.w, 1)) + if self.content_id: + self.surface.blit(FONT.render('| ' + self.content_id), + (self.grow_rect.w + FONT.char_w + 3, 1)) + self.surface.fill( # light grey background + (196, 196, 196), + (0, 0, self.w - 1, self.MINIMUM_HEIGHT), + pygame.BLEND_MULT + ) - def draw_body(self): - MenuViewer.draw_body(self) - ys = range(0, self.body_rect.height, FONT.line_h) - ls = self.lines[self.at_line:self.at_line + self.h_in_lines + 2] - for y, line in zip(ys, ls): - self.draw_line(y, line) + def draw_body(self): + MenuViewer.draw_body(self) + ys = range(0, self.body_rect.height, FONT.line_h) + ls = self.lines[self.at_line:self.at_line + self.h_in_lines + 2] + for y, line in zip(ys, ls): + self.draw_line(y, line) - def draw_line(self, y, line): - surface = FONT.render(line[:self.line_w]) - self.body_surface.blit(surface, (0, y)) + def draw_line(self, y, line): + surface = FONT.render(line[:self.line_w]) + self.body_surface.blit(surface, (0, y)) - def _redraw_line(self, row): - try: line = self.lines[row] - except IndexError: line = ' ' * self.line_w - else: - n = self.line_w - len(line) - if n > 0: line = line + ' ' * n - self.draw_line(self.cursor.screen_y(row), line) + def _redraw_line(self, row): + try: line = self.lines[row] + except IndexError: line = ' ' * self.line_w + else: + n = self.line_w - len(line) + if n > 0: line = line + ' ' * n + self.draw_line(self.cursor.screen_y(row), line) - # General Functionality + # General Functionality - def focus(self, display): - self.cursor.v = self - self.cursor.draw() + def focus(self, display): + self.cursor.v = self + self.cursor.draw() - def unfocus(self): - self.cursor.fade() + def unfocus(self): + self.cursor.fade() - def scroll_up(self): - if self.at_line < len(self.lines) - 1: - self._fade_command() - self._deselect() - self._sel_start = self._sel_end = None - self.at_line += 1 - self.body_surface.scroll(0, -FONT.line_h) - row = self.h_in_lines + self.at_line - self._redraw_line(row) - self._redraw_line(row + 1) - self.cursor.draw() + def scroll_up(self): + if self.at_line < len(self.lines) - 1: + self._fade_command() + self._deselect() + self._sel_start = self._sel_end = None + self.at_line += 1 + self.body_surface.scroll(0, -FONT.line_h) + row = self.h_in_lines + self.at_line + self._redraw_line(row) + self._redraw_line(row + 1) + self.cursor.draw() - def scroll_down(self): - if self.at_line: - self._fade_command() - self._deselect() - self._sel_start = self._sel_end = None - self.at_line -= 1 - self.body_surface.scroll(0, FONT.line_h) - self._redraw_line(self.at_line) - self.cursor.draw() + def scroll_down(self): + if self.at_line: + self._fade_command() + self._deselect() + self._sel_start = self._sel_end = None + self.at_line -= 1 + self.body_surface.scroll(0, FONT.line_h) + self._redraw_line(self.at_line) + self.cursor.draw() - def command_down(self, display, x, y): - if self.command_rect and self.command_rect.collidepoint(x, y): - return - self._fade_command() - line, column, _row = self.at(x, y) - word_start = line.rfind(' ', 0, column) + 1 - word_end = line.find(' ', column) - if word_end == -1: word_end = len(line) - word = line[word_start:word_end] - if not _is_command(display, word): - return - r = self.command_rect = pygame.Rect( - word_start * FONT.char_w, # x - old_div(y, FONT.line_h) * FONT.line_h, # y - len(word) * FONT.char_w, # w - FONT.line_h # h - ) - pygame.draw.line(self.body_surface, FG, r.bottomleft, r.bottomright) - self.command = word + def command_down(self, display, x, y): + if self.command_rect and self.command_rect.collidepoint(x, y): + return + self._fade_command() + line, column, _row = self.at(x, y) + word_start = line.rfind(' ', 0, column) + 1 + word_end = line.find(' ', column) + if word_end == -1: word_end = len(line) + word = line[word_start:word_end] + if not _is_command(display, word): + return + r = self.command_rect = pygame.Rect( + word_start * FONT.char_w, # x + old_div(y, FONT.line_h) * FONT.line_h, # y + len(word) * FONT.char_w, # w + FONT.line_h # h + ) + pygame.draw.line(self.body_surface, FG, r.bottomleft, r.bottomright) + self.command = word - def command_up(self, display): - if self.command: - command = self.command - self._fade_command() - display.broadcast(CommandMessage(self, command)) + def command_up(self, display): + if self.command: + command = self.command + self._fade_command() + display.broadcast(CommandMessage(self, command)) - def _fade_command(self): - self.command = None - r, self.command_rect = self.command_rect, None - if r: - pygame.draw.line(self.body_surface, BG, r.bottomleft, r.bottomright) + def _fade_command(self): + self.command = None + r, self.command_rect = self.command_rect, None + if r: + pygame.draw.line(self.body_surface, BG, r.bottomleft, r.bottomright) - def at(self, x, y): - ''' - Given screen coordinates return the line, row, and column of the - character there. - ''' - row = self.at_line + old_div(y, FONT.line_h) - try: - line = self.lines[row] - except IndexError: - row = len(self.lines) - 1 - line = self.lines[row] - column = len(line) - else: - column = min(old_div(x, FONT.char_w), len(line)) - return line, column, row + def at(self, x, y): + ''' + Given screen coordinates return the line, row, and column of the + character there. + ''' + row = self.at_line + old_div(y, FONT.line_h) + try: + line = self.lines[row] + except IndexError: + row = len(self.lines) - 1 + line = self.lines[row] + column = len(line) + else: + column = min(old_div(x, FONT.char_w), len(line)) + return line, column, row - # Event Processing + # Event Processing - def body_click(self, display, x, y, button): - if button == 1: - _line, column, row = self.at(x, y) - self.cursor.set_to(column, row) - elif button == 2: - if pygame.KMOD_SHIFT & pygame.key.get_mods(): - self.scroll_up() - else: - self.scroll_down() - elif button == 3: - self.command_down(display, x, y) - elif button == 4: self.scroll_down() - elif button == 5: self.scroll_up() + def body_click(self, display, x, y, button): + if button == 1: + _line, column, row = self.at(x, y) + self.cursor.set_to(column, row) + elif button == 2: + if pygame.KMOD_SHIFT & pygame.key.get_mods(): + self.scroll_up() + else: + self.scroll_down() + elif button == 3: + self.command_down(display, x, y) + elif button == 4: self.scroll_down() + elif button == 5: self.scroll_up() - def menu_click(self, display, x, y, button): - if MenuViewer.menu_click(self, display, x, y, button): - return True + def menu_click(self, display, x, y, button): + if MenuViewer.menu_click(self, display, x, y, button): + return True - def mouse_up(self, display, x, y, button): - if MenuViewer.mouse_up(self, display, x, y, button): - return True - elif button == 3 and self.body_rect.collidepoint(x, y): - self.command_up(display) + def mouse_up(self, display, x, y, button): + if MenuViewer.mouse_up(self, display, x, y, button): + return True + elif button == 3 and self.body_rect.collidepoint(x, y): + self.command_up(display) - def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2): - if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y, - button0, button1, button2): - return True - if (button0 - and display.focused_viewer is self - and self.body_rect.collidepoint(x, y) - ): - bx, by = self.body_rect.topleft - _line, column, row = self.at(x - bx, y - by) - self.cursor.set_to(column, row) - elif button2 and self.body_rect.collidepoint(x, y): - bx, by = self.body_rect.topleft - self.command_down(display, x - bx, y - by) + def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2): + if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y, + button0, button1, button2): + return True + if (button0 + and display.focused_viewer is self + and self.body_rect.collidepoint(x, y) + ): + bx, by = self.body_rect.topleft + _line, column, row = self.at(x - bx, y - by) + self.cursor.set_to(column, row) + elif button2 and self.body_rect.collidepoint(x, y): + bx, by = self.body_rect.topleft + self.command_down(display, x - bx, y - by) - def close(self): - self._sel_start = self._sel_end = None + def close(self): + self._sel_start = self._sel_end = None - def key_down(self, display, uch, key, mod): + def key_down(self, display, uch, key, mod): - if key in SELECTION_KEYS: - self._selection_key(display, key, mod) - return - if key in STACK_CHATTER_KEYS: - self._stack_chatter_key(display, key, mod) - return - if key in ARROW_KEYS: - self._arrow_key(key, mod) - return + if key in SELECTION_KEYS: + self._selection_key(display, key, mod) + return + if key in STACK_CHATTER_KEYS: + self._stack_chatter_key(display, key, mod) + return + if key in ARROW_KEYS: + self._arrow_key(key, mod) + return - line, i = self.lines[self.cursor.y], self.cursor.x - modified = () - if key == pygame.K_RETURN: - self._return_key(mod, line, i) - modified = True - elif key == pygame.K_BACKSPACE: - modified = self._backspace_key(mod, line, i) - elif key == pygame.K_DELETE: - modified = self._delete_key(mod, line, i) - elif key == pygame.K_INSERT: - modified = self._insert_key(display, mod, line, i) - elif uch and uch in FONT or uch == ' ': - self._printable_key(uch, mod, line, i) - modified = True - else: - print('%r %i %s' % (uch, key, bin(mod))) + line, i = self.lines[self.cursor.y], self.cursor.x + modified = () + if key == pygame.K_RETURN: + self._return_key(mod, line, i) + modified = True + elif key == pygame.K_BACKSPACE: + modified = self._backspace_key(mod, line, i) + elif key == pygame.K_DELETE: + modified = self._delete_key(mod, line, i) + elif key == pygame.K_INSERT: + modified = self._insert_key(display, mod, line, i) + elif uch and uch in FONT or uch == ' ': + self._printable_key(uch, mod, line, i) + modified = True + else: + print('%r %i %s' % (uch, key, bin(mod))) - if modified: - # The selection is fragile. - self._deselect() - self._sel_start = self._sel_end = None - message = ModifyMessage( - self, self.lines, content_id=self.content_id) - display.broadcast(message) + if modified: + # The selection is fragile. + self._deselect() + self._sel_start = self._sel_end = None + message = ModifyMessage( + self, self.lines, content_id=self.content_id) + display.broadcast(message) - def _stack_chatter_key(self, display, key, mod): - if key == pygame.K_F5: - if mod & pygame.KMOD_SHIFT: - command = 'roll<' - else: - command = 'swap' - elif key == pygame.K_F6: - if mod & pygame.KMOD_SHIFT: - command = 'roll>' - else: - command = 'dup' - elif key == pygame.K_F7: - if mod & pygame.KMOD_SHIFT: - command = 'tuck' - else: - command = 'over' + def _stack_chatter_key(self, display, key, mod): + if key == pygame.K_F5: + if mod & pygame.KMOD_SHIFT: + command = 'roll<' + else: + command = 'swap' + elif key == pygame.K_F6: + if mod & pygame.KMOD_SHIFT: + command = 'roll>' + else: + command = 'dup' + elif key == pygame.K_F7: + if mod & pygame.KMOD_SHIFT: + command = 'tuck' + else: + command = 'over' ## elif key == pygame.K_F8: ## if mod & pygame.KMOD_SHIFT: ## command = '' ## else: ## command = '' - else: - return - display.broadcast(CommandMessage(self, command)) + else: + return + display.broadcast(CommandMessage(self, command)) - # Selection Handling + # Selection Handling - def _selection_key(self, display, key, mod): - self.cursor.fade() - self._deselect() - if key == pygame.K_F1: # set sel start - self._sel_start = self.cursor.y, self.cursor.x - self._update_selection() - elif key == pygame.K_F2: # set sel end - self._sel_end = self.cursor.y, self.cursor.x - self._update_selection() - elif key == pygame.K_F3: # copy - if mod & pygame.KMOD_SHIFT: - self._parse_selection(display) - else: - self._copy_selection(display) - self._update_selection() - elif key == pygame.K_F4: # cut or delete - if mod & pygame.KMOD_SHIFT: - self._delete_selection(display) - else: - self._cut_selection(display) - self.cursor.draw() + def _selection_key(self, display, key, mod): + self.cursor.fade() + self._deselect() + if key == pygame.K_F1: # set sel start + self._sel_start = self.cursor.y, self.cursor.x + self._update_selection() + elif key == pygame.K_F2: # set sel end + self._sel_end = self.cursor.y, self.cursor.x + self._update_selection() + elif key == pygame.K_F3: # copy + if mod & pygame.KMOD_SHIFT: + self._parse_selection(display) + else: + self._copy_selection(display) + self._update_selection() + elif key == pygame.K_F4: # cut or delete + if mod & pygame.KMOD_SHIFT: + self._delete_selection(display) + else: + self._cut_selection(display) + self.cursor.draw() - def _deselect(self): - if self._has_selection(): - srow, erow = self._sel_start[0], self._sel_end[0] - # Just erase the whole selection. - for r in range(min(srow, erow), max(srow, erow) + 1): - self._redraw_line(r) + def _deselect(self): + if self._has_selection(): + srow, erow = self._sel_start[0], self._sel_end[0] + # Just erase the whole selection. + for r in range(min(srow, erow), max(srow, erow) + 1): + self._redraw_line(r) - def _copy_selection(self, display): - if push(self, self._get_selection(), display.broadcast) == SUCCESS: - return True + def _copy_selection(self, display): + if push(self, self._get_selection(), display.broadcast) == SUCCESS: + return True ## om = OpenMessage(self, 'stack.pickle') ## display.broadcast(om) ## if om.status == SUCCESS: @@ -497,208 +497,208 @@ class TextViewer(MenuViewer): ## display.broadcast(ModifyMessage( ## self, om.thing, content_id=om.content_id)) - def _parse_selection(self, display): - if self._has_selection(): - if self._copy_selection(display): - display.broadcast(CommandMessage(self, 'parse')) + def _parse_selection(self, display): + if self._has_selection(): + if self._copy_selection(display): + display.broadcast(CommandMessage(self, 'parse')) - def _cut_selection(self, display): - if self._has_selection(): - if self._copy_selection(display): - self._delete_selection(display) + def _cut_selection(self, display): + if self._has_selection(): + if self._copy_selection(display): + self._delete_selection(display) - def _delete_selection(self, display): - if not self._has_selection(): - return - self.cursor.fade() - srow, scolumn, erow, ecolumn = self._selection_coords() - if srow == erow: - line = self.lines[srow] - self.lines[srow] = line[:scolumn] + line[ecolumn:] - else: - left = self.lines[srow][:scolumn] - right = self.lines[erow][ecolumn:] - self.lines[srow:erow + 1] = [left + right] - self.draw_body() - self.cursor.set_to(srow, scolumn) - display.broadcast(ModifyMessage( - self, self.lines, content_id=self.content_id)) + def _delete_selection(self, display): + if not self._has_selection(): + return + self.cursor.fade() + srow, scolumn, erow, ecolumn = self._selection_coords() + if srow == erow: + line = self.lines[srow] + self.lines[srow] = line[:scolumn] + line[ecolumn:] + else: + left = self.lines[srow][:scolumn] + right = self.lines[erow][ecolumn:] + self.lines[srow:erow + 1] = [left + right] + self.draw_body() + self.cursor.set_to(srow, scolumn) + display.broadcast(ModifyMessage( + self, self.lines, content_id=self.content_id)) - def _has_selection(self): - return (self._sel_start - and self._sel_end - and self._sel_start != self._sel_end) + def _has_selection(self): + return (self._sel_start + and self._sel_end + and self._sel_start != self._sel_end) - def _get_selection(self): - '''Return the current selection if any as a single string.''' - if not self._has_selection(): - return '' - srow, scolumn, erow, ecolumn = self._selection_coords() - if srow == erow: - return str(self.lines[srow][scolumn:ecolumn]) - lines = [] - assert srow < erow - while srow <= erow: - line = self.lines[srow] - e = ecolumn if srow == erow else len(line) - lines.append(line[scolumn:e]) - scolumn = 0 - srow += 1 - return str('\n'.join(lines)) + def _get_selection(self): + '''Return the current selection if any as a single string.''' + if not self._has_selection(): + return '' + srow, scolumn, erow, ecolumn = self._selection_coords() + if srow == erow: + return str(self.lines[srow][scolumn:ecolumn]) + lines = [] + assert srow < erow + while srow <= erow: + line = self.lines[srow] + e = ecolumn if srow == erow else len(line) + lines.append(line[scolumn:e]) + scolumn = 0 + srow += 1 + return str('\n'.join(lines)) - def _selection_coords(self): - (srow, scolumn), (erow, ecolumn) = ( - min(self._sel_start, self._sel_end), - max(self._sel_start, self._sel_end) - ) - return srow, scolumn, erow, ecolumn + def _selection_coords(self): + (srow, scolumn), (erow, ecolumn) = ( + min(self._sel_start, self._sel_end), + max(self._sel_start, self._sel_end) + ) + return srow, scolumn, erow, ecolumn - def _update_selection(self): - if self._sel_start is None and self._sel_end: - self._sel_start = self._sel_end - elif self._sel_end is None and self._sel_start: - self._sel_end = self._sel_start - assert self._sel_start and self._sel_end - if self._sel_start != self._sel_end: - for rect in self._iter_selection_rectangles(): - self.body_surface.fill( - SELECTION_COLOR, - rect, - pygame.BLEND_RGBA_MULT - ) + def _update_selection(self): + if self._sel_start is None and self._sel_end: + self._sel_start = self._sel_end + elif self._sel_end is None and self._sel_start: + self._sel_end = self._sel_start + assert self._sel_start and self._sel_end + if self._sel_start != self._sel_end: + for rect in self._iter_selection_rectangles(): + self.body_surface.fill( + SELECTION_COLOR, + rect, + pygame.BLEND_RGBA_MULT + ) - def _iter_selection_rectangles(self, ): - srow, scolumn, erow, ecolumn = self._selection_coords() - if srow == erow: - yield ( - scolumn * FONT.char_w, - self.cursor.screen_y(srow), - (ecolumn - scolumn) * FONT.char_w, - FONT.line_h - ) - return - lines = self.lines[srow:erow + 1] - assert len(lines) >= 2 - first_line = lines[0] - yield ( - scolumn * FONT.char_w, - self.cursor.screen_y(srow), - (len(first_line) - scolumn) * FONT.char_w, - FONT.line_h - ) - yield ( - 0, - self.cursor.screen_y(erow), - ecolumn * FONT.char_w, - FONT.line_h - ) - if len(lines) > 2: - for line in lines[1:-1]: - srow += 1 - yield ( - 0, - self.cursor.screen_y(srow), - len(line) * FONT.char_w, - FONT.line_h - ) + def _iter_selection_rectangles(self, ): + srow, scolumn, erow, ecolumn = self._selection_coords() + if srow == erow: + yield ( + scolumn * FONT.char_w, + self.cursor.screen_y(srow), + (ecolumn - scolumn) * FONT.char_w, + FONT.line_h + ) + return + lines = self.lines[srow:erow + 1] + assert len(lines) >= 2 + first_line = lines[0] + yield ( + scolumn * FONT.char_w, + self.cursor.screen_y(srow), + (len(first_line) - scolumn) * FONT.char_w, + FONT.line_h + ) + yield ( + 0, + self.cursor.screen_y(erow), + ecolumn * FONT.char_w, + FONT.line_h + ) + if len(lines) > 2: + for line in lines[1:-1]: + srow += 1 + yield ( + 0, + self.cursor.screen_y(srow), + len(line) * FONT.char_w, + FONT.line_h + ) - # Key Handlers + # Key Handlers - def _printable_key(self, uch, _mod, line, i): - line = line[:i] + uch + line[i:] - self.lines[self.cursor.y] = line - self.cursor.fade() - self.cursor.x += 1 - self.draw_line(self.cursor.screen_y(), line) - self.cursor.draw() + def _printable_key(self, uch, _mod, line, i): + line = line[:i] + uch + line[i:] + self.lines[self.cursor.y] = line + self.cursor.fade() + self.cursor.x += 1 + self.draw_line(self.cursor.screen_y(), line) + self.cursor.draw() - def _backspace_key(self, _mod, line, i): - res = False - if i: - line = line[:i - 1] + line[i:] - self.lines[self.cursor.y] = line - self.cursor.fade() - self.cursor.x -= 1 - self.draw_line(self.cursor.screen_y(), line + ' ') - self.cursor.draw() - res = True - elif self.cursor.y: - y = self.cursor.y - left, right = self.lines[y - 1:y + 1] - self.lines[y - 1:y + 1] = [left + right] - self.cursor.x = len(left) - self.cursor.y -= 1 - self.draw_body() - self.cursor.draw() - res = True - return res + def _backspace_key(self, _mod, line, i): + res = False + if i: + line = line[:i - 1] + line[i:] + self.lines[self.cursor.y] = line + self.cursor.fade() + self.cursor.x -= 1 + self.draw_line(self.cursor.screen_y(), line + ' ') + self.cursor.draw() + res = True + elif self.cursor.y: + y = self.cursor.y + left, right = self.lines[y - 1:y + 1] + self.lines[y - 1:y + 1] = [left + right] + self.cursor.x = len(left) + self.cursor.y -= 1 + self.draw_body() + self.cursor.draw() + res = True + return res - def _delete_key(self, _mod, line, i): - res = False - if i < len(line): - line = line[:i] + line[i + 1:] - self.lines[self.cursor.y] = line - self.cursor.fade() - self.draw_line(self.cursor.screen_y(), line + ' ') - self.cursor.draw() - res = True - elif self.cursor.y < len(self.lines) - 1: - y = self.cursor.y - left, right = self.lines[y:y + 2] - self.lines[y:y + 2] = [left + right] - self.draw_body() - self.cursor.draw() - res = True - return res + def _delete_key(self, _mod, line, i): + res = False + if i < len(line): + line = line[:i] + line[i + 1:] + self.lines[self.cursor.y] = line + self.cursor.fade() + self.draw_line(self.cursor.screen_y(), line + ' ') + self.cursor.draw() + res = True + elif self.cursor.y < len(self.lines) - 1: + y = self.cursor.y + left, right = self.lines[y:y + 2] + self.lines[y:y + 2] = [left + right] + self.draw_body() + self.cursor.draw() + res = True + return res - def _arrow_key(self, key, mod): - if key == pygame.K_UP: self.cursor.up(mod) - elif key == pygame.K_DOWN: self.cursor.down(mod) - elif key == pygame.K_LEFT: self.cursor.left(mod) - elif key == pygame.K_RIGHT: self.cursor.right(mod) + def _arrow_key(self, key, mod): + if key == pygame.K_UP: self.cursor.up(mod) + elif key == pygame.K_DOWN: self.cursor.down(mod) + elif key == pygame.K_LEFT: self.cursor.left(mod) + elif key == pygame.K_RIGHT: self.cursor.right(mod) - def _return_key(self, _mod, line, i): - self.cursor.fade() - # Ignore the mods for now. - n = self.cursor.y - self.lines[n:n + 1] = [line[:i], line[i:]] - self.cursor.y += 1 - self.cursor.x = 0 - if self.cursor.y > self.at_line + self.h_in_lines: - self.scroll_up() - else: - self.draw_body() - self.cursor.draw() + def _return_key(self, _mod, line, i): + self.cursor.fade() + # Ignore the mods for now. + n = self.cursor.y + self.lines[n:n + 1] = [line[:i], line[i:]] + self.cursor.y += 1 + self.cursor.x = 0 + if self.cursor.y > self.at_line + self.h_in_lines: + self.scroll_up() + else: + self.draw_body() + self.cursor.draw() - def _insert_key(self, display, mod, _line, _i): - om = OpenMessage(self, 'stack.pickle') - display.broadcast(om) - if om.status != SUCCESS: - return - stack = om.thing[0] - if stack: - content = format_stack_item(stack[0]) - if self.insert(content): - if mod & pygame.KMOD_SHIFT: - display.broadcast(CommandMessage(self, 'pop')) - return True + def _insert_key(self, display, mod, _line, _i): + om = OpenMessage(self, 'stack.pickle') + display.broadcast(om) + if om.status != SUCCESS: + return + stack = om.thing[0] + if stack: + content = format_stack_item(stack[0]) + if self.insert(content): + if mod & pygame.KMOD_SHIFT: + display.broadcast(CommandMessage(self, 'pop')) + return True - def insert(self, content): - assert isinstance(content, basestring), repr(content) - if content: - self.cursor.fade() - row, column = self.cursor.y, self.cursor.x - line = self.lines[row] - lines = (line[:column] + content + line[column:]).splitlines() - self.lines[row:row + 1] = lines - self.draw_body() - self.cursor.y = row + len(lines) - 1 - self.cursor.x = len(lines[-1]) - len(line) + column - self.cursor.draw() - return True + def insert(self, content): + assert isinstance(content, basestring), repr(content) + if content: + self.cursor.fade() + row, column = self.cursor.y, self.cursor.x + line = self.lines[row] + lines = (line[:column] + content + line[column:]).splitlines() + self.lines[row:row + 1] = lines + self.draw_body() + self.cursor.y = row + len(lines) - 1 + self.cursor.x = len(lines[-1]) - len(line) + column + self.cursor.draw() + return True - def append(self, content): - self.cursor.fade() - self.cursor.y = len(self.lines) - 1 - self.cursor.x = len(self.lines[self.cursor.y]) - self.insert(content) + def append(self, content): + self.cursor.fade() + self.cursor.y = len(self.lines) - 1 + self.cursor.x = len(self.lines[self.cursor.y]) + self.insert(content) diff --git a/joy/vui/viewer.py b/joy/vui/viewer.py index 97fcb1c..d769d36 100644 --- a/joy/vui/viewer.py +++ b/joy/vui/viewer.py @@ -32,208 +32,208 @@ from joy.vui.core import BACKGROUND, FOREGROUND class Viewer(object): - ''' - Base Viewer class - ''' + ''' + Base Viewer class + ''' - MINIMUM_HEIGHT = 11 + MINIMUM_HEIGHT = 11 - def __init__(self, surface): - self.resurface(surface) - self.last_touch = 0, 0 + def __init__(self, surface): + self.resurface(surface) + self.last_touch = 0, 0 - def resurface(self, surface): - self.w, self.h = surface.get_width(), surface.get_height() - self.surface = surface + def resurface(self, surface): + self.w, self.h = surface.get_width(), surface.get_height() + self.surface = surface - def split(self, y): - ''' - Split the viewer at the y coordinate (which is relative to the - viewer's surface and must be inside it somewhere) and return the - remaining height. The upper part of the viewer remains (and gets - redrawn on a new surface) and the lower space is now available - for e.g. a new viewer. - ''' - assert y >= self.MINIMUM_HEIGHT - new_viewer_h = self.h - y - self.resurface(self.surface.subsurface((0, 0, self.w, y))) - if y <= self.last_touch[1]: self.last_touch = 0, 0 - self.draw() - return new_viewer_h + def split(self, y): + ''' + Split the viewer at the y coordinate (which is relative to the + viewer's surface and must be inside it somewhere) and return the + remaining height. The upper part of the viewer remains (and gets + redrawn on a new surface) and the lower space is now available + for e.g. a new viewer. + ''' + assert y >= self.MINIMUM_HEIGHT + new_viewer_h = self.h - y + self.resurface(self.surface.subsurface((0, 0, self.w, y))) + if y <= self.last_touch[1]: self.last_touch = 0, 0 + self.draw() + return new_viewer_h - def handle(self, message): - assert self is not message.sender - pass + def handle(self, message): + assert self is not message.sender + pass - def draw(self): - '''Draw the viewer onto its surface.''' - self.surface.fill(BACKGROUND) - x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1 - # Right-hand side. - pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h)) - # Between header and body. - pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y)) - # Bottom. - pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h)) + def draw(self): + '''Draw the viewer onto its surface.''' + self.surface.fill(BACKGROUND) + x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1 + # Right-hand side. + pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h)) + # Between header and body. + pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y)) + # Bottom. + pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h)) - def close(self): - '''Close the viewer and release any resources, etc...''' + def close(self): + '''Close the viewer and release any resources, etc...''' - def focus(self, display): - pass + def focus(self, display): + pass - def unfocus(self): - pass + def unfocus(self): + pass - # Event handling. + # Event handling. - def mouse_down(self, display, x, y, button): - self.last_touch = x, y + def mouse_down(self, display, x, y, button): + self.last_touch = x, y - def mouse_up(self, display, x, y, button): - pass + def mouse_up(self, display, x, y, button): + pass - def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2): - pass + def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2): + pass - def key_up(self, display, key, mod): - if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q - display.close_viewer(self) - return True - if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g - display.grow_viewer(self) - return True + def key_up(self, display, key, mod): + if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q + display.close_viewer(self) + return True + if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g + display.grow_viewer(self) + return True - def key_down(self, display, uch, key, mod): - pass + def key_down(self, display, uch, key, mod): + pass class MenuViewer(Viewer): - ''' - MenuViewer class - ''' + ''' + MenuViewer class + ''' - MINIMUM_HEIGHT = 26 + MINIMUM_HEIGHT = 26 - def __init__(self, surface): - Viewer.__init__(self, surface) - self.resizing = 0 - self.bg = 100, 150, 100 + def __init__(self, surface): + Viewer.__init__(self, surface) + self.resizing = 0 + self.bg = 100, 150, 100 - def resurface(self, surface): - Viewer.resurface(self, surface) - n = self.MINIMUM_HEIGHT - 2 - self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n) - self.grow_rect = pygame.rect.Rect(1, 1, n, n) - self.body_rect = pygame.rect.Rect( - 0, self.MINIMUM_HEIGHT + 1, - self.w - 1, self.h - self.MINIMUM_HEIGHT - 2) + def resurface(self, surface): + Viewer.resurface(self, surface) + n = self.MINIMUM_HEIGHT - 2 + self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n) + self.grow_rect = pygame.rect.Rect(1, 1, n, n) + self.body_rect = pygame.rect.Rect( + 0, self.MINIMUM_HEIGHT + 1, + self.w - 1, self.h - self.MINIMUM_HEIGHT - 2) - def draw(self): - '''Draw the viewer onto its surface.''' - Viewer.draw(self) - if not self.resizing: - self.draw_menu() - self.draw_body() + def draw(self): + '''Draw the viewer onto its surface.''' + Viewer.draw(self) + if not self.resizing: + self.draw_menu() + self.draw_body() - def draw_menu(self): - # menu buttons - pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1) - pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1) + def draw_menu(self): + # menu buttons + pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1) + pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1) - def draw_body(self): - self.surface.fill(self.bg, self.body_rect) + def draw_body(self): + self.surface.fill(self.bg, self.body_rect) - def mouse_down(self, display, x, y, button): - Viewer.mouse_down(self, display, x, y, button) - if y <= self.MINIMUM_HEIGHT: - self.menu_click(display, x, y, button) - else: - bx, by = self.body_rect.topleft - self.body_click(display, x - bx, y - by, button) + def mouse_down(self, display, x, y, button): + Viewer.mouse_down(self, display, x, y, button) + if y <= self.MINIMUM_HEIGHT: + self.menu_click(display, x, y, button) + else: + bx, by = self.body_rect.topleft + self.body_click(display, x - bx, y - by, button) - def body_click(self, display, x, y, button): - if button == 1: - self.draw_an_a(x, y) + def body_click(self, display, x, y, button): + if button == 1: + self.draw_an_a(x, y) - def menu_click(self, display, x, y, button): - if button == 1: - self.resizing = 1 - elif button == 3: - if self.close_rect.collidepoint(x, y): - display.close_viewer(self) - return True - elif self.grow_rect.collidepoint(x, y): - display.grow_viewer(self) - return True + def menu_click(self, display, x, y, button): + if button == 1: + self.resizing = 1 + elif button == 3: + if self.close_rect.collidepoint(x, y): + display.close_viewer(self) + return True + elif self.grow_rect.collidepoint(x, y): + display.grow_viewer(self) + return True - def mouse_up(self, display, x, y, button): + def mouse_up(self, display, x, y, button): - if button == 1 and self.resizing: - if self.resizing == 2: - self.resizing = 0 - self.draw() - display.done_resizing() - self.resizing = 0 - return True + if button == 1 and self.resizing: + if self.resizing == 2: + self.resizing = 0 + self.draw() + display.done_resizing() + self.resizing = 0 + return True - def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2): - if self.resizing and button0: - self.resizing = 2 - display.change_viewer(self, rel_y, relative=True) - return True - else: - self.resizing = 0 - #self.draw_an_a(x, y) + def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2): + if self.resizing and button0: + self.resizing = 2 + display.change_viewer(self, rel_y, relative=True) + return True + else: + self.resizing = 0 + #self.draw_an_a(x, y) - def key_up(self, display, key, mod): - if Viewer.key_up(self, display, key, mod): - return True + def key_up(self, display, key, mod): + if Viewer.key_up(self, display, key, mod): + return True - def draw_an_a(self, x, y): - # Draw a crude letter A. - lw, lh = 10, 14 - try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh)) - except ValueError: return - draw_a(surface, blend=1) + def draw_an_a(self, x, y): + # Draw a crude letter A. + lw, lh = 10, 14 + try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh)) + except ValueError: return + draw_a(surface, blend=1) class SomeViewer(MenuViewer): - def __init__(self, surface): - MenuViewer.__init__(self, surface) + def __init__(self, surface): + MenuViewer.__init__(self, surface) - def resurface(self, surface): - MenuViewer.resurface(self, surface) + def resurface(self, surface): + MenuViewer.resurface(self, surface) - def draw_menu(self): - MenuViewer.draw_menu(self) + def draw_menu(self): + MenuViewer.draw_menu(self) - def draw_body(self): - pass + def draw_body(self): + pass - def body_click(self, display, x, y, button): - pass + def body_click(self, display, x, y, button): + pass - def menu_click(self, display, x, y, button): - if MenuViewer.menu_click(self, display, x, y, button): - return True + def menu_click(self, display, x, y, button): + if MenuViewer.menu_click(self, display, x, y, button): + return True - def mouse_up(self, display, x, y, button): - if MenuViewer.mouse_up(self, display, x, y, button): - return True + def mouse_up(self, display, x, y, button): + if MenuViewer.mouse_up(self, display, x, y, button): + return True - def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2): - if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y, - button0, button1, button2): - return True + def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2): + if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y, + button0, button1, button2): + return True - def key_down(self, display, uch, key, mod): - try: - print(chr(key), end=' ') - except ValueError: - pass + def key_down(self, display, uch, key, mod): + try: + print(chr(key), end=' ') + except ValueError: + pass # Note that Oberon book says that if you split at the exact top of a viewer @@ -243,7 +243,7 @@ class SomeViewer(MenuViewer): def draw_a(surface, color=FOREGROUND, blend=False): - w, h = surface.get_width() - 2, surface.get_height() - 2 - pygame.draw.aalines(surface, color, False, ( - (1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2)) - ), blend) + w, h = surface.get_width() - 2, surface.get_height() - 2 + pygame.draw.aalines(surface, color, False, ( + (1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2)) + ), blend) diff --git a/setup.py b/setup.py index 9bb5939..e03a8bf 100755 --- a/setup.py +++ b/setup.py @@ -23,25 +23,25 @@ from textwrap import dedent setup( - name='Thun', - version='0.2.0', - description='Python Implementation of Joy', - long_description=dedent('''\ - Joy is a programming language created by Manfred von Thun that is easy to - use and understand and has many other nice properties. This Python - package implements an interpreter for a dialect of Joy that attempts to - stay very close to the spirit of Joy but does not precisely match the - behaviour of the original version written in C.'''), - author='Simon Forman', - author_email='forman.simon@gmail.com', - url='https://joypy.osdn.io', - license='GPLv3+', - packages=['joy', 'joy.utils', 'joy.gui', 'joy.vui'], - classifiers=[ - 'Development Status :: 4 - Beta', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Other', - 'Topic :: Software Development :: Interpreters', - ], - ) + name='Thun', + version='0.2.0', + description='Python Implementation of Joy', + long_description=dedent('''\ + Joy is a programming language created by Manfred von Thun that is easy to + use and understand and has many other nice properties. This Python + package implements an interpreter for a dialect of Joy that attempts to + stay very close to the spirit of Joy but does not precisely match the + behaviour of the original version written in C.'''), + author='Simon Forman', + author_email='forman.simon@gmail.com', + url='https://joypy.osdn.io', + license='GPLv3+', + packages=['joy', 'joy.utils', 'joy.gui', 'joy.vui'], + classifiers=[ + 'Development Status :: 4 - Beta', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Other', + 'Topic :: Software Development :: Interpreters', + ], + )