##
## BenchmarkFileParser_SLIF.py
##      - parser class for the SLIF format
##

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


# import of required modules {{{
import sys

from StringIO import StringIO

from Plex import *

from BenchmarkFileParser import BenchmarkFileParser
from ScannerConstants import ScannerConstants
from Constants import Constants
from LinesProducingScanner import LinesProducingScanner
# XXX: move the constants to Constants.py and remove the following import
from BenchmarkFileParser_BLIF import BenchmarkFileParser_BLIF
# }}}


# define all() if necessary {{{
def __all_(iterable):
    # DOC {{{
    # }}}

    # CODE {{{
    for element in iterable:
        if not element:
            return False
    return True
    # }}}


if ((sys.version_info[0] <= 2) and (sys.version_info[1] <= 5)):
    all = __all_
# }}}


# class BenchmarkFileParser_SLIF() {{{
class BenchmarkFileParser_SLIF(BenchmarkFileParser):
    # DOC {{{
    """Parser class for the BENCH format.
    """
    # }}}


    # METHODS {{{

    def createLexicon(self):
        # DOC {{{
        """Creates lexicon for the parser.
        """
        # }}}

        # CODE {{{
        self.lexicon = Lexicon([
            (Str('#') + Rep(AnyBut('\n')), ScannerConstants.T_COMMENT,),
            (Str('.inputs'), ScannerConstants.T_INPUTS,),
            (Str('.outputs'), ScannerConstants.T_OUTPUTS,),
            ScannerConstants.R_AT,
            ScannerConstants.R_LEFT_PARENTHESIS,
            ScannerConstants.R_RIGHT_PARENTHESIS,
            ScannerConstants.R_EQ_EQUAL_SIGN,
            ScannerConstants.R_PLUS_SIGN,
            ScannerConstants.R_COMA,
            ScannerConstants.R_DOT,
            ScannerConstants.R_LEFT_SQUARE_BRACE,
            ScannerConstants.R_RIGHT_SQUARE_BRACE,
            ScannerConstants.R_SINGLE_QUOTE,
            ScannerConstants.R_NEWLINE,
            ScannerConstants.R_IGNORE_SPACES_AND_TABS,
            (ScannerConstants.S_SLIF_SYMBOL, ScannerConstants.T_SYMBOL),
            ScannerConstants.R_SEMICOLON,
        ])
        # }}}
        
    
    def getInitialAttributeValue_INPUTS_COUNT(self):
        # DOC {{{
        """Returns initial value for INPUTS COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_INPUTS_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of INPUTS COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isInputsOrOutputsLine(lineTokens, ScannerConstants.T_INPUTS)):
            setValueMethod(getValueMethod() + len(lineTokens) - 2) # excluding .inputs and semicolon
        # }}}

    
    def getInitialAttributeValue_OUTPUTS_COUNT(self):
        # DOC {{{
        """Returns initial value for OUTPUTS COUNT benchmark attribute.
        """
        # }}}

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_OUTPUTS_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of OUTPUTS COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isInputsOrOutputsLine(lineTokens, ScannerConstants.T_OUTPUTS)):
            setValueMethod(getValueMethod() + len(lineTokens) - 2) # excluding .outputs and semicolon
        # }}}


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

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_DFF_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of DFF COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isDFFLine(lineTokens)):
            setValueMethod(getValueMethod() + 1)
        # }}}


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

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_GENERAL_GATES_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of GENERAL GATES COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isGateLine(lineTokens) and (self.getGateType(lineTokens) == BenchmarkFileParser_BLIF.GT_UNKNOWN)):
            setValueMethod(getValueMethod() + 1)
        # }}}


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

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_BUF_GATES_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of BUF GATES COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isGateLine(lineTokens) and (self.getGateType(lineTokens) == BenchmarkFileParser_BLIF.GT_BUF)):
            setValueMethod(getValueMethod() + 1)
        # }}}


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

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_NOT_GATES_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of NOT GATES COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isGateLine(lineTokens) and (self.getGateType(lineTokens) == BenchmarkFileParser_BLIF.GT_NOT)):
            setValueMethod(getValueMethod() + 1)
        # }}}


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

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_OR_GATES_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of OR GATES COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isGateLine(lineTokens) and (self.getGateType(lineTokens) == BenchmarkFileParser_BLIF.GT_OR)):
            setValueMethod(getValueMethod() + 1)
        # }}}


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

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_AND_GATES_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        """Accumulator method that updates value of AND GATES COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # DOC {{{
        """Accumulator method that updates value of AND GATES COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute

            setValueMethod -- method that sets value of the benchmark attribute
        """
        # }}}

        # CODE {{{
        if (self.isGateLine(lineTokens) and (self.getGateType(lineTokens) == BenchmarkFileParser_BLIF.GT_AND)):
            setValueMethod(getValueMethod() + 1)
        # }}}


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

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_NAND_GATES_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of NAND GATES COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isGateLine(lineTokens) and (self.getGateType(lineTokens) == BenchmarkFileParser_BLIF.GT_NAND)):
            setValueMethod(getValueMethod() + 1)
        # }}}


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

        # CODE {{{
        return 0
        # }}}

        
    def accumulateLine_NOR_GATES_COUNT(self, lineTokens, getValueMethod, setValueMethod):
        # DOC {{{
        """Accumulator method that updates value of NOR GATES COUNT benchmark attribute.

        Parameters

            lineTokens -- tokens from one line of the benchmark file

            getValueMethod -- method that returns the current value of the
                benchmark attribute
                
            setValueMethod -- method that sets value of the benchmark attribute        
        """
        # }}}

        # CODE {{{
        if (self.isGateLine(lineTokens) and (self.getGateType(lineTokens) == BenchmarkFileParser_BLIF.GT_NOR)):
            setValueMethod(getValueMethod() + 1)
        # }}}


    # XXX: asserts for wrong lines (in BLIF too)?
    # XXX: the current implementation does not correctly parse expressions with superfluous parentheses, e.g. (A) or (A)' - the outer ones can be eliminated in getExpressionTokens()
    def getGateType(self, lineTokens):
        # DOC {{{
        """Detects the gate type.

        Parameters

            lineTokens -- tokens from one line of the benchmark file
        """
        # }}}

        # CODE {{{
        expressionTokens = self.getExpressionTokens(lineTokens)

        if (len(expressionTokens) == 0):
            return BenchmarkFileParser_BLIF.GT_UNKNOWN

        if ((len(expressionTokens) == 1) and (expressionTokens[0][0] == ScannerConstants.T_SYMBOL)):
            return BenchmarkFileParser_BLIF.GT_BUF

        if ((len(expressionTokens) == 2) and (expressionTokens[0][0] == ScannerConstants.T_SYMBOL) and (expressionTokens[1][0] == ScannerConstants.T_SINGLE_QUOTE)):
            return BenchmarkFileParser_BLIF.GT_NOT

        if (all((token[0] == ScannerConstants.T_SYMBOL) for token in expressionTokens)):
            return BenchmarkFileParser_BLIF.GT_AND

        if (all((token[0] == ScannerConstants.T_SYMBOL) for token in expressionTokens[::2]) and
            all((token[0] == ScannerConstants.T_PLUS_SIGN) for token in expressionTokens[1::2])):
            return BenchmarkFileParser_BLIF.GT_OR

        if ((expressionTokens[0][0] == ScannerConstants.T_LEFT_PARENTHESIS) and
            (expressionTokens[-2][0] == ScannerConstants.T_RIGHT_PARENTHESIS) and
            (expressionTokens[-1][0] == ScannerConstants.T_SINGLE_QUOTE) and
            all((token[0] == ScannerConstants.T_SYMBOL) for token in expressionTokens[1:-2])):
            return BenchmarkFileParser_BLIF.GT_NAND
                 
        if ((expressionTokens[0][0] == ScannerConstants.T_LEFT_PARENTHESIS) and
            (expressionTokens[-2][0] == ScannerConstants.T_RIGHT_PARENTHESIS) and
            (expressionTokens[-1][0] == ScannerConstants.T_SINGLE_QUOTE) and
            all((token[0] == ScannerConstants.T_SYMBOL) for token in expressionTokens[1:-2:2]) and
            all((token[0] == ScannerConstants.T_PLUS_SIGN) for token in expressionTokens[2:-2:2])):
            return BenchmarkFileParser_BLIF.GT_NOR
                 
        return BenchmarkFileParser_BLIF.GT_UNKNOWN
        # }}}


    def isGateLine(self, lineTokens):
        # DOC {{{
        """Tells whether the line is a gate line.

        Parameters

            lineTokens -- tokens from one line of the benchmark file
        """
        # }}}

        # CODE {{{
        return ([token[0] for token in lineTokens[:2]] == [ScannerConstants.T_SYMBOL, ScannerConstants.T_EQUAL_SIGN,] and 
                ((lineTokens[2][0] != ScannerConstants.T_AT) or (lineTokens[3][1] == 'T')))
        # }}}


    def getExpressionTokens(self, lineTokens):
        # DOC {{{
        """Extracts and returns expression tokens from the SLIF expression
        line.
        
        Parameters

            lineTokens -- tokens from one line of the benchmark file
        """
        # }}}

        # CODE {{{
        return lineTokens[2:-1]
        # }}}


    def isDFFLine(self, lineTokens):
        # DOC {{{
        """Tells whether the line is a D flip-flop line.

        Parameters

            lineTokens -- tokens from one line of the benchmark file
        """
        # }}}

        # CODE {{{
        return (([token[0] for token in lineTokens[:4]] == [ScannerConstants.T_SYMBOL, ScannerConstants.T_EQUAL_SIGN, ScannerConstants.T_AT, ScannerConstants.T_TOKEN]) and (lineTokens[3][1] == 'D'))
        # }}}


    def getInputsOrOutputsCount(self, lineTokensIterator, startingToken):
        # DOC {{{
        """Returns inputs or outputs count from the proper token line.

        Parameters

            lineTokensIterator -- iterator over the line tokens

            startingToken -- the expected starting token
        """
        # }}}

        # CODE {{{
        count = 0
        
        for lineTokens in lineTokensIterator:
            if (lineTokens[0] == ScannerConstants.T_COMMENT_START):
                continue
            if (self.isInputsOrOutputsLine(lineTokens, startingToken)):
                count += len(lineTokens) - 2 # excluding .inputs (or outputs) and semicolon

        return count
        # }}}


    def isInputsOrOutputsLine(self, tokens, startingToken):
        # DOC {{{
        """Tells whether the line is an input or output line.
        """
        # }}}

        # CODE {{{
        return ((tokens[0][0] == startingToken) and
                (tokens[-1][0] == ScannerConstants.T_SEMICOLON) and
                self.areAllTokenSymbols(tokens[1:-1]))
        # }}}


    def areAllTokenSymbols(self, tokens):
        # DOC {{{
        """Tells whether all tokens are symbols.
        
        Parameters

            tokens -- a sequence of tokens
        """
        # }}}

        # CODE {{{
        return (not (len([tokenTuple for tokenTuple in tokens[1:-1] if (tokenTuple[0] != ScannerConstants.T_SYMBOL)])))
        # }}}


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

        Parameters

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

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

    
    # }}}
# }}}


# class SemicolonLinesProducingScanner() {{{
class SemicolonLinesProducingScanner(LinesProducingScanner):
    # DOC {{{
    """Tokens that produces line tokens for virtual lines that can spread
    across multiple physical rows of text and are terminated by a semicolon.
    """
    # }}}


    # METHODS {{{

    def allowLineBreak(self, tokens):
        # DOC {{{
        """If the first nonwhitespace token string begins with a comment sign
        (#), returns True; otherwise if there are uncommented nonwhitespace
        tokens and the last one is not semicolon, returns False;
        otherwise returns True.

        Parameters

            tokens -- a sequence of tokens
        """
        # }}}

        # CODE {{{
        lastNonSpaceToken = None

        for tokenTuple in tokens:
            if (tokenTuple[1][0] == '#'):
                if (lastNonSpaceToken is None):
                    return True
                elif (self.isSemicolonToken(lastNonSpaceToken)):
                    return True
                else:
                    return False
            elif (not self.isSpaceToken(tokenTuple)):
                lastNonSpaceToken = tokenTuple

        if ((lastNonSpaceToken is None) or self.isSemicolonToken(tokenTuple)):
            return True
        else:
            return False
        # }}}


    def isSemicolonToken(tokenTuple):
        # DOC {{{
        """Tells whether the token is a semicolon.

        Parameters

            tokenTuple -- the token tuple
        """
        # }}}

        # CODE {{{
        return (tokenTuple[1] == ';')
        # }}}
    isSemicolonToken = staticmethod(isSemicolonToken)
    
    
    def isSpaceToken(tokenTuple):
        # DOC {{{
        """Tells whether the token is a space or tab.

        Parameters

            tokenTuple -- the token tuple
        """
        # }}}

        # CODE {{{
        return ((tokenTuple[1][0] == ' ') or (tokenTuple[1][0] == '\t'))
        # }}}
    isSpaceToken = staticmethod(isSpaceToken)


    # }}}
# }}}
