Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Adding the this expression (backported from construct3). The this
expression builds an evaluatable (callable) object directly from python
expressions, so it's less verbose and much easier to read and write.
Also, it's compatible with the existing lambda functions. Instead of
doing `lambda ctx: ctx["length"] * 2`, simply use `this.length * 2`
  • Loading branch information
Tomer Filiba authored and tomerfiliba committed May 15, 2012
1 parent 1478482 commit 969e568
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 7 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -1 +1,3 @@
.project
.pydevproject
*.pyc
13 changes: 7 additions & 6 deletions construct/__init__.py
Expand Up @@ -30,16 +30,17 @@
"\\x01\\x02\\x03"
"""

from .core import *
from .adapters import *
from .macros import *
from .debug import Probe, Debugger
from construct.core import *
from construct.adapters import *
from construct.macros import *
from construct.lib.expr import this
from construct.debug import Probe, Debugger


#===============================================================================
# Metadata
#===============================================================================
__author__ = "tomer filiba (tomerfiliba [at] gmail.com)"
__author__ = "tomer filiba <tomerfiliba@gmail.com>"
__maintainer__ = "Corbin Simpson <MostAwesomeDude@gmail.com>"
__version__ = "2.06"

Expand Down Expand Up @@ -106,5 +107,5 @@ def wrapper(*args, **kwargs):
'SymmetricMapping', 'Terminator', 'TerminatorError', 'Tunnel',
'TunnelAdapter', 'UBInt16', 'UBInt32', 'UBInt64', 'UBInt8', 'ULInt16',
'ULInt32', 'ULInt64', 'ULInt8', 'UNInt16', 'UNInt32', 'UNInt64', 'UNInt8',
'Union', 'ValidationError', 'Validator', 'Value', "Magic",
'Union', 'ValidationError', 'Validator', 'Value', "Magic", "this",
]
142 changes: 142 additions & 0 deletions construct/lib/expr.py
@@ -0,0 +1,142 @@
#
# The 'this' expression (backported from construct3).
#
import operator


class ExprMixin(object):
__slots__ = ()
def __add__(self, other):
return BinExpr(operator.add, self, other, "+")
def __sub__(self, other):
return BinExpr(operator.sub, self, other, "-")
def __mul__(self, other):
return BinExpr(operator.mul, self, other, "*")
def __floordiv__(self, other):
return BinExpr(operator.div, self, other, "//")
def __truediv__(self, other):
return BinExpr(operator.div, self, other, "/")
__div__ = __floordiv__
def __mod__(self, other):
return BinExpr(operator.mod, self, other, "%")
def __pow__(self, other):
return BinExpr(operator.pow, self, other, "**")
def __xor__(self, other):
return BinExpr(operator.xor, self, other, "^")
def __rshift__(self, other):
return BinExpr(operator.rshift, self, other, ">>")
def __lshift__(self, other):
return BinExpr(operator.rshift, self, other, "<<")
def __and__(self, other):
return BinExpr(operator.and_, self, other, "and")
def __or__(self, other):
return BinExpr(operator.or_, self, other, "or")

def __radd__(self, other):
return BinExpr(operator.add, other, self, "+")
def __rsub__(self, other):
return BinExpr(operator.sub, other, self, "-")
def __rmul__(self, other):
return BinExpr(operator.mul, other, self, "*")
def __rfloordiv__(self, other):
return BinExpr(operator.div, other, self, "//")
def __rtruediv__(self, other):
return BinExpr(operator.div, other, self, "/")
__rdiv__ = __rfloordiv__
def __rmod__(self, other):
return BinExpr(operator.mod, other, self, "%")
def __rpow__(self, other):
return BinExpr(operator.pow, other, self, "**")
def __rxor__(self, other):
return BinExpr(operator.xor, other, self, "^")
def __rrshift__(self, other):
return BinExpr(operator.rshift, other, self, ">>")
def __rlshift__(self, other):
return BinExpr(operator.rshift, other, self, "<<")
def __rand__(self, other):
return BinExpr(operator.and_, other, self, "and")
def __ror__(self, other):
return BinExpr(operator.or_, other, self, "or")

def __neg__(self):
return UniExpr(operator.neg, self, "-")
def __pos__(self):
return UniExpr(operator.pos, self, "+")
def __invert__(self):
return UniExpr(operator.not_, self, "not")
__inv__ = __invert__

def __contains__(self, other):
return BinExpr(operator.contains, self, other, "in")
def __gt__(self, other):
return BinExpr(operator.gt, self, other, ">")
def __ge__(self, other):
return BinExpr(operator.ge, self, other, ">=")
def __lt__(self, other):
return BinExpr(operator.lt, self, other, "<")
def __le__(self, other):
return BinExpr(operator.le, self, other, "<=")
def __eq__(self, other):
return BinExpr(operator.eq, self, other, "==")
def __ne__(self, other):
return BinExpr(operator.ne, self, other, "!=")


class UniExpr(ExprMixin):
__slots__ = ["op", "operand", "opname"]
def __init__(self, op, operand, opname):
self.op = op
self.opname = opname
self.operand = operand
def __repr__(self):
return "%s %r" % (self.lhs, self.opname, self.rhs)
def __call__(self, context):
operand = self.operand(context) if callable(self.operand) else self.operand
return self.op(operand)


class BinExpr(ExprMixin):
__slots__ = ["op", "lhs", "rhs", "opname"]
def __init__(self, op, lhs, rhs, opname):
self.op = op
self.opname = opname
self.lhs = lhs
self.rhs = rhs
def __repr__(self):
return "(%r %s %r)" % (self.lhs, self.opname, self.rhs)
def __call__(self, context):
lhs = self.lhs(context) if callable(self.lhs) else self.lhs
rhs = self.rhs(context) if callable(self.rhs) else self.rhs
return self.op(lhs, rhs)


class Path(ExprMixin):
__slots__ = ["__name", "__parent"]
def __init__(self, name, parent = None):
self.__name = name
self.__parent = parent
def __repr__(self):
if self.__parent is None:
return self.__name
return "%r.%s" % (self.__parent, self.__name)
def __call__(self, context):
if self.__parent is None:
return context
context2 = self.__parent(context)
return context2[self.__name]
def __getattr__(self, name):
return Path(name, self)


# let the magic begin!
this = Path("this")


if __name__ == "__main__":
x = (this.foo * 2 + 3 << 2) % 11
print x
print x({"foo" : 7})




43 changes: 43 additions & 0 deletions construct/tests/test_this.py
@@ -0,0 +1,43 @@
import unittest
from construct import Struct, UBInt8, UBInt32, IfThenElse, Value, Field, Container
from construct import this


class TestThisExpressions(unittest.TestCase):

this_example = Struct("this_example",
# straight-forward usage: instead of passing (lambda ctx: ctx["length"]) use this.length
UBInt8("length"),
Field("value", this.length),

# an example of nesting: '_' refers to the parent's scope
Struct("nested",
UBInt8("b1"),
UBInt8("b2"),
Value("b3", this.b1 * this.b2 + this._.length)
),

# and conditions work as expected
IfThenElse("condition", this.nested.b1 > 50,
UBInt32("foo"),
UBInt8("bar"),
)
)

def test_parse(self):
res = self.this_example.parse("\x05helloABXXXX")
expected = Container(length = 5, value = 'hello',
nested = Container(b1 = 65, b2 = 66, b3 = 4295),
condition = 1482184792)
self.assertEquals(res, expected)

def test_build(self):
obj = Container(length = 5, value = 'hello',
nested = Container(b1 = 65, b2 = 66, b3 = None), condition = 1482184792)
data = self.this_example.build(obj)
self.assertEquals(data, "\x05helloABXXXX")


if __name__ == "__main__":
unittest.main()

2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -11,7 +11,7 @@
long_description=open("README.rst").read(),
url="https://github.com/MostAwesomeDude/construct",
author="Tomer Filiba",
author_email="tomerfiliba at gmail dot com",
author_email="tomerfiliba@gmail.com",
maintainer="Corbin Simpson",
maintainer_email="MostAwesomeDude@gmail.com",
)

0 comments on commit 969e568

Please sign in to comment.