docs: def to_mongo(qry):
      """Transform a simple query with one or more filter expressions
      into a MongoDB query expression.
  
      :param qry: Filter expression(s), see function docstring for details.
      :type qry: str or list
      :return: MongoDB query
      :rtype: dict
      :raises: BadExpression, if one of the input expressions cannot be parsed
  
      Expressions have three parts, called in order ``field``, ``operator``,
      and ``value``.
  
          - `field` is the name of a field in a MongoDB document
          - `value` is the value to compare against:
              * numeric
              * string, you MUST use 'single' or "double" quotes
              * boolean: true, false
          - `operator` is a comparison operator:
              * inequalities: >, <, =, <=, >=, !=
              * PCRE regular expression: ~
              * data type: int, float, string, or bool
              * exists: boolean (true/false) whether field exists in record
              * size: for array fields, an inequality for the array size, given as
                a suffix to the operator: size>, size<
  
      Multiple expressions can be a single string, or a list.
      In either case, the form is a "disjunction of conjunctions".
  
      In the string form:
  
          * "and" joins expressions into groups
          * "or" joins one or more expression groups
  
      In the list form:
  
          * The inner list is a group of "and"ed expressions
          * The outer list "or"s the expression groups together.
  
      In the string form, parentheses before or after the "or"ed expression
      groups [note: even non-sensical ones like '((('], are ignored.
      So these can be used to clarify the groupings.
  
      **Examples**
  
      Two sets of filters, return records where either is true:
  
      >>> to_mongo('(a > 3 and b = "hello") or (c > 1 and d = "goodbye")')
      {'$or': [{'a': {'$gt': 3}, 'b': 'hello'}, {'c': {'$gt': 1}, 'd': 'goodbye'}]}
  
      Same as previous, but without parentheses:
  
      >>> to_mongo('a > 3 and b = "hello" or c > 1 and d = "goodbye"')
      {'$or': [{'a': {'$gt': 3}, 'b': 'hello'}, {'c': {'$gt': 1}, 'd': 'goodbye'}]}
  
      Same as previous, but using lists rather than "and"/"or":
  
      >>> to_mongo([['a > 3', 'b = "hello"'], ['c > 1', 'd = "goodbye"']])
      {'$or': [{'a': {'$gt': 3}, 'b': 'hello'}, {'c': {'$gt': 1}, 'd': 'goodbye'}]}
      """
      rev = False     # filters, not constraints
      # special case for empty string/list
      if qry == "" or qry == []:
          return {}
      # break input into groups of filters
      unpar = lambda s: s.strip().strip('()')
      if isinstance(qry, str):
          groups = []
          if _TOK_OR in qry:
              groups = [unpar(g).split(_TOK_AND) for g in qry.split(_TOK_OR)]
          else:
              groups = [unpar(qry).split(_TOK_AND)]
      else:
          if isinstance(qry[0], list) or isinstance(qry[0], tuple):
              groups = qry
          else:
              groups = [qry]
      # generate mongodb queries for each filter group
      filters = []
      for filter_exprs in groups:
          mq = MongoQuery()
          for e in filter_exprs:
              try:
                  e = unpar(e)
              except AttributeError:
                  raise BadExpression(e, "expected string, got '{t}'".format(t=type(e)))
              try:
                  constraint = Constraint(*parse_expr(e))
              except ValueError as err:
                  raise BadExpression(e, err)
              clause = MongoClause(constraint, rev=rev)
              mq.add_clause(clause)
          filters.append(mq.to_mongo(rev))
      # combine together filters, or strip down the one filter
      if len(filters) > 1:
          result = {'$or': filters}
      else:
          result = filters[0]
      return result