Merge a branch.

I sure hope I'm doing this right.
This commit is contained in:
Simon Forman 2018-04-21 18:36:31 -07:00
commit b2a4c0e1f8
2 changed files with 130 additions and 36 deletions

View File

@ -24,10 +24,12 @@ returns a dictionary of Joy functions suitable for use with the joy()
function.
'''
from inspect import getdoc
from functools import wraps
import operator, math
from .parser import text_to_expression, Symbol
from .utils.stack import list_to_stack, iter_stack, pick, pushback
from .utils.brutal_hackery import rename_code_object
_dictionary = {}
@ -152,61 +154,55 @@ step_zero == 0 roll> step
)
class FunctionWrapper(object):
'''
Allow functions to have a nice repr().
At some point it's likely this class and its subclasses would gain
machinery to support type checking and inference.
'''
def __init__(self, f):
self.f = f
self.name = f.__name__.rstrip('_') # Don't shadow builtins.
self.__doc__ = f.__doc__ or str(f)
def __call__(self, stack, expression, dictionary):
'''
Functions in general receive and return all three.
'''
return self.f(stack, expression, dictionary)
def __repr__(self):
return self.name
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
class SimpleFunctionWrapper(FunctionWrapper):
def SimpleFunctionWrapper(f):
'''
Wrap functions that take and return just a stack.
'''
def __call__(self, stack, expression, dictionary):
return self.f(stack), expression, dictionary
@FunctionWrapper
@wraps(f)
@rename_code_object(f.__name__)
def inner(stack, expression, dictionary):
return f(stack), expression, dictionary
return inner
class BinaryBuiltinWrapper(FunctionWrapper):
def BinaryBuiltinWrapper(f):
'''
Wrap functions that take two arguments and return a single result.
'''
def __call__(self, stack, expression, dictionary):
@FunctionWrapper
@wraps(f)
@rename_code_object(f.__name__)
def inner(stack, expression, dictionary):
(a, (b, stack)) = stack
result = self.f(b, a)
result = f(b, a)
return (result, stack), expression, dictionary
return inner
class UnaryBuiltinWrapper(FunctionWrapper):
def UnaryBuiltinWrapper(f):
'''
Wrap functions that take one argument and return a single result.
'''
def __call__(self, stack, expression, dictionary):
@FunctionWrapper
@wraps(f)
@rename_code_object(f.__name__)
def inner(stack, expression, dictionary):
(a, stack) = stack
result = self.f(a)
result = f(a)
return (result, stack), expression, dictionary
return inner
class DefinitionWrapper(FunctionWrapper):
class DefinitionWrapper(object):
'''
Provide implementation of defined functions, and some helper methods.
'''
@ -692,12 +688,15 @@ floor.__doc__ = math.floor.__doc__
@inscribe
@SimpleFunctionWrapper
def divmod_(S):
'''
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)
divmod_.__doc__ = divmod.__doc__
def sqrt(a):
'''
@ -738,12 +737,14 @@ def rolldown(S):
@inscribe
@SimpleFunctionWrapper
def id_(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

View File

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2018 Simon Forman
#
# This file is part of Joypy
#
# Joypy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Joypy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Joypy. If not see <http://www.gnu.org/licenses/>.
#
'''
I really want tracebacks to show which function was being executed when
an error in the wrapper function happens. In order to do that, you have
to do this (the function in this module.)
Here's what it looks like when you pass too few arguments to e.g. "mul".
>>> from joy.library import _dictionary
>>> m = _dictionary['*']
>>> m((), (), {})
Traceback (most recent call last):
File "<pyshell#49>", line 1, in <module>
m((), (), {})
File "joy/library.py", line 185, in mul:inner
(a, (b, stack)) = stack
ValueError: need more than 0 values to unpack
>>>
Notice that line 185 in the library.py file is (as of this writing) in
the BinaryBuiltinWrapper's inner() function, but this hacky code has
managed to insert the name of the wrapped function ("mul") along with a
colon into the wrapper function's reported name.
Normally I would frown on this sort of mad hackery, but... this is in
the service of ease-of-debugging! Very valuable. And note that all the
hideous patching is finished in the module-load-stage, it shouldn't cause
issues of its own at runtime.
The main problem I see with this is that people coming to this code later
might be mystified if they just see a traceback with a ':' in the
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, you must do this brutal
hackery to change the func.__code__.co_name attribute. See:
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
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