#!/usr/bin/env python
##
## BenchmarkFileParser.py
##      - superclass of individual parser classes
##

# $Id: BenchmarkFileParser.py,v 1.12 2007/01/18 22:20:59 jarda Exp $


# import of required modules {{{
import logging
import os.path
import sys
from StringIO import StringIO
from Plex import *

from Constants import Constants
from LinesProducingScanner import LinesProducingScanner
# }}}


# class BenchmarkFileParser() {{{
class BenchmarkFileParser(object):
    # DOC {{{
    """Superclass of individual parser classes.
    """
    # }}}


    # METHODS {{{
    
    # add format name parameter filled by subclass' constructors
    def __init__(self):
        # DOC {{{
        """Initializes some instance attributes, registers all benchmark
        attributes and creates the lexicon.
        """
        # }}}

        # CODE {{{
        # initialize the instance attributes {{{
        self.registeredBenchmarkAttributes = []
        self.attributeValues = {}
        # }}}

        # register the benchmark attributes
        self.setUpBenchmarkAttributes()

        # create the lexicon
        self.createLexicon()

        self.attributeValues = {}
        # }}}


    def createLexicon(self):
        # DOC {{{
        """Creates and returns a lexicon for the scanner.
        """
        # }}}

        # CODE {{{
        self.lexicon = Lexicon([])
        # }}}


    def createScanner(self, text):
        # DOC {{{
        """Creates and returns a scanner.

        Parameters

            text -- scanned text
        """
        # }}}

        # CODE {{{
        return LinesProducingScanner(self.lexicon, StringIO(text))
        # }}}


    def setUpBenchmarkAttributes(self):
        # DOC {{{
        """Registers all benchmark attributes.
        """
        # }}}

        # CODE {{{
        for benchmarkAttributeId in Constants.SUPPORTED_BENCHMARK_ATTRIBUTES:
            self.registeredBenchmarkAttributes.append(benchmarkAttributeId)
        # }}}


    def __getattr__(self, name):
        # DOC {{{
        """Fallback for the case that a support for a benchmark attribute is
        not included.

        Parameters

            name -- name of the requested attribute           
        """
        # }}}

        # CODE {{{
        if (name.startswith(Constants.GET_INITIAL_ATTRIBUTE_VALUE_PREFIX)):
            return self.__notImplementedDataFetcher

        elif (name.startswith(Constants.CALCULATE_ATTRIBUTE_DATA_PREFIX)):
            return self.__notImplementedDataFetcher
        # }}}


    def __notImplementedDataFetcher(self, *params):
        # DOC {{{
        """Fallback data fetching function.
        """
        # }}}

        # CODE {{{
        return Constants.BA_NOT_YET_IMPLEMENTED
        # }}}


    def calculateAttributeData_GATES_COUNT(self):
        # DOC {{{
        """Calculates number of all gates using numbers of gates of individual
        types.
        """
        # }}}

        # CODE {{{
        gatesCounts = [
            getattr(self, Constants.INTERNAL_BA_NAME[benchmarkAttributeId]) 
            for benchmarkAttributeId in Constants.GATES_COUNT_BENCHMARK_ATTRIBUTES
        ]

        return reduce(self.additionRegardingSpecialValues, gatesCounts)
        # }}}


    def parseBenchmarkFileContents(self, benchmarkFileContents):
        # DOC {{{
        """Parses contents of a benchmark file.

        Parameters

            benchmarkFileContents -- the contents of the benchmark file
        """
        # }}}

        # CODE {{{
        # NOTE: could eliminate comment lines...
        self.initializeAttributeValues()
        
        self.accumulateData(iter(self.tokenLinesGenerator(benchmarkFileContents)))
        
        # parsed benchmark attributes {{{
        for benchmarkAttributeId in self.registeredBenchmarkAttributes:
            if (Constants.IS_BA_CALCULATED[benchmarkAttributeId]):
                continue
            
            attributeName = Constants.INTERNAL_BA_NAME[benchmarkAttributeId]

            setattr(self, attributeName, self.attributeValues[benchmarkAttributeId])
        # }}}

        # calculated benchmark attributes {{{
        for benchmarkAttributeId in self.registeredBenchmarkAttributes:
            if (not Constants.IS_BA_CALCULATED[benchmarkAttributeId]):
                continue

            attributeName = Constants.INTERNAL_BA_NAME[benchmarkAttributeId]
            calculateAttributeDataMethodName = Constants.CALCULATE_ATTRIBUTE_DATA_PREFIX + attributeName
            calculateAttributeDataMethod = getattr(self, calculateAttributeDataMethodName)

            attributeValue = calculateAttributeDataMethod()
            setattr(self, attributeName, attributeValue)
        # }}}
        # }}}


    def initializeAttributeValues(self):
        # DOC {{{
        """Initializes the attribute values.
        """
        # }}}

        # CODE {{{
        for benchmarkAttributeId in self.registeredBenchmarkAttributes:
            attributeName = Constants.INTERNAL_BA_NAME[benchmarkAttributeId]
            methodName = Constants.GET_INITIAL_ATTRIBUTE_VALUE_PREFIX + attributeName
            method = getattr(self, methodName)

            self.attributeValues[benchmarkAttributeId] = method()
        # }}}


    def accumulateData(self, lineTokensIterator):
        # DOC {{{
        """Accumulates the data.

        Parameters

            lineTokensIterator -- iterator over the line tokens
        """
        # }}}

        # CODE {{{
        methods = []
        
        for benchmarkAttributeId in self.registeredBenchmarkAttributes:
            attributeName = Constants.INTERNAL_BA_NAME[benchmarkAttributeId]
            methodName = Constants.ACCUMULATE_LINE_PREFIX + attributeName
            if (hasattr(self, methodName) and (getattr(self, methodName) is not None)):
                methods.append(self.createAccumulateLineClosure(benchmarkAttributeId, methodName))

        for lineTokens in lineTokensIterator:
            for method in methods:
                if (method(lineTokens)):
                    continue
        # }}}


    def createAccumulateLineClosure(self, benchmarkAttributeId, methodName):
        # DOC {{{
        """Creates a closure over a specific accumulate line method. The closure
        supplies correct getValueMethod and setValue methods so the accumulation
        method can concentrate on its main task.

        Parameters

            benchmarkAttributeId -- ID of the accumulated benchmark attribute

            methodName -- name of the accumulation method
        """
        # }}}

        # CODE {{{
        # get the method by its name
        method = getattr(self, methodName)

        def getValueMethod():
            return self.attributeValues[benchmarkAttributeId]

        def setValueMethod(value):
            self.attributeValues[benchmarkAttributeId] = value

        def closureMethod(lineTokens):
            method(lineTokens, getValueMethod, setValueMethod)

        return closureMethod
        # }}}
        

    def getInitialAttributeValue_BUF_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of BUF GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_NOT_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of NOT GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_OR_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of OR GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_AND_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of AND GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_NAND_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of NAND GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_NOR_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of NOR GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_XOR_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of XOR GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_XNOR_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of XNOR GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_GENERAL_GATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of GENERAL GATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_STATES_COUNT(self):
        # DOC {{{
        """Returns default initial value of STATES COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_TERMS_COUNT(self):
        # DOC {{{
        """Returns default initial value of TERMS COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_TRANSITIONS_COUNT(self):
        # DOC {{{
        """Returns default initial value of TRANSITIONS COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_DFF_COUNT(self):
        # DOC {{{
        """Returns default initial value of DFF COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

    
    def getInitialAttributeValue_RESET_STATE(self):
        # DOC {{{
        """Returns default initial value of RESET STATE benchmark attribute.
        """
        # }}}

        # CODE {{{
        return Constants.BA_UNSUPPORTED
        # }}}

 
    def tokenLinesGenerator(self, benchmarkFileContents):
        # DOC {{{
        """Generates lines of tokens.

        Parameters

            benchmarkFileContents -- the contents of the benchmark file
        """
        # }}}

        # CODE {{{
        # create the scanner
        scanner = self.createScanner(benchmarkFileContents)
        
        # yield the token lines {{{
        for lineTokens in scanner.tokenLinesGenerator():
            yield(lineTokens)
        # }}}
        # }}}


    def __str__(self):
        # DOC {{{
        """Returns info string parsed from the benchmark file.
        """
        # }}}

        # CODE {{{
        return self.getParsedFileInfo()
        # }}}


    def getParsedFileInfo(self):
        # DOC {{{
        """Returns info string parsed from the benchmark file.
        """
        # }}}

        # CODE {{{
        lines = []

        for benchmarkAttributeId in self.getAttributesWithKnownValues():
            # get internal name of the benchmark attribute
            internalBAName = Constants.INTERNAL_BA_NAME[benchmarkAttributeId]

            # get the benchmark attribute value
            benchmarkAttributeValue = getattr(self, internalBAName)

            # get the readable name of the benchmark attribute
            readableBAName = Constants.READABLE_BA_NAME[benchmarkAttributeId]

            # append the line with the value of the attribute
            lines.append('%s: %s' % (readableBAName, 
                self.humanizeValue(benchmarkAttributeValue)))

        return '\n'.join(lines)
        # }}}


    def getAttributesWithKnownValues(self):
        # DOC {{{
        """Returns a dictionary whose keys are benchmark attribute IDs whose
        values are known and the values are the benchmark attribute values.
        """
        # }}}

        # CODE {{{
        # create the resulting dictionary
        resultingDictionary = {}
        
        # fill the dictionary {{{
        for benchmarkAttributeId in self.registeredBenchmarkAttributes:
            if (benchmarkAttributeId in Constants.INTERNAL_BENCHMARK_ATTRIBUTES):
                continue

            internalBAName = Constants.INTERNAL_BA_NAME[benchmarkAttributeId]
            benchmarkAttributeValue = getattr(self, internalBAName)

            if (benchmarkAttributeValue in Constants.UNSUCCESSFUL_BA_VALUES):
                continue

            resultingDictionary[benchmarkAttributeId] = benchmarkAttributeValue
        # }}}

        # return the resulting dictionary
        return resultingDictionary
        # }}}


    def humanizeValue(self, value):
        # DOC {{{
        """Returns human readable representation of the value.
        """
        # }}}

        # CODE {{{
        return Constants.HUMAN_READABLE_REPRESENTATION.get(value, value)
        # }}}


    def additionRegardingSpecialValues(firstValue, secondValue):
        # DOC {{{
        """Creates a sum of two values regarding some special cases.

        Parameters

            firstValue -- the first value

            secondValue -- the second value
        """
        # }}}

        # CODE {{{
        if ((firstValue == Constants.BA_NOT_YET_IMPLEMENTED) or
                (secondValue == Constants.BA_NOT_YET_IMPLEMENTED)):
            return Constants.BA_AMBIGUOUS_VALUE
        elif ((firstValue == Constants.BA_UNSUPPORTED) and
                (secondValue == Constants.BA_UNSUPPORTED)):
            return Constants.BA_UNSUPPORTED
        elif (firstValue == Constants.BA_UNSUPPORTED):
            return secondValue
        elif (secondValue == Constants.BA_UNSUPPORTED):
            return firstValue
        elif ((firstValue == Constants.BA_AMBIGUOUS_VALUE) or 
                (secondValue == Constants.BA_AMBIGUOUS_VALUE)):
            return Constants.BA_AMBIGUOUS_VALUE
        else:
            return firstValue + secondValue
        # }}}
    additionRegardingSpecialValues = staticmethod(additionRegardingSpecialValues)


    def getParserForFileName(fileName):
        # DOC {{{
        # }}}

        # CODE {{{
        formatName = BenchmarkFileParser.getExtensionWithoutDot(fileName)

        return BenchmarkFileParser.getParserForFormatName(formatName)
        # }}}
    getParserForFileName = staticmethod(getParserForFileName)


    def getExtensionWithoutDot(fileName):
        # DOC {{{
        """Returns file extension without the leading dot.

        Parameters

            fileName -- the file name
        """
        # }}}

        # CODE {{{
        # get extension withoutr the leading dot and return it
        return os.path.splitext(fileName)[1][1:]
        # }}}
    getExtensionWithoutDot = staticmethod(getExtensionWithoutDot)


    def getParserForFormatName(formatName):
        # DOC {{{
        """Select and returns a specific parser for a file name

        Parameters

            formatName -- the format name
        """
        # }}}

        # CODE {{{
        # get the lower cased format name
        lowerCaseName = formatName.lower()

        if (lowerCaseName not in Constants.EXT_TO_BENCHMARK_FORMAT):
            print "Unknown extension: '%s'" % (lowerCaseName,)
            sys.exit(1)

        # get the format ID
        benchmarkFormat = Constants.EXT_TO_BENCHMARK_FORMAT[lowerCaseName]

        # get the internal format name
        internalBenchmarkFormatName = Constants.INTERNAL_BENCHMARK_FORMAT_NAME[benchmarkFormat]

        # construct module name from the prefix and internal format name
        parserModuleName = Constants.BENCHMARK_FILE_PARSER_MODULE_PREFIX + internalBenchmarkFormatName
        
        # import the module
        parserModule = __import__(parserModuleName)

        # construct the class name
        parserClassName = Constants.BENCHMARK_FILE_PARSER_CLASS_PREFIX + internalBenchmarkFormatName

        # get the parser class
        parserClass = getattr(parserModule, parserClassName)

        return parserClass()
        # }}}
    getParserForFormatName = staticmethod(getParserForFormatName)


    def isBenchmarkFile(fileName):
        # DOC {{{
        """Tells whether a file with the specified name is a benchmark file -
        the test is based on the extension.

        Parameters

            fileName -- the file name
        """
        # }}}

        # CODE {{{
        extensionWithoutDot = BenchmarkFileParser.getExtensionWithoutDot(fileName)

        return (extensionWithoutDot.lower() in Constants.EXT_TO_BENCHMARK_FORMAT)
        # }}}
    isBenchmarkFile = staticmethod(isBenchmarkFile)

    
    # }}}
# }}}


# test {{{
if (__name__ == '__main__'):
    parser = BenchmarkFileParser()
    parser.parseBenchmarkFileContents('')
    print parser
# }}}
