class Parser(): # 1.a. Define a class Parser. The constructor should take a filename # as argument, and do the following. Load a CFG from the file and # store it in the member grammar. Create a chart and store it in # the member chart def __init__( self, fname ): f = open( fname ).read() self.grammar = CFG.fromstring( f ) self.chart = Chart() self.chart.trace = True return # 1.b. Also define the method reset. It takes a sentence (a list of tokens) # as input, and stores it in the member words. It also resets the chart, # and sets the value of the memebr todo to the empty list. def reset( self, tokens ): self.chart = Chart() self.words = tokens self.todo = [] return # 2. Implement create_node and create_edge. They take the same arguments # as the corresponding Chart methods. Each should simply call the # corresponding Chart method, and append the resulting node or edge to # the todo list. (However, do not append None to the todo list.) def create_node(self, start, category, end, expansion): node = self.chart.create_node(start, (category), end, expansion) if node: self.todo.append(node) return def create_edge(self, edge_or_rule, child): edge = self.chart.create_edge(edge_or_rule, child) if edge: self.todo.append(edge) return # 3. Implement the shift method. Contrary to the handout, its argument # should be the index of the word to shift, i, and it should create nodes # spanning positions i to i + 1. Create one node for each part of speech # that the grammar assigns to words[i]. You may assume that the members # grammar, chart, and words are all appropriately set. def shift(self, i): foo = [] for rule in self.grammar.productions(): if(rule.rhs()[0] == self.words[i]): foo.append(rule.lhs()) j = i + 1 for bar in foo: self.create_node(i, bar, j, self.words[i]) return # 4. Implement the bu_predict method. It takes a Node as input. Let # X be the node's category. For each rule r whose righthand side begins # with X, call create_edge on r and the node def bu_predict(self, node): rules = self.grammar.productions(rhs=node.cat) for rule in rules: self.create_edge(rule, node) return # 5. Implement the extend_edges method. It takes a Node as input. It # iterates through the edge e taht end where the node begins, and if # the node's category is the same as the category after the dot in e, # then a new edge is created that combines e and the node. (Use create_edge.) def extend_edges(self, node): for e in self.chart.get_edges_at(node.i): if e.after_dot() == node.cat: self.create_edge(e, node) return # 6. Implement the complete method. It takes an Edge as input. For # safety, it should signal an error if the dot is not at the end. Create # a node corresponding to the lefthand side of the rule, with the edge as # its expansion, covering the same span as the edge. def complete(self, edge): if not edge.dot_at_end(): raise ValueError('Dot not at the end') else: self.create_node(edge.i, edge.rule.lhs(), edge.j, edge) # 7. The method next_task takes no input. It expects todo to be non-empty. # It removes the last item from todo and process it. If the item is a node, # it calls bu_predict and extend_edges on it, and if the item is an edge, # and the dot is at the end, it calls complete on it. def next_task(self): while len(self.todo) > 0: item = self.todo.pop() if type(item) == Node: self.bu_predict(item) self.extend_edges(item) elif type(item) == Edge and item.dot_at_end(): self.complete(item) return # 8. Implement the method fill_chart. It should call shift for each word, # and after each time it calls shift, it should call next_task repeatedly # until the todo list is empty def fill_chart(self): for num in range(len(self.words)): self.shift(num) while self.todo != []: self.next_task() # 9. Make the parser callable. When it is called as a function, it should take # a sentence ( that is, a list of tokens ) as input. It should call reset and # fill_chart. Then, if there is a node that spans the whole sentence and whose # category is the grammar's start symbol, it should return an iteration over the # tree of that node. Otherwise, it should return an empty iteration. Note: if # a method calls yield for some inputs but not others, the result will be an # empty iteration for the inputs where it never calls yield. def __call__(self, sentence): self.reset(sentence) self.fill_chart() new = self.chart.get_node(0, self.grammar.start(), 6).unwind() return new