-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Labels and Macros #65
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import sys | ||
import types | ||
from toolz import mapcat | ||
|
||
from . import instructions as instrs | ||
from .code import Code | ||
|
||
|
||
Label = instrs.Label | ||
|
||
|
||
def assemble_function(signature, objs, code_kwargs=None, function_kwargs=None): | ||
"""TODO | ||
""" | ||
if code_kwargs is None: | ||
code_kwargs = {} | ||
if function_kwargs is None: | ||
function_kwargs = {} | ||
|
||
code_kwargs.setdefault('argnames', list(gen_argnames_for_code(signature))) | ||
|
||
# Default to using the globals of the calling stack frame. | ||
function_kwargs.setdefault('globals', sys._getframe(1).f_globals) | ||
|
||
function_kwargs.setdefault('argdefs', tuple(extract_defaults(signature))) | ||
|
||
code = assemble_code(objs, **code_kwargs).to_pycode() | ||
|
||
return types.FunctionType(code, **function_kwargs) | ||
|
||
|
||
def assemble_code(objs, **code_kwargs): | ||
"""TODO | ||
""" | ||
instrs = resolve_labels(assemble_instructions(objs)) | ||
return Code(instrs, **code_kwargs) | ||
|
||
|
||
def assemble_instructions(objs): | ||
"""Assemble a sequence of Instructions or iterables of instructions. | ||
""" | ||
return list(mapcat(_validate_instructions, objs)) | ||
|
||
|
||
def resolve_labels(objs): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an earlier draft of this I was allowing people to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that adding the |
||
"""TODO | ||
""" | ||
out = [] | ||
last_instr = None | ||
for i in reversed(objs): | ||
if isinstance(i, Label): | ||
if last_instr is None: | ||
# TODO: Better error here. | ||
raise ValueError("Can't end with a Label!") | ||
# Make any jumps to `i` resolve to `last_instr`. | ||
last_instr.steal(i) | ||
elif isinstance(i, instrs.Instruction): | ||
last_instr = i | ||
out.append(i) | ||
else: | ||
raise TypeError("Unknown type: {}", i) | ||
|
||
for i in out: | ||
if isinstance(i.arg, Label): | ||
raise ValueError("Unresolved label for {}".format(i)) | ||
|
||
return reversed(out) | ||
|
||
|
||
def _validate_instructions(obj): | ||
"""TODO | ||
""" | ||
Instruction = instrs.Instruction | ||
if isinstance(obj, (Label, Instruction)): | ||
yield obj | ||
else: | ||
for instr in obj: | ||
if not isinstance(instr, (Instruction, Label)): | ||
raise TypeError( | ||
"Expected an Instruction or Label. Got %s" % obj, | ||
) | ||
yield instr | ||
|
||
|
||
def gen_argnames_for_code(sig): | ||
"""Get argnames from an inspect.signature to pass to a Code object. """ | ||
for name, param in sig.parameters.items(): | ||
if param.kind == param.VAR_POSITIONAL: | ||
yield '*' + name | ||
elif param.kind == param.VAR_KEYWORD: | ||
yield '**' + name | ||
else: | ||
yield name | ||
|
||
|
||
def extract_defaults(sig): | ||
"""Get default parameters from an inspect.signature. | ||
""" | ||
return (p.default for p in sig.parameters.values() if p.default != p.empty) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -344,21 +344,18 @@ def __init__(self, | |
if kwarg is not None: | ||
raise ValueError('cannot specify **kwargs more than once') | ||
kwarg = argname[2:] | ||
append_argname(argname) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs a test |
||
continue | ||
elif argname.startswith('*'): | ||
if varg is not None: | ||
raise ValueError('cannot specify *args more than once') | ||
varg = argname[1:] | ||
argcounter = kwonlyargcount # all following args are kwonly. | ||
append_argname(argname) | ||
continue | ||
argcounter[0] += 1 | ||
append_argname(argname) | ||
|
||
if varg is not None: | ||
append_argname(varg) | ||
if kwarg is not None: | ||
append_argname(kwarg) | ||
|
||
cellvar_names = set(cellvars) | ||
freevar_names = set(freevars) | ||
for instr in filter(op.attrgetter('uses_free'), instrs): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,30 +64,29 @@ def _vartype(self): | |
|
||
|
||
class InstructionMeta(ABCMeta, matchable): | ||
_marker = object() # sentinel | ||
_type_cache = {} | ||
|
||
def __init__(self, *args, opcode=None): | ||
def __init__(self, *args, opcode=None, synthetic=False): | ||
return super().__init__(*args) | ||
|
||
def __new__(mcls, name, bases, dict_, *, opcode=None): | ||
def __new__(mcls, name, bases, dict_, *, opcode=None, synthetic=False): | ||
try: | ||
return mcls._type_cache[opcode] | ||
except KeyError: | ||
pass | ||
|
||
if len(bases) != 1: | ||
if len(bases) > 1: | ||
raise TypeError( | ||
'{} does not support multiple inheritance'.format( | ||
mcls.__name__, | ||
), | ||
) | ||
|
||
if bases[0] is mcls._marker: | ||
if synthetic: | ||
dict_['_reprname'] = immutableattr(name) | ||
for attr in ('absjmp', 'have_arg', 'opcode', 'opname', 'reljmp'): | ||
dict_[attr] = _notimplemented(attr) | ||
return super().__new__(mcls, name, (object,), dict_) | ||
return super().__new__(mcls, name, bases, dict_) | ||
|
||
if opcode not in opmap.values(): | ||
raise TypeError('Invalid opcode: {}'.format(opcode)) | ||
|
@@ -123,7 +122,45 @@ def __repr__(self): | |
__str__ = __repr__ | ||
|
||
|
||
class Instruction(InstructionMeta._marker, metaclass=InstructionMeta): | ||
class JumpTarget: | ||
"""Base class for objects that can be targets of jump instructions. | ||
|
||
This is the base for both Instruction and Label. | ||
""" | ||
|
||
def __init__(self): | ||
self._target_of = set() | ||
self._stolen_by = None # used for lnotab recalculation | ||
|
||
def steal(self, instr): | ||
"""Steal the jump index off of `instr`. | ||
|
||
This makes anything that would have jumped to `instr` jump to | ||
this Instruction instead. | ||
|
||
Parameters | ||
---------- | ||
instr : JumpTarget | ||
The target to steal the jump sources from. | ||
|
||
Returns | ||
------- | ||
self : JumpTarget | ||
The object that owns this method. | ||
|
||
Notes | ||
----- | ||
This mutates self and ``instr`` inplace. | ||
""" | ||
instr._stolen_by = self | ||
for jmp in instr._target_of: | ||
jmp.arg = self | ||
self._target_of = instr._target_of | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's remember to do this |
||
instr._target_of = set() | ||
return self | ||
|
||
|
||
class Instruction(JumpTarget, metaclass=InstructionMeta, synthetic=True): | ||
""" | ||
Base class for all instruction types. | ||
|
||
|
@@ -139,13 +176,12 @@ class Instruction(InstructionMeta._marker, metaclass=InstructionMeta): | |
_no_arg = no_default | ||
|
||
def __init__(self, arg=_no_arg): | ||
super().__init__() | ||
if self.have_arg and arg is self._no_arg: | ||
raise TypeError( | ||
"{} missing 1 required argument: 'arg'".format(self.opname), | ||
) | ||
self.arg = self._normalize_arg(arg) | ||
self._target_of = set() | ||
self._stolen_by = None # used for lnotab recalculation | ||
|
||
def __repr__(self): | ||
arg = self.arg | ||
|
@@ -158,33 +194,6 @@ def __repr__(self): | |
def _normalize_arg(arg): | ||
return arg | ||
|
||
def steal(self, instr): | ||
"""Steal the jump index off of `instr`. | ||
|
||
This makes anything that would have jumped to `instr` jump to | ||
this Instruction instead. | ||
|
||
Parameters | ||
---------- | ||
instr : Instruction | ||
The instruction to steal the jump sources from. | ||
|
||
Returns | ||
------- | ||
self : Instruction | ||
The instruction that owns this method. | ||
|
||
Notes | ||
----- | ||
This mutates self and ``instr`` inplace. | ||
""" | ||
instr._stolen_by = self | ||
for jmp in instr._target_of: | ||
jmp.arg = self | ||
self._target_of = instr._target_of | ||
instr._target_of = set() | ||
return self | ||
|
||
@classmethod | ||
def from_opcode(cls, opcode, arg=_no_arg): | ||
""" | ||
|
@@ -302,13 +311,13 @@ def _call_repr(self): | |
|
||
|
||
def _check_jmp_arg(self, arg): | ||
if not isinstance(arg, (Instruction, _RawArg)): | ||
if not isinstance(arg, (JumpTarget, _RawArg)): | ||
raise TypeError( | ||
'argument to %s must be an instruction, got: %r' % ( | ||
'argument to %s must be a valid jump target, got: %r' % ( | ||
type(self).__name__, arg, | ||
), | ||
) | ||
if isinstance(arg, Instruction): | ||
if isinstance(arg, JumpTarget): | ||
arg._target_of.add(self) | ||
return arg | ||
|
||
|
@@ -424,6 +433,18 @@ def __get__(self, instance, owner): | |
del class_ | ||
|
||
|
||
class Label(JumpTarget): | ||
"""A "pseudo-instruction" that can be the target of a jump instruction. | ||
""" | ||
|
||
def __init__(self, debug_name='anonymous'): | ||
super().__init__() | ||
self.debug_name = debug_name | ||
|
||
def __repr__(self): | ||
return "Label({!r})".format(self.debug_name) | ||
|
||
|
||
# Clean up the namespace | ||
del name | ||
del globals_ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to decide how we want to manage signatures here.
Ideally it should be easy to either type this by hand or use the signature of an existing function. For the tests, I've been using
inspect.signature(lambda <sig>: None)
as an easy way to get the signature I want.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one idea is to allow a function which would likely look like:
lambda a, b, c: ...