Add a grammar rules to _self.rules_, _self.rule2func_,
            and _self.rule2name_

        Comments, lines starting with # and blank lines are stripped from
        doc. We also allow limited form of * and + when there it is of
        the RHS has a single item, e.g.
               stmts ::= stmt+

def addRule(self, doc, func, _preprocess=True):
        """Add a grammar rules to _self.rules_, _self.rule2func_,
            and _self.rule2name_

        Comments, lines starting with # and blank lines are stripped from
        doc. We also allow limited form of * and + when there it is of
        the RHS has a single item, e.g.
               stmts ::= stmt+
        """
        fn = func

        # remove blanks lines and comment lines, e.g. lines starting with "#"
        doc = os.linesep.join([s for s in doc.splitlines() if s and not re.match("^\s*#", s)])

        rules = doc.split()

        index = []
        for i in range(len(rules)):
            if rules[i] == '::=':
                index.append(i-1)
        index.append(len(rules))

        for i in range(len(index)-1):
            lhs = rules[index[i]]
            rhs = rules[index[i]+2:index[i+1]]
            rule = (lhs, tuple(rhs))

            if _preprocess:
                rule, fn = self.preprocess(rule, func)

            # Handle a stripped-down form of *, +, and ?:
            #   allow only one nonterminal on the right-hand side
            if len(rule[1]) == 1:

                if rule[1][0] == rule[0]:
                    raise TypeError("Complete recursive rule %s" % rule2str(rule))

                repeat = rule[1][-1][-1]
                if repeat in ('*', '+', '?'):
                    nt = rule[1][-1][:-1]

                    if repeat == '?':
                        new_rule_pair = [rule[0], list((nt,))]
                        self.optional_nt.add(rule[0])
                    else:
                        self.list_like_nt.add(rule[0])
                        new_rule_pair = [rule[0], [rule[0]] + list((nt,))]
                    new_rule = rule2str(new_rule_pair)
                    self.addRule(new_rule, func, _preprocess)
                    if repeat == '+':
                        second_rule_pair = (lhs, (nt,))
                    else:
                        second_rule_pair = (lhs, tuple())
                    new_rule = rule2str(second_rule_pair)
                    self.addRule(new_rule, func, _preprocess)
                    continue

            if lhs in self.rules:
                if rule in self.rules[lhs]:
                    if 'dups' in self.debug and self.debug['dups']:
                        self.duplicate_rule(rule)
                    continue
                self.rules[lhs].append(rule)
            else:
                self.rules[lhs] = [ rule ]
            self.rule2func[rule] = fn
            self.rule2name[rule] = func.__name__[2:]
            self.ruleschanged = True

            # Note: In empty rules, i.e. len(rule[1] == 0, we don't
            # call reductions on explicitly. Instead it is computed
            # implicitly.
            if self.profile_info is not None and len(rule[1]) > 0:
                rule_str = self.reduce_string(rule)
                if rule_str not in self.profile_info:
                    self.profile_info[rule_str] = 0
            pass
        return