Rev 167 | Blame | Compare with Previous | Last modification | View Log | RSS feed
#!/usr/bin/env python# Copyright: Ira W. Snyder# Start Date: 2005-11-18# End Date: 2005-11-30# License: Public Domain## Changelog Follows:## 2005-11-18# * Just getting the basics in place, since we haven't been given# the whole description of the project yet.## 2005-11-26# * Got the whole class working, now to implement pretty-printing## 2005-11-28# * This has pretty-printing now. (The derivation looks correct)# * Needs testing still, but for every case I can come up with, it works.## 2005-11-29# * Add error checking to the input.# * Add comments to all of the methods.## Check for <Python-2.3 compatibility (boolean values)try:True, Falseexcept NameError:(True, False) = (1, 0)import sys# Default SymbolsSYM_E = "A" # "E" -- These next few symbols were renamed to makeSYM_T = "B" # "T" -- output easier.SYM_P = "C" # "P"SYM_F = "F"SYM_N = "N"SYM_V = "V"SYM_D = "D"SYM_EPRM = "E'"SYM_TPRM = "T'"SYM_PPRM = "P'"SYM_EMPTY = ""class RecursiveDescentParser:"""An implementation of a Recursive Descent Parser for a simplealgebraic language, with both variables and numbers."""def __init__(self):self.__clear()def __clear(self):self.str = "" # the string of input to testself.strpos = 0 # the current position in strself.derivation = "" # the current state the grammar is indef __replace_sym(self, to_repl, with_this):"""Replace a string in the derivation with another string"""pos = self.derivation.find(to_repl)if pos == -1:#print 'str: %s' % (self.derivation, )#print 'to_repl: %s' % (to_repl, )#print 'with_this: %s' % (with_this, )raise ValueErrorself.derivation = self.derivation[:pos] + str(with_this) + self.derivation[pos+len(to_repl):]def __input_test_str(self):"""Read a test string from the user"""while len(self.str) <= 0:self.str = raw_input("input str: ")def main_menu(self):done = Falsewhile not done:print 'Menu:'print '========================================'print '1. Test a string'print '2. Quit's = raw_input('Choice >>> ')if s == '1':self.__clear()self.__input_test_str()self.__test_str()elif s == '2':done = Trueelse:print 'Bad Selection'def __print_deriv(self, with_arrow=True):"""Print out the derivation as it is at this moment, eitherwith or without the arrow."""if with_arrow:print "%s ->" % (self.derivation, ),else:print "%s" % (self.derivation, )def __test_str(self):"""Run test.str through the parser"""self.derivation = SYM_E[:] # set the initial symbol: EprocE_ans = self.procE()all_input_used = (len(self.str) == self.strpos)ans = procE_ans and all_input_usedself.__print_deriv(with_arrow=False)print 'Parsing: %s' % (ans and 'Completed' or 'Failed', )if not ans:print 'All input used: %s' % (all_input_used and 'True' or 'False', )############ NOTE: for all of the proc_X rules, see the grammar provided. They#### explain what is going on, I think. These functions are just direct#### implementations of the grammar.########def procE(self):self.__print_deriv()# Catch the exception generated below if we don't have enough# input to 'look ahead'try:self.__replace_sym(SYM_E, SYM_T + SYM_EPRM)return self.procT() and self.procEprm()except IndexError:print 'Not enough input to continue, failed.'return Falsedef procEprm(self):self.__print_deriv()# Check empty strif self.strpos >= len(self.str):self.__replace_sym(SYM_EPRM, SYM_EMPTY)return True# Check +if self.str[self.strpos] == '+':self.strpos += 1self.__replace_sym(SYM_EPRM, '+' + SYM_T + SYM_EPRM)return self.procT() and self.procEprm()# Check -if self.str[self.strpos] == '-':self.strpos += 1self.__replace_sym(SYM_EPRM, '-' + SYM_T + SYM_EPRM)return self.procT() and self.procEprm()# We accept empty str, so take itself.__replace_sym(SYM_EPRM, SYM_EMPTY)return Truedef procT(self):self.__print_deriv()self.__replace_sym(SYM_T, SYM_P + SYM_TPRM)return self.procP() and self.procTprm()def procTprm(self):self.__print_deriv()# Check empty strif self.strpos >= len(self.str):self.__replace_sym(SYM_TPRM, SYM_EMPTY)return True# Check *if self.str[self.strpos] == '*':self.strpos += 1self.__replace_sym(SYM_TPRM, '*' + SYM_P + SYM_TPRM)return self.procP() and self.procTprm()# we accept empty str, so take itself.__replace_sym(SYM_TPRM, SYM_EMPTY)return Truedef procP(self):self.__print_deriv()self.__replace_sym(SYM_P, SYM_F + SYM_PPRM)return self.procF() and self.procPprm()def procPprm(self):self.__print_deriv()# Check empty strif self.strpos >= len(self.str):self.__replace_sym(SYM_PPRM, SYM_EMPTY)return True# check exp symbolif self.str[self.strpos] == '^':self.strpos += 1self.__replace_sym(SYM_PPRM, '^' + SYM_F + SYM_PPRM)return self.procF() and self.procPprm()# we accept empty str, so take itself.__replace_sym(SYM_PPRM, SYM_EMPTY)return Truedef procF(self):self.__print_deriv()# Check xif self.str[self.strpos] == 'x':self.strpos += 1self.__replace_sym(SYM_F, 'x' + SYM_V)return self.procV()# Check (E)if self.str[self.strpos] == '(':self.strpos += 1self.__replace_sym(SYM_F, '(' + SYM_E + ')')if self.procE():if self.str[self.strpos] == ')':self.strpos += 1return Truereturn Falsereturn False# Must be a numberself.__replace_sym(SYM_F, SYM_N)return self.procN()def procN(self):self.__print_deriv()nums = range(100) # get 0-99nums.reverse() # make it 99-0for n in nums:if self.str[self.strpos:self.strpos+len(str(n))] == str(n):self.strpos += len(str(n))self.__replace_sym(SYM_N, str(n))return Truereturn Falsedef procV(self):self.__print_deriv()if self.str[self.strpos] in '0123456789':self.strpos += 1self.__replace_sym(SYM_V, self.str[self.strpos-1])return Truereturn Falseif __name__ == '__main__':rdp = RecursiveDescentParser()rdp.main_menu()