docs: def eval(self, exp):
      "main dispatch for expression evaluation"
      # todo: this needs an AST-assert that all BaseX descendants are being handled
      if isinstance(exp,sqparse2.BinX): return evalop(exp.op.op, *map(self.eval, (exp.left, exp.right)))
      elif isinstance(exp,sqparse2.UnX): return self.eval_unx(exp)
      elif isinstance(exp,sqparse2.NameX): return self.nix.rowget(self.tables,self.c_row,exp)
      elif isinstance(exp,sqparse2.AsterX): return sum(self.c_row,[]) # todo doc: how does this get disassembled by caller?
      elif isinstance(exp,sqparse2.ArrayLit): return map(self.eval,exp.vals)
      elif isinstance(exp,(sqparse2.Literal,sqparse2.ArrayLit)): return exp.toliteral()
      elif isinstance(exp,sqparse2.CommaX):
        # todo: think about getting rid of CommaX everywhere; it complicates syntax tree navigation.
        #   a lot of things that are CommaX now should become weval.Row.
        ret = []
        for child in exp.children:
          (ret.extend if starlike(child) else ret.append)(self.eval(child))
        return ret
      elif isinstance(exp,sqparse2.CallX): return self.eval_callx(exp)
      elif isinstance(exp,sqparse2.SelectX):
        raise NotImplementedError('subqueries should have been evaluated earlier') # todo: specific error class
      elif isinstance(exp,sqparse2.AttrX):return self.nix.rowget(self.tables,self.c_row,exp)
      elif isinstance(exp,sqparse2.CaseX):
        for case in exp.cases:
          if self.eval(case.when): return self.eval(case.then)
        return self.eval(exp.elsex)
      elif isinstance(exp,sqparse2.CastX):
        if exp.to_type.type.lower() in ('text','varchar'): return unicode(self.eval(exp.expr))
        else: raise NotImplementedError('unhandled_cast_type',exp.to_type)
      elif isinstance(exp,(int,basestring,float,type(None))):
        return exp # I think Table.insert is creating this in expand_row
      # todo: why tuple, list, dict below? throw some asserts in here and see where these are coming from.
      elif isinstance(exp,tuple): return tuple(map(self.eval, exp))
      elif isinstance(exp,list): return map(self.eval, exp)
      elif isinstance(exp,dict): return exp
      elif isinstance(exp,sqparse2.NullX): return None
      elif isinstance(exp,sqparse2.ReturnX):
        # todo: I think ReturnX is *always* CommaX now; revisit this
        ret=self.eval(exp.expr)
        print "warning: not sure what I'm doing here with cardinality tweak on CommaX"
        return [ret] if isinstance(exp.expr,(sqparse2.CommaX,sqparse2.AsterX)) else [[ret]] # todo: update parser so this is always * or a commalist
      elif isinstance(exp,sqparse2.AliasX): return self.eval(exp.name) # todo: rename AliasX 'name' to 'expr'
      else: raise NotImplementedError(type(exp), exp)