import sys

import lxml.etree


class XPathError(Exception):
    def __init__(self, query, cause):
        self.query = query
        self.cause = cause
        super(XPathError, self).__init__(query, cause)

    def __str__(self):
        return '%s: %s' % (self.cause.__class__.__name__, self.cause.message)


class XPathQuery(object):
    def __init__(self, query, compiled_query):
        self.query = query
        self.compiled_query = compiled_query

    def __call__(self, xml):
        try:
            return self.compiled_query(xml)
        except lxml.etree.XPathError as e:
            raise XPathError(self.query, e), None, sys.exc_info()[2]


def parse_xml_filename(filename):
    return lxml.etree.parse(filename)


def compile(query):
    try:
        return XPathQuery(query, lxml.etree.XPath(query))
    except lxml.etree.XPathError as e:
        raise XPathError(query, e), None, sys.exc_info()[2]


def register_extensions():
    ns = lxml.etree.FunctionNamespace(None)
    ns['is_call'] = is_call
    ns['num_args'] = num_args
    ns['arg'] = arg
    ns['lhs'] = lhs
    ns['rhs'] = rhs
    ns['target'] = target


def is_call(context, *names):
    node = context.context_node
    if node.tag != 'ParameterizedExpr':
        return False
    if node[0].tag != 'NameExpr' or node[0].get('kind') != 'FUN':
        return False
    # no-arg is_call() returns whether this is a call to any function
    if not names:
        return True

    called_name = node[0][0].get('nameId')

    # Could call this function like
    # is_call('eval') -> names is a string
    if isinstance(names, basestring):
        return called_name == names
    # is_call('eval', 'feval') -> names is a tuple of strings
    # is_call(//some/sequence) -> names[0] is a list of strings
    names = names[0] if isinstance(names[0], list) else names
    return any(called_name == name for name in names)

def num_args(context):
    node = context.context_node
    if node.tag != 'ParameterizedExpr':
        return 0
    return len(node) - 1

def arg(context, index):
    return context.context_node[int(index)]

def lhs(context):
    return context.context_node[0]

def rhs(context):
    return context.context_node[1]

def target(context):
    return context.context_node[0]

@@ -1,12 +1,29 @@
+import functools%0A
 import sys%0A%0A
@@ -300,16 +300,20 @@
 name__, 
+str(
 self.cau
@@ -318,16 +318,9 @@
 ause
-.message
+)
 )%0A%0A%0A
@@ -558,32 +558,33 @@
 %0A        except 
+(
 lxml.etree.XPath
@@ -580,32 +580,52 @@
 etree.XPathError
+, UnexpectedContext)
  as e:%0A         
@@ -952,224 +952,746 @@
 %5D%0A%0A%0A
-def register_extensions():%0A    ns = lxml.etree.FunctionNamespace(None
+class UnexpectedContext(Exception):%0A    def __init__(self, f, context, tags):%0A        self.f = f%0A        self.context = context%0A        self.expected = tags%0A        super(UnexpectedContext, self).__init__(f, context, tags
 )%0A
+%0A
     
-ns%5B'is_call'%5D = is_call%0A    ns%5B'num_args'%5D = num_args%0A    ns%5B'arg'%5D = arg%0A    ns%5B'lhs'%5D = lhs%0A    ns%5B'rhs'%5D = rhs%0A    ns%5B'target'%5D = target%0A%0A
+def __str__(self):%0A        return '%25s called in %25s context, expecting any of %25s' %25 (%0A            self.f, self.context, ', '.join(self.expected))%0A%0A%0Adef expect(*nodes):%0A    def decorator(f):%0A        @functools.wraps(f)%0A        def helper(context, *args):%0A            node = context.context_node%0A            if node.tag not in nodes:%0A                raise UnexpectedContext(f.__name__, node.tag, nodes)%0A            return f(context, *args)%0A        return helper%0A    return decorator%0A%0A%0A@expect('ParameterizedExpr')
 %0Adef
@@ -1753,69 +1753,8 @@
 ode%0A
-    if node.tag != 'ParameterizedExpr':%0A        return False%0A
     
@@ -2385,16 +2385,63 @@
 names)%0A%0A
+%0A@expect('ParameterizedExpr', 'CellIndexExpr')%0A
 def num_
@@ -2455,31 +2455,35 @@
 ntext):%0A    
-node = 
+return len(
 context.cont
@@ -2494,91 +2494,61 @@
 node
-%0A    if node.tag != 'ParameterizedExpr':%0A        return 0%0A    return len(node) - 1%0A
+) - 1%0A%0A%0A@expect('ParameterizedExpr', 'CellIndexExpr')
 %0Adef
@@ -2614,16 +2614,39 @@
 ndex)%5D%0A%0A
+%0A@expect('AssignStmt')%0A
 def lhs(
@@ -2691,16 +2691,39 @@
 ode%5B0%5D%0A%0A
+%0A@expect('AssignStmt')%0A
 def rhs(
@@ -2772,60 +2772,335 @@
 1%5D%0A%0A
-def target(context):%0A    return context.context_node%5B0%5D
+%0A@expect('CellIndexExpr', 'DotExpr', 'ParameterizedExpr')%0Adef target(context):%0A    return context.context_node%5B0%5D%0A%0A%0Adef register_extensions():%0A    ns = lxml.etree.FunctionNamespace(None)%0A    ns%5B'is_call'%5D = is_call%0A    ns%5B'num_args'%5D = num_args%0A    ns%5B'arg'%5D = arg%0A    ns%5B'lhs'%5D = lhs%0A    ns%5B'rhs'%5D = rhs%0A    ns%5B'target'%5D = target
 %0A

Add simple context checking for xpath helpers.