Converted Symbol Table to a first-class object in its own right.
1 parent 14b4a7b commit 15df92e5d70f8e5c6b147a92c60f1acd9614a8e1
@tundra tundra authored on 13 Jan 2005
Showing 1 changed file
View
396
tconfpy.py
 
# Program Information
 
PROGNAME = "tconfpy"
RCSID = "$Id: tconfpy.py,v 1.186 2005/01/13 10:36:24 tundra Exp $"
RCSID = "$Id: tconfpy.py,v 1.187 2005/01/13 20:45:50 tundra Exp $"
VERSION = RCSID.split()[2]
 
# Copyright Information
 
# Global Variables & Data Structures #
#----------------------------------------------------------#
 
 
ALLOWNEWVAR = True # Allow new variable creation in cfg file
TEMPLATES = {} # Place to hold variable templates
TEMPONLY = False # Allow only template variable creation
LITERALVARS = False
DEBUG = False # Control Debug output
INLITERAL = False # Indicates we are currently in a literal block
 
DebugMsg = [] # Place to store and return debug info
ErrMsgs = [] # Place to store and return errors
WarnMsgs = [] # Place to store and return warnings
LiteralLines = [] # Place to store and return unprocessed lines
 
 
CondStack = [["", True,]] # Conditional stack
TotalLines = 0 # Total number of lines parsed
 
 
#####
# Dummy Object Used To Return Parsing Results
#####
 
# This is done so the caller only needs to know the name of
# each result set, not its position in the list of returned items
 
 
class RetObj(object):
 
def __init__(self):
self.SymTable = {}
self.Errors = []
self.Warnings = []
self.Debug = []
self.Literals = []
 
# End of 'RetObj'
 
 
##########
# Symbol Table
##########
 
# Symbol Table is a dictionary in the form:
##########
# Symbol Table Related Classes
##########
 
# Symbol Table is an object containing a dictionary in the form:
#
# {varname : descriptor}
#
# where the descriptor is an object with the following attributes
 
class SymbolTable(object):
 
Symbols = {}
DebugMsgs = []
ErrMsgs = []
WarnMsgs = []
LiteralLines = []
ALLOWNEWVAR = True
TEMPLATES = {}
TEMPONLY = False
LITERALVARS = False
INLITERAL = False
DEBUG = False
CondStack = [["", True],] # Always has one entry as a sentinel
TotalLines = 0
 
# End of class 'SymbolTable'
 
 
# The descriptor is an object with the following attributes
#
# Value, Writeable, Type, Default, LegalVals = [list of legal vals], Min, Max]
 
 
 
# End of class 'VarDescriptor'
 
 
# Initialize the table using the predefined symbols
 
SymTable = {}
 
 
SymTable = SymbolTable()
 
for var in Predefined.keys():
 
d = VarDescriptor()
d.Value = Predefined[var]
d.Writeable = False
SymTable[var] = d
SymTable.Symbols[var] = d
##########
# Error, Warning, And Debug Message Strings Stored In A Global Dictionary
##########
 
def DebugMsg(dmsg, args):
 
global DebugMsgs
global SymTable
DebugMsgs.append(mkmsg(Messages[dmsg] % args, dmsg))
SymTable.DebugMsgs.append(mkmsg(Messages[dmsg] % args, dmsg))
 
# End of 'DebugMsg()'
 
 
##########
 
def ErrorMsg(error, args):
 
global ErrMsgs
global SymTable
ErrMsgs.append(mkmsg(Messages[error] % args + "!", error))
SymTable.ErrMsgs.append(mkmsg(Messages[error] % args + "!", error))
 
# End of 'ErrorMsg()'
 
 
##########
 
def WarningMsg(warning, args):
 
global WarnMsgs
global SymTable
WarnMsgs.append(mkmsg(Messages[warning] % args + "!", warning))
SymTable.WarnMsgs.append(mkmsg(Messages[warning] % args + "!", warning))
 
# End of 'WarningMsg()'
 
 
#----------------------------------------------------------#
 
def ParseConfig(cfgfile,
CallingProgram=PROGINFO,
InitialSymTable={},
InitialSymTable=SymbolTable(),
AllowNewVars=True,
Templates={},
TemplatesOnly=False,
LiteralVars=False,
ReturnPredefs=True,
Debug=False):
 
global DebugMsgs, ErrMsgs, WarnMsgs, LiteralLines
global ALLOWNEWVAR, TEMPLATES, TEMPONLY, LITERALVARS, INLITERAL, DEBUG
global CondStack, SymTable, TotalLines
global SymTable
# Initialize the globals
 
DebugMsgs = []
ErrMsgs = []
WarnMsgs = []
LiteralLines = []
 
ALLOWNEWVAR = AllowNewVars
TEMPLATES = Templates
TEMPONLY = TemplatesOnly
LITERALVARS = LiteralVars
INLITERAL = False
DEBUG = Debug
 
CondStack = [["", True],] # Always has one entry as a sentinel
TotalLines = 0
 
# Set the name of the calling program for output messages
mkmsg.proginfo = CallingProgram
# Setup container to return parsing results
 
retobj = RetObj()
# Create a new symbol table
 
SymTable = SymbolTable()
 
# Initialize the globals
 
SymTable.ALLOWNEWVAR = AllowNewVars
SymTable.TEMPLATES = Templates
SymTable.TEMPONLY = TemplatesOnly
SymTable.LITERALVARS = LiteralVars
SymTable.INLITERAL = False
SymTable.DEBUG = Debug
 
 
# Add any passed symbols to the SymbolTable
 
deserror = False
for sym in InitialSymTable:
 
des = InitialSymTable[sym]
for sym in InitialSymTable.Symbols:
 
des = InitialSymTable.Symbols[sym]
 
# Make sure a valid descriptor was passed for each variable
 
desok = True
ErrorMsg("eDESCRIPTBAD", (eBADMINMAX, sym))
 
# Only load the symbol table with valid entries
if desok:
SymTable[sym] = des
SymTable.Symbols[sym] = des
 
# Indicate that a problem was encountered
else:
deserror = True
 
# If any of the passed symbols had bogus descriptor contents, we're done
 
if deserror:
 
retobj.SymTable = SymTable
retobj.Errors = ErrMsgs
retobj.Warnings = WarnMsgs
retobj.Debug = DebugMsgs
retobj.Literals = LiteralLines
return retobj
return SymTable
 
 
# Symbol Table passed to API was OK, so keep going
 
# Make sure the symbol table has a valid namespace
 
if NAMESPACE not in SymTable:
SymTable[NAMESPACE] = VarDescriptor()
SymTable[NAMESPACE].Value = ""
if NAMESPACE not in SymTable.Symbols:
SymTable.Symbols[NAMESPACE] = VarDescriptor()
SymTable.Symbols[NAMESPACE].Value = ""
 
# Otherwise, ensure that the initial namespace is properly formed.
# If not, revert to root namespace.
elif not ValidateSymbolName(SymTable[NAMESPACE].Value, STARTUP, STARTUP, AllowEmpty=True):
SymTable[NAMESPACE].Value = ""
elif not ValidateSymbolName(SymTable.Symbols[NAMESPACE].Value, STARTUP, STARTUP, AllowEmpty=True):
SymTable.Symbols[NAMESPACE].Value = ""
 
# Report namespace to debug output
 
if DEBUG:
DebugMsg("dNAMESPACE", (STARTUP, STARTUP, SymTable[NAMESPACE].Value))
if SymTable.DEBUG:
DebugMsg("dNAMESPACE", (STARTUP, STARTUP, SymTable.Symbols[NAMESPACE].Value))
 
 
# Parse the file
 
ParseFile(cfgfile, eSTARTUP, 0)
 
# Make sure we had all condition blocks balanced with matching '.endif'
 
finalcond = len(CondStack)
finalcond = len(SymTable.CondStack)
if finalcond != 1:
ErrorMsg("eENDIFMISS", (cfgfile, ATEOF, finalcond-1))
 
# Make sure we ended any literal processing properly
if INLITERAL:
if SymTable.INLITERAL:
WarningMsg("wENDLITMISS", (cfgfile, ATEOF))
 
# Return the parsing results
 
if DEBUG:
DebugMsg("dNUMLINES", (cfgfile, TotalLines))
if SymTable.DEBUG:
DebugMsg("dNUMLINES", (cfgfile, SymTable.TotalLines))
# Strip out Prefefined Variables if user does not want them
if not ReturnPredefs:
 
del SymTable[NAMESPACE]
del SymTable.Symbols[NAMESPACE]
for sym in Predefined:
del SymTable[sym]
 
 
retobj.SymTable = SymTable
retobj.Errors = ErrMsgs
retobj.Warnings = WarnMsgs
retobj.Debug = DebugMsgs
retobj.Literals = LiteralLines
 
return retobj
del SymTable.Symbols[sym]
 
 
return SymTable
 
 
# End of 'ParseConfig()'
 
 
# Prepend the current namespace unless we are in the top-level
# namespace.
 
elif SymTable[NAMESPACE].Value:
sym = "%s%s%s" % (SymTable[NAMESPACE].Value, NSSEP, sym)
elif SymTable.Symbols[NAMESPACE].Value:
sym = "%s%s%s" % (SymTable.Symbols[NAMESPACE].Value, NSSEP, sym)
 
# Handle environment variables
if sym and sym[0] == ENVIRO and sym[1:] in os.environ:
envvar = os.getenv(sym[1:])
line = line.replace(var, envvar)
 
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dVARREF", (cfgfile, linenum, var, envvar))
# Handle variables in symbol table
elif sym in SymTable:
symval = str(SymTable[sym].Value)
elif sym in SymTable.Symbols:
symval = str(SymTable.Symbols[sym].Value)
line = line.replace(var, symval)
 
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dVARREF", (cfgfile, linenum, var, symval))
 
# Reference to undefined variable
##########
 
def ParseFile(cfgfile, current_cfg, current_linenum):
 
global IgnoreCase, MsgList, SymTable, TotalLines
global MsgList, SymTable
 
 
linenum=0
 
 
# Process and massage the configuration file
for line in cf.read().splitlines():
linenum += 1
TotalLines += 1
SymTable.TotalLines += 1
 
# Parse this line
ParseLine(line, cfgfile, linenum)
 
##########
 
def ParseLine(line, cfgfile, linenum):
 
global CondStack, MsgList, SymTable, INLITERAL
global MsgList, SymTable
 
orig = line # May need copy of original for debug output
 
 
 
if line.strip() in (LITERAL, ENDLITERAL):
 
if line.strip() == LITERAL:
if INLITERAL:
if SymTable.INLITERAL:
WarningMsg("wLITEXTRA", (cfgfile, linenum))
else:
INLITERAL = True
SymTable.INLITERAL = True
 
# Process ENDLITERAL statements
else:
if not INLITERAL:
if not SymTable.INLITERAL:
WarningMsg("wENDLITEXTRA", (cfgfile, linenum))
else:
INLITERAL = False
 
 
if DEBUG:
SymTable.INLITERAL = False
 
 
if SymTable.DEBUG:
DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line))
 
return
 
# literal blocks. But, only if we're inside a True conditional
# block.
#####
 
if INLITERAL:
 
if CondStack[-1][1]:
 
if LITERALVARS:
if SymTable.INLITERAL:
 
if SymTable.CondStack[-1][1]:
 
if SymTable.LITERALVARS:
line, ref_ok = DerefVar(line, cfgfile, linenum)
 
LiteralLines.append(line)
 
if DEBUG:
SymTable.LiteralLines.append(line)
 
if SymTable.DEBUG:
DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line))
 
 
# Conditional block was false, so just throw away the line,
# noting in debug output if requested.
else:
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE))
 
return
 
# Line was blank
if not line:
 
# Note blank lines for debug purposes
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dBLANKLINE))
 
return
 
if line == ELSE:
 
# Get the enclosing block type and state
 
btyp, bst = CondStack.pop()
btyp, bst = SymTable.CondStack.pop()
 
# ELSE is only permitted after an immediately preceding IF form
 
 
if btyp != IF:
ErrorMsg("eELSENOIF", (cfgfile, linenum))
CondStack.append(["", False]) # Error makes all that follows False
SymTable.CondStack.append(["", False]) # Error makes all that follows False
 
# We *are* in an IF block an ELSE is appropriate.
# To determine whether the ELSE should be taken or not we have
# to look at the state of that IF block AND the state of
 
# If the containing block is True, the contained IF state is legitimate.
# The ELSE inverts the IF state in that case.
if CondStack[-1][1]:
CondStack.append([ELSE, not bst])
if SymTable.CondStack[-1][1]:
SymTable.CondStack.append([ELSE, not bst])
 
# The containing block is false, so everything within it is also false
 
else:
CondStack.append([ELSE, False])
SymTable.CondStack.append([ELSE, False])
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line))
 
return
 
 
if line == ENDIF:
 
# Remove one level of conditional nesting
CondStack.pop()
SymTable.CondStack.pop()
 
# Error, if there are more .endifs than conditionals
if not CondStack:
if not SymTable.CondStack:
ErrorMsg("eENDIFEXTRA", (cfgfile, linenum))
CondStack.append(["", False]) # Restore sentinel & inhibit further parsing
 
if DEBUG:
SymTable.CondStack.append(["", False]) # Restore sentinel & inhibit further parsing
 
if SymTable.DEBUG:
DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line))
 
return
 
#####
 
if FIRSTTOK == INCLUDE:
 
if not CondStack[-1][1]:
if DEBUG:
if not SymTable.CondStack[-1][1]:
if SymTable.DEBUG:
DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE))
return
 
line, ref_ok = DerefVar(line.split(INCLUDE)[1].strip(), cfgfile, linenum)
# Only attempt the include if all the variable dereferencing was successful
if ref_ok:
ParseFile(line, cfgfile, linenum)
 
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line))
 
return
 
# 3) We're dealing with a predefined variable
# 4) We're dealing with the NAMESPACE variable
# 5) The variable name is not escaped
 
ns = SymTable[NAMESPACE].Value
ns = SymTable.Symbols[NAMESPACE].Value
 
if ns and \
v[0] != ENVIRO and \
v not in Predefined and \
else:
v = "%s%s%s" % (ns, NSSEP, v)
 
# Produce debug ouput of actual variable name being checked
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dVAREXISTS", (cfgfile, linenum, v))
# Handle environment variables
if v[0] == ENVIRO:
if v[1:] in os.environ:
numexist += 1
 
# Handle local local variables
elif v in SymTable:
elif v in SymTable.Symbols:
numexist += 1
 
# And set the conditional state accordingly
 
 
# Set parser state based on a successful conditional test
# But it has to be ANDed with the state of the enclosing block
 
enclosing = CondStack[-1][1]
CondStack.append([IF, condstate and enclosing])
enclosing = SymTable.CondStack[-1][1]
SymTable.CondStack.append([IF, condstate and enclosing])
 
# Now reflect this in the parsed line
line = sTRUE
if not condstate:
line = sFALSE
 
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line))
 
return
 
# definition/assignment.
 
elif EQUAL in line:
 
if not CondStack[-1][1]:
if DEBUG:
if not SymTable.CondStack[-1][1]:
if SymTable.DEBUG:
DebugMsg("dLINEIGNORE", (cfgfile, linenum, orig, dNOTINCLUDE))
return
 
# Catch attempts to dereference without name anywhere in the line
 
if line.count(DELIML + DELIMR):
ErrorMsg("eSYMNONAME", (cfgfile, linenum))
 
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line))
 
return
# variables in the namespace munging below. IOW, Don't
# move this test down into the body of the variable name
# processing where namespace fiddling takes place.
 
elif l in SymTable and not SymTable[l].Writeable:
elif l in SymTable.Symbols and not SymTable.Symbols[l].Writeable:
ErrorMsg("eVARREADONLY", (cfgfile, linenum, l))
 
# Load variable into the symbol table
else:
 
# In all other cases prepend current namespace
else:
varname = l
ns = SymTable[NAMESPACE].Value
ns = SymTable.Symbols[NAMESPACE].Value
 
# Top level namespace variables don't need
# separator.
# If this is a newly defined variable, set its
# default to be this first value assigned and
# create the new entry
 
if l not in SymTable:
if l not in SymTable.Symbols:
 
# Only do this if new variable creation allowed
if ALLOWNEWVAR:
if SymTable.ALLOWNEWVAR:
 
# Rules for new variable creation:
#
# 1) If var has a template, use it
# 3) If var has no template, and TemplatesOnly=False -> Create new var
 
# Rule 1
if varname in TEMPLATES:
if varname in SymTable.TEMPLATES:
 
# Create the new variable
SymTable[l] = TEMPLATES[varname]
SymTable.Symbols[l] = SymTable.TEMPLATES[varname]
 
# Load the proposed value only if valid
 
if ValidateValue(l, r, cfgfile, linenum):
SymTable[l].Value = r
SymTable.Symbols[l].Value = r
# If value is invalid, remove this
# variable. We cannot create a new
# variable on an error. Errors are
# actually reported by the validation
# function.
 
else:
del(SymTable[l])
del(SymTable.Symbols[l])
 
# Rule 2
elif TEMPONLY:
elif SymTable.TEMPONLY:
ErrorMsg("eTEMPONLY", (cfgfile, linenum, varname))
 
# Rule 3
 
d = VarDescriptor()
d.Default = r
d.Value = r
SymTable[l] = d
SymTable.Symbols[l] = d
 
# New vars not allowed
else:
ErrorMsg("eVARNEW", (cfgfile, linenum))
# Translate various strings to their
# equivalent logical value for boolean
# variables first
 
if SymTable[l].Type == TYPE_BOOL:
if SymTable.Symbols[l].Type == TYPE_BOOL:
r = Booleans[r.capitalize()]
 
SymTable[l].Value = r
SymTable.Symbols[l].Value = r
 
# Produce debug output when we change namespaces
if DEBUG and l == NAMESPACE:
if SymTable.DEBUG and l == NAMESPACE:
DebugMsg("dNAMESPACE", (cfgfile, linenum, r))
 
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dPARSEDLINE", (cfgfile, linenum, orig, line))
 
return
 
if not ValidateSymbolName(r, cfgfile, linenum, AllowEmpty=True):
update = False # Validation function issues appropriate errors
 
des = SymTable[l]
des = SymTable.Symbols[l]
typ = des.Type
lv = des.LegalVals
low = des.Min
up = des.Max
 
# This can be kind of complex, so produce
# debug output here to help the poor user
 
if DEBUG:
if SymTable.DEBUG:
DebugMsg("dREGEXMATCH", (cfgfile, linenum, r, rex, l))
except:
ErrorMsg("eBADREGEX", (cfgfile, linenum, rex, l))
#----------------------------------------------------------#
 
 
__all__ = ["ParseConfig",
"RetObj",
"SymbolTable",
"TYPE_BOOL",
"TYPE_COMPLEX",
"TYPE_FLOAT",
"TYPE_INT",