def visitAttribute(self, ctx):
     # attribute : cardinality? reverseFlag? attributeOperator? attributeName
     #   ( expressionComparisonOperator expressionConstraintValue
     #   | numericComparisonOperator numericValue
     #   | stringComparisonOperator stringValue )
     #   ;
     #
     # attributeOperatorValue ::=
     # attrib expr⟨⟨expressionComparisonOperator × expressionConstraintValue⟩⟩ |
     # attrib num⟨⟨numericComparisonOperator × numericValue⟩⟩ |
     # attrib str⟨⟨stringComparisonOperator × stringValue⟩⟩ numericValue ::= nv decimal⟨⟨decimalValue⟩⟩ | nv integer⟨⟨N⟩⟩
     v = self._child_visitor(self, ctx)
     card = v.getNext(lambda e: cardinality.has_member(e))
     rf = v.getNext(lambda e: e == reverseFlag)
     # TODO: fix type checking for Seq and Set -- this is too loose
     attOper = v.getNext(lambda e: attributeOperator.has_member(e))
     name = v.getNext()
     op = v.getNext()
     targ = v.getNext()
     cp = CrossProduct()(op, targ)
     opValue = attributeOperatorValue(attrib_expr=cp) if expressionComparisonOperator.has_member(op) else \
               attributeOperatorValue(attrib_num=cp) if numericComparisonOperator.has_member(op) else \
               attributeOperatorValue(attrib_str=cp)
     arf = Optional(reverseFlags)(rf)
     acard = Optional(cardinality)(card)
     aattrOper = Optional(attributeOperator)(attOper)
     return attribute(card=Optional(cardinality)(card), rf=Optional(reverseFlags)(rf),
                      attrOper=Optional(attributeOperator)(attOper), name=name, opValue=opValue)
 def visitAttributeGroup(self, ctx):
     # attributeGroup == cardinality [0 . . 1] × attributeSet
     v = self._child_visitor(self, ctx)
     card = v.getNext(lambda e: cardinality.has_member(e))
     v.getNext()         # open braces
     attset = v.getNext()
     v.getNext()         # close braces
     return attributeGroup(Optional(cardinality)(card), attset)