"""Definition of the ParserSyntaxError raised on parse failure"""
from simpleparse.stt.TextTools.TextTools import countlines

class ParserSyntaxError( SyntaxError ):
    """Sub-class of SyntaxError for use by SimpleParse parsers

    Every instance will have the following attributes:
        buffer -- pointer to the source buffer
        position -- integer position in buffer where error occured or -1
        production -- the production which failed
        expected -- string (currently taken from grammar) describing
            what production/element token failed to match
    the following will be calculated in order to display
    human-friendly error messages:
        line -- ~ text line-number or -1
        lineChar -- ~ character on line where parsing failed or -1
    
    """
    buffer = ""
    position = -1
    line = -1
    production = ""
    expected = ""
    error_message = None
    DEFAULTTEMPLATE = """Failed parsing production "%(production)s" @pos %(position)s (~line %(line)s:%(lineChar)s).\nExpected syntax: %(expected)s\nGot text: %(text)s"""
    def __str__( self ):
        """Create a string representation of the error"""
        if self.error_message:
            return '%s: %s'%( self.__class__.__name__, self.messageFormat(self.error_message) )
        else:
            return '%s: %s'%( self.__class__.__name__, self.messageFormat() )
    def messageFormat( self, template=None):
        """Create a default message for this syntax error"""
        if template is None:
            template = self.DEFAULTTEMPLATE
        line, lineChar = self.getLineCoordinate()
        variables = {
            "production": self.production,
            "position": self.position,
            "line": line,
            "lineChar": lineChar,
            "expected": self.expected or "UNKNOWN",
            "text": repr(self.buffer[ self.position:self.position+50 ]),
        }
        return template % variables
    def getLineCoordinate( self ):
        """Get (line number, line character) for the error"""
        lineChar = self.buffer.rfind('\n', 0, self.position)
        if lineChar == -1: # was no \n before the current position
            lineChar = self.position
            line = 1
        else:
            line = countlines( self.buffer[:lineChar] )
            lineChar = self.position-lineChar
        return line, lineChar
