def __init__(self): self.KNOWN_WORDS = [] self.add_known_words(*PARSER_ARTICLES) # subparser takes (parser, var, input, i, ctxt, actor, next) self.subparsers = dict() self.object_classes = {"something": "thing", "somewhere": "room"} self.parse_thing = ActivityTable(accumulator=list_append, doc="""A parse_thing parser takes the arguments (parser, subparser, var, name, words, input, i, ctxt, next, multiplier=1), and then tries to parse the input from the point of view of the name and the words=(adjs,nouns) arguments. The subparser is the name of the subparser which called parse_thing so that we can disambiguate properly. Note: this may be backwards from what is expected--we try to find the thing we're looking for rather than try to find any thing. Should return [Matched(..), ...] or something.""")
def __init__(self) : self.KNOWN_WORDS = [] self.add_known_words(*PARSER_ARTICLES) # subparser takes (parser, var, input, i, ctxt, actor, next) self.subparsers = dict() self.object_classes = {"something" : "thing", "somewhere" : "room"} self.parse_thing = ActivityTable(accumulator=list_append, doc="""A parse_thing parser takes the arguments (parser, subparser, var, name, words, input, i, ctxt, next, multiplier=1), and then tries to parse the input from the point of view of the name and the words=(adjs,nouns) arguments. The subparser is the name of the subparser which called parse_thing so that we can disambiguate properly. Note: this may be backwards from what is expected--we try to find the thing we're looking for rather than try to find any thing. Should return [Matched(..), ...] or something.""")
class Parser(object) : def __init__(self) : self.KNOWN_WORDS = [] self.add_known_words(*PARSER_ARTICLES) # subparser takes (parser, var, input, i, ctxt, actor, next) self.subparsers = dict() self.object_classes = {"something" : "thing", "somewhere" : "room"} self.parse_thing = ActivityTable(accumulator=list_append, doc="""A parse_thing parser takes the arguments (parser, subparser, var, name, words, input, i, ctxt, next, multiplier=1), and then tries to parse the input from the point of view of the name and the words=(adjs,nouns) arguments. The subparser is the name of the subparser which called parse_thing so that we can disambiguate properly. Note: this may be backwards from what is expected--we try to find the thing we're looking for rather than try to find any thing. Should return [Matched(..), ...] or something.""") def init_current_objects(self, ctxt, with_objs=None) : """For parsing efficiency of things (needed in the something parser). Gets the referenceable objects and their words.""" self.current_objects = dict() self.current_words = dict() self.current_names = dict() for parser, kind in self.object_classes.iteritems() : if with_objs and parser in with_objs : self.current_objects[parser] = with_objs[parser] else : self.current_objects[parser] = ctxt.world.activity.objects_of_kind(kind) self.current_words[parser] = [separate_object_words(ctxt.world.get_property("Words", o)) for o in self.current_objects[parser]] self.current_names[parser] = dict() for o in self.current_objects[parser] : self.current_names[parser][o] = " ".join(ctxt.stringeval.eval_str(ctxt.world.get_property("Name", o), ctxt).split()) def add_object_class(self, parsername, kind) : """Sets up the object_classes dictionary for a subparser called parsername so that current_objects[parsername] will be loaded with objects of kind kind when init_current_objects is run. The current_words[parsername] entry will be updated.""" self.object_classes[parsername] = kind def add_known_words(self,*words) : """Helps let the user know which word was not recognized when they make a typo.""" self.KNOWN_WORDS.extend(words) def __is_word_for_thing(self, word) : for word_list in self.current_words.itervalues() : for adjs,nouns in word_list : if word in adjs or word in nouns : return True return False def define_subparser(self, name, doc=None) : self.subparsers[name] = ActivityTable(accumulator=list_append, doc=doc) def add_subparser(self, name, **kwargs) : def _add_subparser(f) : self.subparsers[name].add_handler(f, **kwargs) return f return _add_subparser def run_subparser(self, name, var, input, i, ctxt, actor, next) : return self.subparsers[name].notify([self, var, input, i, ctxt, actor, next], {}) def run_parser(self, name, input, ctxt) : """Like run_subparser, but matches the end of input, too.""" def _end(i) : if len(input) == i : return [[]] else : return [] return self.subparsers[name].notify([self, None, input, 0, ctxt, ctxt.actor, _end], {}) def understand(self, text, result=None, dest="action") : """Takes a textual form of a command and adds it to the parser so that action is executed. The following is an example of the textual form: give [something y] to [something z] The understood nonterminals are [something varname] and [anything varname] where "something" refers to something the actor can immediately access, where "anything" refers to anything in the game world. One can give multiple options for a word by using a slash. For instance: take/get [something x] Other nonterminals: - [direction x] for a cardinal direction - [text x] for taking some number of arbitrary words, and then binding x to the resulting string. - [object oid] for matching against a particular object. The whitespace in the variable is turned into a single space. For instance, [object my ball] is the same as [object my ball].""" parts = [] lastindex = 0 for match in re.finditer(r"\[([A-Za-z]+)\s+([^\]]+)\]", text) : textparts = text[lastindex:match.start()].split() for tp in textparts : parts.append(tp.lower().split("/")) lastindex = match.end() if self.subparsers.has_key(match.group(1).lower()) : csp = CallSubParser(match.group(1), " ".join(match.group(2).split())) parts.append(csp) else : raise Exception("No such subparser %r" % match.group(1)) textparts = text[lastindex:].split() for tp in textparts : parts.append(tp.lower().split("/")) self.__subparser_add_sequence(dest, parts, result, text) def __subparser_add_sequence(self, dest, parts, result, text) : """Takes a sequence (parts) and adds a parser to the dest subparser which then returns result. To be used with understand.""" # first let the parser know about the words for part in parts : if type(part) is list : self.add_known_words(*part) # then add the subparser @self.add_subparser(dest) @docstring("Parses "+repr(text)+" as "+repr(result)+".") def _handler_sequence(parser, var, input, i, ctxt, actor, next) : def _handler_part(part_i, var, input, i2) : def _next2(i3) : return _handler_part(part_i+1, var, input, i3) if part_i == len(parts) : # we've matched all of our parts, so return (with # extra data at the end so we can construct the # proper Matched object) return [[(i2, next(i2))]] elif type(parts[part_i]) is CallSubParser : # or, we have a subparser to execute csp = parts[part_i] return parser.run_subparser(csp.name, csp.var, input, i2, ctxt, actor, _next2) elif i2 < len(input) and type(parts[part_i]) is list and input[i2].lower() in parts[part_i] : # as in "if we haven't reached the end of input, # and we have a list of options in parts, check to # see if the input is currently one of these # options." Then we go on. return _handler_part(part_i+1, var, input, i2+1) else : return [] res = _handler_part(0, var, input, i) out = [] # We need to massage the data to handle the last tuple. for r in res : i2, rest = r[-1] subobjects = r[:-1] score = 0 matches = dict() subparsers = dict() for part in subobjects : if type(part) is Matched : score += part.score # score is accumulation of all subscores if part.var : matches[part.var] = part.value if part.subparser == "action" : subparsers[part.var] = part.supdata else : subparsers[part.var] = ("subparser", part.subparser) matches["actor"] = actor subparsers["actor"] = ("subparser", "something") supdata = None if result : if type(result) == str : value = result elif isinstance(result, AbstractPattern) : try : value = result.expand_pattern(matches, data={"world":ctxt.world}) supdata = result.expand_pattern(subparsers) except ExpansionException : continue # just skip it! else : value = result(**matches) else : value = subobjects m = Matched(input[i:i2], value, score, dest, var=var, subobjects=subobjects) m.supdata = supdata # hack!!! We need this to disambiguate properly out.extend(product([[m]], rest)) return out def transform_text_to_words(self, text) : text = text.replace(",", " , ").replace("?", " ? ").replace("!", " ! ").strip() return text.split() def handle_all(self, input, ctxt, action_verifier, allow_period_at_end=False) : input = input.strip() if allow_period_at_end and input.endswith(".") : input = input[:-1] words = self.transform_text_to_words(input) if not words : raise NoInput() self.init_current_objects(ctxt) results = [r[0] for r in self.run_parser("action", words, ctxt)] if not results : # then maybe we didn't know one of the words for word in words : word = word.lower() if word not in self.KNOWN_WORDS and not self.__is_word_for_thing(word) : raise NoSuchWord(word) raise NoUnderstand() action, did_disambiguate = self.disambiguate(results, ctxt, action_verifier) return (action, did_disambiguate) def disambiguate(self, results, ctxt, action_verifier) : """Try to disambiguate the results if needed using the action_verifier to get whether things work. Returns (action, did_disambiguate) pair, where did_disambiguate represents whether there were multiple logical options.""" if len(results) == 1 : # no need to disambiguate return results[0].value, False else : # it's ambiguous! # first, see if verification helps at all scores_pre = [(r, action_verifier(r.value, ctxt)) for r in results] # we separate out the ones which are illogical because # something wasn't visible because we don't want to even # mention the objects involved (because they weren't # visible). scores = [(r, v) for r,v in scores_pre if type(v) is not IllogicalNotVisible] if len(scores) == 0 : # In this case, we are stuck with an invalid action # because some item is not visible scores_not_visible = [r for r,v in scores_pre if type(v) is IllogicalNotVisible] # we say is_disambiguating=False so there is no # disambiguation message return scores_not_visible[0].value, False scores.sort(key=lambda z : z[1].score) is_disambiguating = 1 < len([True for r,v in scores if v.is_acceptible()]) if scores[-1][1].is_acceptible() : # good, there is some acceptible action. best_score = scores[-1][1].score best_results = [r for r,v in scores if v.score >= best_score] if len(best_results) == 1 : # good, the verification score saved us return best_results[0].value, is_disambiguating else : # we assume that the order of the results # disambiguates potential multi-action result sets # (that is, we assume the order reflects the order # of parser definition). We take the action # parsed by the last-defined parser ordered_best_results = [r for r in results if r in best_results] ordered_best_results.reverse() new_results = [] for r in ordered_best_results : if type(r.value) is type(ordered_best_results[0].value) : new_results.append(r) else : break new_results.sort(key=lambda r : r.score) best_score = new_results[-1].score new_best_results = [r for r in new_results if r.score >= best_score] if len(new_best_results) == 1 : # good, the specificity score saved us return new_best_results[0].value, True else : # We need the user to disambiguate. The # following returns the Ambiguous exception. raise self.__construct_amb_exception([r.value for r in new_best_results], [r.supdata for r in new_best_results]) else : # well, none of them are acceptible. Let's go for the # worst one. return scores[0][0].value, True def __construct_amb_exception(self, results, supdata) : # It's ambiguous. Construct the possibilities for each argument def __construct_pattern(results, supdata, subparsers, to_replace, next_var) : poss_args = [[a] for a in results[0].args] poss_subparsers = [(isinstance(r, BasicAction) and s) or (type(s) == tuple and len(s) == 2 and s[0] == "subparser" and s[1]) for r,s in zip(results[0].args, supdata[0].args)] # the following represents that the result is a matched object # which needs to be followed for disambiguation more_disambig_flag = [isinstance(a, BasicAction) for a in results[0].args] for r,sup in zip(results[1:], supdata[1:]) : for a,curr_poss in zip(r.args, poss_args) : if a not in curr_poss : curr_poss.append(a) for i in xrange(0, len(poss_subparsers)) : # part of a hack to get the 'subparser' s = sup.args if more_disambig_flag[i] : if not poss_subparsers[i] : poss_subparsers[i] = r[i] elif type(r.args[i]) != type(s[i]) : raise Exception("Conflicting subactions", r.args[i], s) elif type(s[i]) == tuple and len(s[i]) == 2 and s[i][0] == "subparser" : if not poss_subparsers[i] : poss_subparsers[i] = s[i][1] elif s[i][1] != poss_subparsers[i] : raise Exception("Conflicting subparsers", s[i][1], poss_subparsers[i]) constructed_args = [] for poss, disamb_more, sp, i in zip(poss_args, more_disambig_flag, poss_subparsers, xrange(0, len(poss_args))) : var = next_var[0] next_var[0] = chr(ord(var) + 1) if len(poss) == 1 : # no need to disambiguate this slot constructed_args.extend(poss) elif i == 0 : # this is the actor of the action. don't need to disambiguate constructed_args.append(poss[0]) else : if disamb_more : constructed_args.append(__construct_pattern(poss, [sp]*len(poss), # len maybe hack? subparsers, to_replace, next_var)) else : to_replace[var] = poss subparsers[var] = sp constructed_args.append(VarPattern(var)) return type(results[0])(*constructed_args) to_replace = dict() subparsers = dict() return Ambiguous(__construct_pattern(results, supdata, subparsers, to_replace, ['a']), to_replace, subparsers) def copy(self) : newparser = Parser() newparser.KNOWN_WORDS = list(self.KNOWN_WORDS) for name, table in self.subparsers.iteritems() : newparser.subparsers[name] = table.copy() newparser.parse_thing = self.parse_thing.copy() newparser.object_classes = self.object_classes.copy() return newparser def make_documentation(self, escape, heading_level=1) : hls = str(heading_level) shls = str(heading_level+1) print "<h"+hls+">Parser</h"+hls+">" print "<p>This is the documentation for the parser.</p>" for spn in self.subparsers.iterkeys() : print "<h"+shls+">"+escape(spn)+"</h"+shls+">" self.subparsers[spn].make_documentation(escape, heading_level=heading_level+2)
def _to(f): if not self._activities.has_key(name): self._activities[name] = ActivityTable() self._activities[name].add_handler(f, **kwargs) return f
def define_activity(self, name, **kwargs): self._activities[name] = ActivityTable(**kwargs)
def define_subparser(self, name, doc=None): self.subparsers[name] = ActivityTable(accumulator=list_append, doc=doc)
class Parser(object): def __init__(self): self.KNOWN_WORDS = [] self.add_known_words(*PARSER_ARTICLES) # subparser takes (parser, var, input, i, ctxt, actor, next) self.subparsers = dict() self.object_classes = {"something": "thing", "somewhere": "room"} self.parse_thing = ActivityTable(accumulator=list_append, doc="""A parse_thing parser takes the arguments (parser, subparser, var, name, words, input, i, ctxt, next, multiplier=1), and then tries to parse the input from the point of view of the name and the words=(adjs,nouns) arguments. The subparser is the name of the subparser which called parse_thing so that we can disambiguate properly. Note: this may be backwards from what is expected--we try to find the thing we're looking for rather than try to find any thing. Should return [Matched(..), ...] or something.""") def init_current_objects(self, ctxt, with_objs=None): """For parsing efficiency of things (needed in the something parser). Gets the referenceable objects and their words.""" self.current_objects = dict() self.current_words = dict() self.current_names = dict() for parser, kind in self.object_classes.iteritems(): if with_objs and parser in with_objs: self.current_objects[parser] = with_objs[parser] else: self.current_objects[ parser] = ctxt.world.activity.objects_of_kind(kind) self.current_words[parser] = [ separate_object_words(ctxt.world.get_property("Words", o)) for o in self.current_objects[parser] ] self.current_names[parser] = dict() for o in self.current_objects[parser]: self.current_names[parser][o] = " ".join( ctxt.stringeval.eval_str( ctxt.world.get_property("Name", o), ctxt).split()) def add_object_class(self, parsername, kind): """Sets up the object_classes dictionary for a subparser called parsername so that current_objects[parsername] will be loaded with objects of kind kind when init_current_objects is run. The current_words[parsername] entry will be updated.""" self.object_classes[parsername] = kind def add_known_words(self, *words): """Helps let the user know which word was not recognized when they make a typo.""" self.KNOWN_WORDS.extend(words) def __is_word_for_thing(self, word): for word_list in self.current_words.itervalues(): for adjs, nouns in word_list: if word in adjs or word in nouns: return True return False def define_subparser(self, name, doc=None): self.subparsers[name] = ActivityTable(accumulator=list_append, doc=doc) def add_subparser(self, name, **kwargs): def _add_subparser(f): self.subparsers[name].add_handler(f, **kwargs) return f return _add_subparser def run_subparser(self, name, var, input, i, ctxt, actor, next): return self.subparsers[name].notify( [self, var, input, i, ctxt, actor, next], {}) def run_parser(self, name, input, ctxt): """Like run_subparser, but matches the end of input, too.""" def _end(i): if len(input) == i: return [[]] else: return [] return self.subparsers[name].notify( [self, None, input, 0, ctxt, ctxt.actor, _end], {}) def understand(self, text, result=None, dest="action"): """Takes a textual form of a command and adds it to the parser so that action is executed. The following is an example of the textual form: give [something y] to [something z] The understood nonterminals are [something varname] and [anything varname] where "something" refers to something the actor can immediately access, where "anything" refers to anything in the game world. One can give multiple options for a word by using a slash. For instance: take/get [something x] Other nonterminals: - [direction x] for a cardinal direction - [text x] for taking some number of arbitrary words, and then binding x to the resulting string. - [object oid] for matching against a particular object. The whitespace in the variable is turned into a single space. For instance, [object my ball] is the same as [object my ball].""" parts = [] lastindex = 0 for match in re.finditer(r"\[([A-Za-z]+)\s+([^\]]+)\]", text): textparts = text[lastindex:match.start()].split() for tp in textparts: parts.append(tp.lower().split("/")) lastindex = match.end() if self.subparsers.has_key(match.group(1).lower()): csp = CallSubParser(match.group(1), " ".join(match.group(2).split())) parts.append(csp) else: raise Exception("No such subparser %r" % match.group(1)) textparts = text[lastindex:].split() for tp in textparts: parts.append(tp.lower().split("/")) self.__subparser_add_sequence(dest, parts, result, text) def __subparser_add_sequence(self, dest, parts, result, text): """Takes a sequence (parts) and adds a parser to the dest subparser which then returns result. To be used with understand.""" # first let the parser know about the words for part in parts: if type(part) is list: self.add_known_words(*part) # then add the subparser @self.add_subparser(dest) @docstring("Parses " + repr(text) + " as " + repr(result) + ".") def _handler_sequence(parser, var, input, i, ctxt, actor, next): def _handler_part(part_i, var, input, i2): def _next2(i3): return _handler_part(part_i + 1, var, input, i3) if part_i == len(parts): # we've matched all of our parts, so return (with # extra data at the end so we can construct the # proper Matched object) return [[(i2, next(i2))]] elif type(parts[part_i]) is CallSubParser: # or, we have a subparser to execute csp = parts[part_i] return parser.run_subparser(csp.name, csp.var, input, i2, ctxt, actor, _next2) elif i2 < len(input) and type( parts[part_i]) is list and input[i2].lower( ) in parts[part_i]: # as in "if we haven't reached the end of input, # and we have a list of options in parts, check to # see if the input is currently one of these # options." Then we go on. return _handler_part(part_i + 1, var, input, i2 + 1) else: return [] res = _handler_part(0, var, input, i) out = [] # We need to massage the data to handle the last tuple. for r in res: i2, rest = r[-1] subobjects = r[:-1] score = 0 matches = dict() subparsers = dict() for part in subobjects: if type(part) is Matched: score += part.score # score is accumulation of all subscores if part.var: matches[part.var] = part.value if part.subparser == "action": subparsers[part.var] = part.supdata else: subparsers[part.var] = ("subparser", part.subparser) matches["actor"] = actor subparsers["actor"] = ("subparser", "something") supdata = None if result: if type(result) == str: value = result elif isinstance(result, AbstractPattern): try: value = result.expand_pattern( matches, data={"world": ctxt.world}) supdata = result.expand_pattern(subparsers) except ExpansionException: continue # just skip it! else: value = result(**matches) else: value = subobjects m = Matched(input[i:i2], value, score, dest, var=var, subobjects=subobjects) m.supdata = supdata # hack!!! We need this to disambiguate properly out.extend(product([[m]], rest)) return out def transform_text_to_words(self, text): text = text.replace(",", " , ").replace("?", " ? ").replace("!", " ! ").strip() return text.split() def handle_all(self, input, ctxt, action_verifier, allow_period_at_end=False): input = input.strip() if allow_period_at_end and input.endswith("."): input = input[:-1] words = self.transform_text_to_words(input) if not words: raise NoInput() self.init_current_objects(ctxt) results = [r[0] for r in self.run_parser("action", words, ctxt)] if not results: # then maybe we didn't know one of the words for word in words: word = word.lower() if word not in self.KNOWN_WORDS and not self.__is_word_for_thing( word): raise NoSuchWord(word) raise NoUnderstand() action, did_disambiguate = self.disambiguate(results, ctxt, action_verifier) return (action, did_disambiguate) def disambiguate(self, results, ctxt, action_verifier): """Try to disambiguate the results if needed using the action_verifier to get whether things work. Returns (action, did_disambiguate) pair, where did_disambiguate represents whether there were multiple logical options.""" if len(results) == 1: # no need to disambiguate return results[0].value, False else: # it's ambiguous! # first, see if verification helps at all scores_pre = [(r, action_verifier(r.value, ctxt)) for r in results] # we separate out the ones which are illogical because # something wasn't visible because we don't want to even # mention the objects involved (because they weren't # visible). scores = [(r, v) for r, v in scores_pre if type(v) is not IllogicalNotVisible] if len(scores) == 0: # In this case, we are stuck with an invalid action # because some item is not visible scores_not_visible = [ r for r, v in scores_pre if type(v) is IllogicalNotVisible ] # we say is_disambiguating=False so there is no # disambiguation message return scores_not_visible[0].value, False scores.sort(key=lambda z: z[1].score) is_disambiguating = 1 < len( [True for r, v in scores if v.is_acceptible()]) if scores[-1][1].is_acceptible(): # good, there is some acceptible action. best_score = scores[-1][1].score best_results = [r for r, v in scores if v.score >= best_score] if len(best_results ) == 1: # good, the verification score saved us return best_results[0].value, is_disambiguating else: # we assume that the order of the results # disambiguates potential multi-action result sets # (that is, we assume the order reflects the order # of parser definition). We take the action # parsed by the last-defined parser ordered_best_results = [ r for r in results if r in best_results ] ordered_best_results.reverse() new_results = [] for r in ordered_best_results: if type(r.value) is type( ordered_best_results[0].value): new_results.append(r) else: break new_results.sort(key=lambda r: r.score) best_score = new_results[-1].score new_best_results = [ r for r in new_results if r.score >= best_score ] if len(new_best_results ) == 1: # good, the specificity score saved us return new_best_results[0].value, True else: # We need the user to disambiguate. The # following returns the Ambiguous exception. raise self.__construct_amb_exception( [r.value for r in new_best_results], [r.supdata for r in new_best_results]) else: # well, none of them are acceptible. Let's go for the # worst one. return scores[0][0].value, True def __construct_amb_exception(self, results, supdata): # It's ambiguous. Construct the possibilities for each argument def __construct_pattern(results, supdata, subparsers, to_replace, next_var): poss_args = [[a] for a in results[0].args] poss_subparsers = [ (isinstance(r, BasicAction) and s) or (type(s) == tuple and len(s) == 2 and s[0] == "subparser" and s[1]) for r, s in zip(results[0].args, supdata[0].args) ] # the following represents that the result is a matched object # which needs to be followed for disambiguation more_disambig_flag = [ isinstance(a, BasicAction) for a in results[0].args ] for r, sup in zip(results[1:], supdata[1:]): for a, curr_poss in zip(r.args, poss_args): if a not in curr_poss: curr_poss.append(a) for i in xrange(0, len(poss_subparsers) ): # part of a hack to get the 'subparser' s = sup.args if more_disambig_flag[i]: if not poss_subparsers[i]: poss_subparsers[i] = r[i] elif type(r.args[i]) != type(s[i]): raise Exception("Conflicting subactions", r.args[i], s) elif type(s[i]) == tuple and len( s[i]) == 2 and s[i][0] == "subparser": if not poss_subparsers[i]: poss_subparsers[i] = s[i][1] elif s[i][1] != poss_subparsers[i]: raise Exception("Conflicting subparsers", s[i][1], poss_subparsers[i]) constructed_args = [] for poss, disamb_more, sp, i in zip(poss_args, more_disambig_flag, poss_subparsers, xrange(0, len(poss_args))): var = next_var[0] next_var[0] = chr(ord(var) + 1) if len(poss) == 1: # no need to disambiguate this slot constructed_args.extend(poss) elif i == 0: # this is the actor of the action. don't need to disambiguate constructed_args.append(poss[0]) else: if disamb_more: constructed_args.append( __construct_pattern( poss, [sp] * len(poss), # len maybe hack? subparsers, to_replace, next_var)) else: to_replace[var] = poss subparsers[var] = sp constructed_args.append(VarPattern(var)) return type(results[0])(*constructed_args) to_replace = dict() subparsers = dict() return Ambiguous( __construct_pattern(results, supdata, subparsers, to_replace, ['a']), to_replace, subparsers) def copy(self): newparser = Parser() newparser.KNOWN_WORDS = list(self.KNOWN_WORDS) for name, table in self.subparsers.iteritems(): newparser.subparsers[name] = table.copy() newparser.parse_thing = self.parse_thing.copy() newparser.object_classes = self.object_classes.copy() return newparser def make_documentation(self, escape, heading_level=1): hls = str(heading_level) shls = str(heading_level + 1) print "<h" + hls + ">Parser</h" + hls + ">" print "<p>This is the documentation for the parser.</p>" for spn in self.subparsers.iterkeys(): print "<h" + shls + ">" + escape(spn) + "</h" + shls + ">" self.subparsers[spn].make_documentation( escape, heading_level=heading_level + 2)