class Accept(Exception): "this exception is raised if a Grammar recognizes a string" def __init__(self, pda): Exception.__init__(self) self.pda = pda class Reject(Exception): "this exception is raised if a Grammar rejects a string" class Grammar: def __init__(self, vars, terms, start): """vars and terms are disjoint sets of strings; start is a member of terms. rules is a mapping from vars to lists of strings of terms and vars. """ self.vars = vars self.terms = terms self.start = start self.rules = dict() def add_rule(self, var, rhs): """add a new rule of the form '(var) can be a (rhs)'""" self.rules.setdefault(var, []).append(rhs) def process(self, string): """process the given string and print 'accept' or 'reject' """ try: self._process(string) except Accept, e: print 'accept', e.pda except Reject: print 'reject' def _process(self, string): """process the given string and raise Accept or Reject""" # initialize the queue with one PDA q = Queue() pda = PDA(self, string) q.push(pda) while q.not_empty(): # get the first PDA pda = q.pop() # process it and add the results to the queue for new in pda.step(): q.push(new) # if the queue is empty, that means we ran out of # viable PDAs, so we reject raise Reject import copy class PDA: def __init__(self, grammar, string): """grammar is a Grammar; string is the unprocessed part of the input string. stack is a Stack, which is initialized with None (as a marker for an empty stack) and the start variable. """ self.grammar = grammar self.string = string self.stack = Stack() self.stack.push(None) self.stack.push(grammar.start) def copy(self): """make a copy of this PDA""" return copy.deepcopy(self) def step(self): """perform one processing step on this PDA and return a list of viable PDAs""" top = self.stack.pop() # if the stack is empty if top == None: # and there is leftover input if self.string: # then this PDA is not viable return [] else: # if there is no more input, raise an # Accept object that contains a ref to this PDA raise Accept(self) # if the top of the stack is a terminal elif top in self.grammar.terms: # and there is no more input if self.string == '': # then this PDA is not viable return [] # if the terminal matches the input if top == self.string[0]: # then consume the input and return this PDA self.string = self.string[1:] return [self] else: # otherwise this PDA is not viable return [] # if the top of the stack is a variable elif top in self.grammar.vars: # return a list of PDAs, one for each rule return self.branch(top) else: raise Exception, "this shouldn't happen" def branch(self, top): """process a variable by creating one PDA for each rule""" pdas = [] # for each right hand side for rhs in self.grammar.rules[top]: # copy the current PDA pda = self.copy() # push the right hand side pda.stack.pushrhs(rhs) # add this PDA to the list pdas.append(pda) return pdas class Sequence: """the parent class for Stack and Queue""" def __init__(self): self.elts = [] def not_empty(self): return self.elts def push(self, elt): self.elts.append(elt) class Stack(Sequence): """a first-in last-out stack""" def pop(self): return self.elts.pop(-1) def pushrhs(self, seq): """add the symbols for the right hand side in reverse order""" for symbol in reversed(seq): self.push(symbol) class Queue(Sequence): """a first-in first-out queue""" def pop(self): return self.elts.pop(0) def main(): # create a grammar that recognizes strings with # nested pairs of 0 and 1 vars = set(['S', 'T']) terms = set(['0', '1']) start = 'S' g = Grammar(vars, terms, start) g.add_rule('S', 'TS') g.add_rule('S', '') g.add_rule('T', '0S1') # test Grammar.process g.process('01') g.process('0011') g.process('0101') g.process('001') g.process('00111') if __name__ == '__main__': main()