r"""
    http://stackoverflow.com/questions/4801403/how-can-i-use-pyparsing-to-parse-nested-expressions-that-have-mutiple-opener-clo

    Ignore:
        >>> from utool.util_gridsearch import *  # NOQA
        cfgopt_strs = 'f=2,c=[(1,2),(3,4)],d=1,j=[[1,2],[3,4]],foobar,x="[fdsfds",y="]fdsfds",e=[[1,2],[3,4]],[]'
        string  =  cfgopt_strs
        parse_cfgstr3(string)

    CommandLine:
        python -m utool.util_gridsearch parse_cfgstr3 --show

    Example:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_gridsearch import *  # NOQA
        >>> import utool as ut
        >>> cfgopt_strs = 'b=[1,2]'
        >>> cfgdict = parse_cfgstr3(cfgopt_strs)
        >>> result = ('cfgdict = %s' % (ut.repr2(cfgdict),))
        >>> print(result)
        cfgdict = {'b': [1, 2]}

    Example:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_gridsearch import *  # NOQA
        >>> import utool as ut
        >>> cfgopt_strs = 'myprefix=False,sentence_break=False'
        >>> cfgdict = parse_cfgstr3(cfgopt_strs, debug=True)
        >>> print('cfgopt_strs = %r' % (cfgopt_strs,))
        >>> result = ('cfgdict = %s' % (ut.repr2(cfgdict),))
        >>> print(result)
        cfgdict = {'myprefix': False, 'sentence_break': False}

def parse_cfgstr3(string, debug=None):
    r"""
    http://stackoverflow.com/questions/4801403/how-can-i-use-pyparsing-to-parse-nested-expressions-that-have-mutiple-opener-clo

    Ignore:
        >>> from utool.util_gridsearch import *  # NOQA
        cfgopt_strs = 'f=2,c=[(1,2),(3,4)],d=1,j=[[1,2],[3,4]],foobar,x="[fdsfds",y="]fdsfds",e=[[1,2],[3,4]],[]'
        string  =  cfgopt_strs
        parse_cfgstr3(string)

    CommandLine:
        python -m utool.util_gridsearch parse_cfgstr3 --show

    Example:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_gridsearch import *  # NOQA
        >>> import utool as ut
        >>> cfgopt_strs = 'b=[1,2]'
        >>> cfgdict = parse_cfgstr3(cfgopt_strs)
        >>> result = ('cfgdict = %s' % (ut.repr2(cfgdict),))
        >>> print(result)
        cfgdict = {'b': [1, 2]}

    Example:
        >>> # ENABLE_DOCTEST
        >>> from utool.util_gridsearch import *  # NOQA
        >>> import utool as ut
        >>> cfgopt_strs = 'myprefix=False,sentence_break=False'
        >>> cfgdict = parse_cfgstr3(cfgopt_strs, debug=True)
        >>> print('cfgopt_strs = %r' % (cfgopt_strs,))
        >>> result = ('cfgdict = %s' % (ut.repr2(cfgdict),))
        >>> print(result)
        cfgdict = {'myprefix': False, 'sentence_break': False}
    """
    import utool as ut  # NOQA
    import pyparsing as pp

    if debug is None:
        debug_ = ut.VERBOSE
    else:
        debug_ = debug

    def as_tagged(parent, doctag=None, namedItemsOnly=False):
        """Returns the parse results as XML. Tags are created for tokens and lists that have defined results names."""
        namedItems = dict((v[1], k) for (k, vlist) in parent._ParseResults__tokdict.items()
                          for v in vlist)
        # collapse out indents if formatting is not desired
        parentTag = None
        if doctag is not None:
            parentTag = doctag
        else:
            if parent._ParseResults__name:
                parentTag = parent._ParseResults__name
        if not parentTag:
            if namedItemsOnly:
                return ""
            else:
                parentTag = "ITEM"
        out = []
        for i, res in enumerate(parent._ParseResults__toklist):
            if isinstance(res, pp.ParseResults):
                if i in namedItems:
                    child = as_tagged(
                        res, namedItems[i], namedItemsOnly and doctag is None)
                else:
                    child = as_tagged(
                        res, None, namedItemsOnly and doctag is None)
                out.append(child)
            else:
                # individual token, see if there is a name for it
                resTag = None
                if i in namedItems:
                    resTag = namedItems[i]
                if not resTag:
                    if namedItemsOnly:
                        continue
                    else:
                        resTag = "ITEM"
                child = (resTag, pp._ustr(res))
                out += [child]
        return (parentTag, out)

    def combine_nested(opener, closer, content, name=None):
        """
        opener, closer, content = '(', ')', nest_body
        """
        import utool as ut  # NOQA
        ret1 = pp.Forward()
        _NEST = ut.identity
        #_NEST = pp.Suppress
        opener_ = _NEST(opener)
        closer_ = _NEST(closer)
        # ret1 <<= pp.Group(opener_ + pp.ZeroOrMore(content) + closer_)
        ret2 = ret1 << pp.Group(opener_ + pp.ZeroOrMore(content) + closer_)
        if ret2 is None:
            ret2 = ret1
        else:
            pass
            #raise AssertionError('Weird pyparsing behavior. Comment this line if encountered. pp.__version__ = %r' % (pp.__version__,))
        if name is None:
            ret3 = ret2
        else:
            ret3 = ret2.setResultsName(name)
        assert ret3 is not None, 'cannot have a None return'
        return ret3

    # Current Best Grammar
    STRING = (pp.quotedString.copy()).setResultsName('quotedstring')
    NUM    = pp.Word(pp.nums).setResultsName('num')
    NAME   = pp.Regex('[a-zA-Z_][a-zA-Z_0-9]*')
    atom   = (NAME | NUM | STRING).setResultsName('atom')
    key    = pp.Word(pp.alphanums + '_').setResultsName('key')  # identifier

    nest_body = pp.Forward().setResultsName('nest_body')
    nestedParens   = combine_nested('(', ')', content=nest_body, name='paren')
    nestedBrackets = combine_nested('[', ']', content=nest_body, name='brak')
    nestedCurlies  = combine_nested('{', '}', content=nest_body, name='curl')

    nest_stmt = pp.Combine((nestedParens | nestedBrackets | nestedCurlies).setResultsName('sequence'))

    val = (atom | nest_stmt)

    # Nest body cannot have assignments in it
    #COMMA = pp.Suppress(',')
    COMMA = ','
    nest_body << val + pp.ZeroOrMore(COMMA + val)

    assign = pp.Group(key + pp.Suppress('=') + (val)).setResultsName('assign')
    #item = (assign | val).setResultsName('item')
    item = (assign | val)
    # OMG THIS LINE CAUSES NON-DETERMENISTIC RESULTS IN PYTHON3
    #item = (assign | val).setResultsName('item')

    # Assignments only allowed at outer level
    assign_body = item + pp.ZeroOrMore(pp.Suppress(',') + item)

    if len(string) > 0:
        tokens = assign_body.parseString(string)
        if debug_:
            print('string = %r' % (string,))
            print('tokens List: ' + ut.repr3(tokens.asList()))
            print('tokens XML: ' + tokens.asXML())
        parsed_blocks = as_tagged(tokens)[1]
        if debug_:
            print('PARSED_BLOCKS = ' + ut.repr3(parsed_blocks, nl=1))
    else:
        parsed_blocks = []

    from utool import util_type  # NOQA
    #from collections import OrderedDict
    #cfgdict = OrderedDict()
    cfgdict = {}
    for item in parsed_blocks:
        if item[0] == 'assign':
            keyval_pair = item[1]
            keytup, valtup = keyval_pair
            key = keytup[1]
            val = util_type.smart_cast2(valtup[1])
            #val = valtup[1]
        elif item[0] == 'atom':
            key = item[1]
            val = True
        else:
            key = None
            val = None
        if key is not None:
            cfgdict[key] = val
    if debug_:
        print('[TOKENS] CFGDICT ' + ut.repr3(cfgdict, nl=1))
        #x = list(map(type, cfgdict.keys()))
        #print(x)
        #print(list(cfgdict.keys()))
        #if len(x) > 0 and str(x[0]).find('Word') > -1:
        #    import utool
        #    utool.embed()
    return cfgdict