import functools
import re
from collections import namedtuple
from typing import Tuple
from pymavryk.michelson.tags import prim_tags
COMPARE = {'prim': 'COMPARE'}
UNIT = {'prim': 'UNIT'}
FAILWITH = {'prim': 'FAILWITH'}
DUP = {'prim': 'DUP'}
SWAP = {'prim': 'SWAP'}
CAR = {'prim': 'CAR'}
CDR = {'prim': 'CDR'}
CAR__ = {'prim': 'CAR', 'annots': ['@%%']}
CDR__ = {'prim': 'CDR', 'annots': ['@%%']}
DROP = {'prim': 'DROP'}
FAIL = [[UNIT, FAILWITH]]
macros = []
PxrNode = namedtuple('PxrNode', ['depth', 'annots', 'args', 'is_root'])
[docs]def macro(regexp):
def register_macro(func):
macros.append((re.compile(regexp), func))
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return register_macro
[docs]def seq(instr=None) -> list:
if instr is None:
return []
elif isinstance(instr, list):
return instr
else:
return [instr]
[docs]def expand_macro(prim, annots, args, internal=False):
"""Expands Michelson macro.
:param prim: macro name
:param annots: annotations (optional)
:param args: arguments (optional)
:param internal: this function is called during another macro expansion
:returns: Code sequence (Micheline expression)
"""
assert isinstance(annots, list)
assert isinstance(args, list)
if prim in prim_tags:
return expr(prim=prim, annots=annots, args=args)
for regexp, handler in macros:
groups = regexp.findall(prim)
if groups:
assert len(groups) == 1
res = handler(groups[0], annots, args)
return res if internal else seq(res)
raise AssertionError(f'unknown primitive `{prim}`')
[docs]def get_field_annots(annots):
return list(filter(lambda x: isinstance(x, str) and x[0] == '%', annots))
[docs]def get_var_annots(annots):
return list(filter(lambda x: isinstance(x, str) and x[0] == '@', annots))
[docs]def skip_nones(array):
return list(filter(lambda x: x is not None, array))
[docs]def expr(**kwargs) -> dict:
return {k: v for k, v in kwargs.items() if v}
[docs]def dip_n(instr, depth=1):
if depth <= 0:
return instr
elif depth == 1:
return expr(
prim='DIP',
args=[seq(instr)],
)
else:
return expr(
prim='DIP',
args=[{'int': str(depth)}, seq(instr)],
)
[docs]@macro(r'^CMP(EQ|NEQ|LT|GT|LE|GE)$')
def expand_cmpx(prim, annots, args) -> list:
assert not args
return [
COMPARE,
expr(prim=prim, annots=annots),
]
[docs]@macro(r'^IF(EQ|NEQ|LT|GT|LE|GE)$')
def expand_ifx(prim, annots, args) -> list:
assert len(args) == 2
return [
expr(prim=prim, annots=annots),
expr(prim='IF', args=args),
]
[docs]@macro(r'^IFCMP(EQ|NEQ|LT|GT|LE|GE)$')
def expand_ifcmpx(prim, annots, args) -> list:
assert len(args) == 2
return [
[COMPARE, expr(prim=prim, annots=annots)],
expr(prim='IF', args=args),
]
[docs]@macro(r'^FAIL$')
def expand_fail(prim, annots, args) -> list:
assert not annots
assert not args
return [
UNIT,
FAILWITH,
]
[docs]@macro(r'^ASSERT$')
def expand_assert(prim, annots, args) -> dict:
assert not annots
assert not args
return expr(
prim='IF',
args=[[], FAIL],
)
[docs]@macro(r'^ASSERT_(EQ|NEQ|LT|LE|GT|GE)$')
def expand_assert_x(prim, annots, args) -> list:
assert not args
assert not annots # TODO: ask why
return expand_ifx(
prim,
annots=[],
args=[[], FAIL],
)
[docs]@macro(r'^ASSERT_CMP(EQ|NEQ|LT|LE|GT|GE)$')
def expand_assert_cmpx(prim, annots, args) -> list:
assert not args
assert not annots # TODO: ask why
return expand_ifcmpx(
prim,
annots=[],
args=[[], FAIL],
)
[docs]@macro(r'^ASSERT_NONE$')
def expand_assert_none(prim, annots, args) -> dict:
assert not annots
assert not args
return expr(
prim='IF_NONE',
args=[[], FAIL],
)
[docs]@macro('^ASSERT_SOME$')
def expand_assert_some(prim, annots, args) -> dict:
assert not args
return expr(
prim='IF_NONE',
args=[FAIL, [expr(prim='RENAME', annots=annots)]],
)
[docs]@macro('^ASSERT_LEFT$')
def expand_assert_left(prim, annots, args) -> dict:
assert not args
return expr(
prim='IF_LEFT',
args=[[expr(prim='RENAME', annots=annots)], FAIL],
)
[docs]@macro('^ASSERT_RIGHT$')
def expand_assert_right(prim, annots, args) -> dict:
assert not args
return expr(
prim='IF_LEFT',
args=[FAIL, [expr(prim='RENAME', annots=annots)]],
)
[docs]@macro(r'^D(II+)P$')
def expand_dixp(prim, annots, args) -> dict:
assert not annots
assert len(args) == 1
return dip_n(args, depth=len(prim))
[docs]@macro(r'^D(UU+)P$')
def expand_duxp(prim, annots, args) -> dict:
assert not args
depth = len(prim)
return expr(
prim='DUP',
annots=annots,
args=[{'int': str(depth)}],
)
[docs]def build_pxr_tree(pxr_macro, pxr_annots) -> PxrNode:
def parse(prim, annots, depth=0, is_root=False):
letter, prim = prim[0], prim[1:]
if letter == 'P':
dip_depth = depth
left, l_annot, prim, annots, depth = parse(prim, annots, depth)
right, r_annot, prim, annots, depth = parse(prim, annots, depth)
return (
PxrNode(
dip_depth,
[l_annot, r_annot],
[left, right],
is_root,
),
None,
prim,
annots,
depth,
)
else:
annot, annots = (annots[0], annots[1:]) if annots else (None, [])
return letter, annot, prim, annots, depth + 1
root, _, _, _, _ = parse(pxr_macro, pxr_annots, is_root=True)
return root
[docs]def traverse_pxr_tree(prim, annots, produce):
res = []
def walk(node):
if isinstance(node, PxrNode):
res.insert(0, dip_n(produce(node), depth=node.depth))
_ = list(map(walk, node.args))
walk(build_pxr_tree(prim, annots))
return res
[docs]@macro(r'^P[PAI]{3,}R$')
def expand_pxr(prim, annots, args) -> list:
def produce(node: PxrNode):
pair_annots = [node.annots[0] or '%', node.annots[1]] if any(node.annots) else []
if node.is_root:
pair_annots.extend(get_var_annots(annots))
return expr(prim='PAIR', annots=skip_nones(pair_annots))
assert not args
return traverse_pxr_tree(prim, get_field_annots(annots), produce)
[docs]@macro(r'^UN(P[PAI]{3,}R)$')
def expand_unpxr(prim, annots, args) -> list:
def produce(node: PxrNode):
return [expr(prim='UNPAIR', annots=skip_nones(node.annots))]
assert not args
return list(reversed(traverse_pxr_tree(prim, annots, produce)))
[docs]def expand_cxr(prim, annots) -> list:
return seq(expand_macro(prim=f'C{prim}R', annots=annots, args=[], internal=True))
[docs]@macro(r'^CA([AD]+)R$')
def expand_caxr(prim, annots, args) -> list:
assert not args
return [
CAR,
*expand_cxr(prim, annots),
]
[docs]@macro(r'^CD([AD]+)R$')
def expand_cdxr(prim, annots, args) -> list:
assert not args
return [
CDR,
*expand_cxr(prim, annots),
]
[docs]@macro(r'^IF_SOME$')
def expand_if_some(prim, annots, args) -> dict:
assert not annots
assert len(args) == 2
return expr(
prim='IF_NONE',
args=list(reversed(args)),
)
[docs]@macro(r'^IF_RIGHT$')
def expand_if_right(prim, annots, args) -> dict:
assert not annots
assert len(args) == 2
return expr(
prim='IF_LEFT',
args=list(reversed(args)),
)
[docs]@macro(r'^SET_CAR$')
def expand_set_car(prim, annots, args) -> list:
assert not args
return [
SWAP,
expr(
prim='UPDATE',
args=[{'int': '1'}],
annots=annots,
),
]
[docs]@macro(r'^SET_CDR$')
def expand_set_cdr(prim, annots, args) -> list:
assert not args
return [
SWAP,
expr(
prim='UPDATE',
args=[{'int': '2'}],
annots=annots,
),
]
[docs]def expand_set_cxr(prim, annots):
set_cxr = expand_macro(
prim=f'SET_C{prim}R',
annots=get_field_annots(annots),
args=[],
internal=True,
)
pair = expr(
prim='PAIR',
annots=['%@', '%@'] + get_var_annots(annots),
)
return set_cxr, pair
[docs]@macro(r'^SET_CA([AD]+)R$')
def expand_set_caxr(prim, annots, args) -> list:
assert not args
set_cxr, pair = expand_set_cxr(prim, annots)
return [
DUP,
dip_n([CAR__, set_cxr]),
CDR__,
SWAP,
pair,
]
[docs]@macro(r'^SET_CD([AD]+)R$')
def expand_set_cdxr(prim, annots, args) -> list:
assert not args
set_cxr, pair = expand_set_cxr(prim, annots)
return [
DUP,
dip_n([CDR__, set_cxr]),
CAR__,
pair,
]
[docs]def get_map_cxr_annots(annots) -> Tuple[str, list]:
field_annots = get_field_annots(annots)
if field_annots:
assert len(field_annots) == 1
return field_annots[0], [f'@{field_annots[0][1:]}']
else:
return '%', []
[docs]@macro(r'^MAP_CAR$')
def expand_map_car(prim, annots, args) -> list:
car_annot, var_annots = get_map_cxr_annots(annots)
return [
DUP,
CDR__,
dip_n(
[
expr(
prim='CAR',
annots=var_annots,
),
*args,
]
),
SWAP,
expr(
prim='PAIR',
annots=[car_annot, '%@'],
),
]
[docs]@macro(r'^MAP_CDR$')
def expand_map_cdr(prim, annots, args) -> list:
cdr_annot, var_annots = get_map_cxr_annots(annots)
return [
DUP,
expr(prim='CDR', annots=var_annots),
*args,
SWAP,
CAR__,
expr(
prim='PAIR',
annots=['%@', cdr_annot],
),
]
[docs]def expand_map_cxr(prim, annots, args):
set_cxr = expand_macro(
prim=f'MAP_C{prim}R',
annots=get_field_annots(annots),
args=args,
internal=True,
)
pair = expr(
prim='PAIR',
annots=['%@', '%@'] + get_var_annots(annots),
)
return set_cxr, pair
[docs]@macro(r'^MAP_CA([AD]+)R$')
def expand_map_caxr(prim, annots, args) -> list:
map_cxr, pair = expand_map_cxr(prim, annots, args)
return [
DUP,
dip_n([CAR__, map_cxr]),
CDR__,
SWAP,
pair,
]
[docs]@macro(r'^MAP_CD([AD]+)R$')
def expand_map_cdxr(prim, annots, args) -> list:
map_cxr, pair = expand_map_cxr(prim, annots, args)
return [
DUP,
dip_n([CDR__, map_cxr]),
CAR__,
pair,
]