'''
Created on 29-Jan-2016

@author: umair
'''

import base64, clang.cindex, collections, traceback
from subprocess import Popen, PIPE, STDOUT
from srcT.DataStruct import CompError

from srcT.Common import ConfigFile as CF, Helper as H
from clang.cindex import Config
from clang.cindex import Diagnostic

class Code:
    '''Constructor takes: base64 content and compilation error objects. AutoAddCEs => Add CEs using the TU (clang api) object'''
    index = clang.cindex.Index.create()
        
    def __init__(self, codeText, codeID=None, indent=False):
        '''Needs a codeText (plain text code)'''
        self.codeText = codeText
        self.codeID = codeID
        self.notIndented = indent # True => trigger self.codeIndent() on calling clangParse

        self.tu = None
        self.ces = []
    
    def codeIndent(self):
        '''Pretty format the student's codeText (multi statements into multi lines)'''
        p = Popen(['indent', '-linux'], stdout=PIPE, stdin=PIPE, stderr=PIPE)
        self.codeText,err = p.communicate(input=self.codeText.replace('\r','\n'))
        
        if err!='' and err!=None:
            pass 
            #print err
    
    def clangParse(self):
        if self.notIndented: 
            self.codeIndent()
            self.notIndented = False

        try:
            filename = 'temp.c'
            self.tu = Code.index.parse(filename, 
                args=CF.ClangArgs, unsaved_files=[(filename, self.codeText)])
        except Exception:             
            traceback.print_exc()
            print(self.codeText)
    
    def getTU(self):
        if self.tu == None:
            self.clangParse()
            self.addCEsTU()
        
        if self.tu is None: raise Exception('tu is None!')
        return self.tu
    
    def delTU_CEs(self):
        self.tu = None
        self.ces = [] 

    def getTokens(self):
        tu = self.getTU()
        if tu != None:
            return list(self.getTU().cursor.get_tokens())
        else: return None

    def getTokenLines(self):
        allTokens = self.getTokens()
        lineNum, index = 1, 0
        tempLine, tokenLines = [], []

        while index < len(allTokens):
            token = allTokens[index]
            if token.location.line == lineNum:
                tempLine.append(token)
                index += 1 
            else:
                tokenLines.append(tempLine)
                tempLine = []
                lineNum += 1

        if len(tempLine)!=0: # Add the leftover tokens
            tokenLines.append(tempLine)

        return tokenLines

    def getTokenSpellLines(self):
        tokenLines = self.getTokenLines()
        return [[token.spelling for token in line] for line in tokenLines]

    def getTokensAtLine(self, lineNum):
        fileTokens = self.getTokens()
        if fileTokens != None:
            return [t for t in fileTokens if t.location.line==lineNum]
        return None

    def getCEs(self):
        '''Get all compilation errors'''
        if len(self.ces) == 0:
            tu = self.getTU()
        
        return self.ces

    def getCE_lines(self):
        '''Get all line numbers, where compilation error occurred'''
        return list(set([ce.line for ce in self.getSevereErrors()]))


    def getCEsAtLine(self, lineNum):
        if self.getCEs() != None:
            return [ce for ce in self.ces if ce.line==lineNum]
        return None

    def addCEsTU(self):
        if len(self.ces) == 0 and self.tu != None:
            for diag in self.tu.diagnostics:
                ce = CompError.CompError()
                ce.initDiagnostics(diag)
                ce.findTokens(self.tu)
                
                self.ces.append(ce)
                #print ce
    
    def getSevereErrors(self):
        return [ ce for ce in self.getCEs()
                    if ce.severity == Diagnostic.Error or ce.severity == Diagnostic.Fatal
               ]
    
    def getWarnings(self):
        return [ce for ce in self.getCEs() if ce.severity == Diagnostic.Warning]

    def getNumErrors(self):
        return len(self.getSevereErrors())

    def getNumWarnings(self):
        return len(self.getWarnings())

    def cesToString(self):
        return '\n'.join([str(ce) for ce in self.getCEs()])
            
        
    def checkErrLineExists(self, givenCE):
        '''Check if the givenCE exists in this particular, at the level of line (ignore pos, just check for line and msg equivalence)'''
        for ce in self.getCEs():
            if ce.compareErrLine(givenCE):
                return True
        return False
    
    def compareErrsDiffLine(self, codeObj, lineNum):
        '''Given a lineNum, return #errsDiff btw self and codeObj at that lineNum'''
        ces1, ces2 = self.getCEs(), codeObj.getCEs()
        numErrSelf, numErrObj = 0,0
        if ces1 == None or ces2 == None:    return None
        
        lineSelfCEs = collections.defaultdict(lambda: [])
        for ce in self.ces: lineSelfCEs[ce.line].append(ce)
            
        lineObjCEs = collections.defaultdict(lambda: [])
        for ce in codeObj.ces: lineObjCEs[ce.line].append(ce)
        
        if lineNum in lineSelfCEs: numErrSelf = len(lineSelfCEs[lineNum])
        if lineNum in lineObjCEs: numErrObj = len(lineObjCEs[lineNum])
        
        return numErrSelf - numErrObj
            
         
    def compareErrs(self, codeObj):
        ces1, ces2 = self.getCEs(), codeObj.getCEs()
        numErr1, numErr2 = 0,0
        index1, index2 = 0,0
        flag = True
        if ces1 == None or ces2 == None:    return None
        
        while index1 < len(self.ces) or index2 < len(codeObj.ces):
            # Ignore note type of errors (present only in command line compile mode)        
            '''
            if  index1 < len(self.ces) and self.ces[index1].typeErr == 'note': 
                index1 += 1 
                continue             
            
            if index2 < len(codeObj.ces) and codeObj.ces[index2].typeErr == 'note':
                index2 += 1 
                continue
            '''
                        
            # No point in continuing compare if codeObj (or self) ran out of ces. Inc num errs & index (ordering imp - typeErr check needs to be above)
            if index1 >= len(self.ces):
                index2 += 1
                numErr2 += 1
                flag = False
                continue 
            
            if index2 >= len(codeObj.ces):
                index1 += 1
                numErr1 += 1
                flag = False
                continue
            
            if not self.ces[index1].compareErr(codeObj.ces[index2]):
                flag = False
                
            index1 += 1
            index2 += 1
            numErr1 += 1
            numErr2 += 1
            
        return flag, numErr1-numErr2
                                

'''
Created on 29-Jan-2016

@author: umair
'''

import base64, clang.cindex, collections, traceback
from subprocess import Popen, PIPE, STDOUT
from srcT.DataStruct import CompError

from srcT.Common import ConfigFile as CF, Helper as H
from clang.cindex import Config
from clang.cindex import Diagnostic

class Code:
    '''Constructor takes: base64 content and compilation error objects. AutoAddCEs => Add CEs using the TU (clang api) object'''
    index = clang.cindex.Index.create()
        
    def __init__(self, codeText, codeID=None, indent=False):
        '''Needs a codeText (plain text code)'''
        self.codeText = codeText
        self.codeID = codeID
        self.notIndented = indent # True => trigger self.codeIndent() on calling clangParse

        self.tu = None
        self.ces = []
    
    def codeIndent(self):
        '''Pretty format the student's codeText (multi statements into multi lines)'''
        p = Popen(['indent', '-linux'], stdout=PIPE, stdin=PIPE, stderr=PIPE)
        self.codeText,err = p.communicate(input=self.codeText.replace('\r','\n'))
        
        if err!='' and err!=None:
            pass 
            #print err
    
    def clangParse(self):
        if self.notIndented: 
            self.codeIndent()
            self.notIndented = False

        try:
            filename = 'temp.c'
            self.tu = Code.index.parse(filename, 
                args=CF.ClangArgs, unsaved_files=[(filename, self.codeText)])
        except Exception:             
            traceback.print_exc()
            print(self.codeText)
    
    def getTU(self):
        if self.tu == None:
            self.clangParse()
            self.addCEsTU()
        
        if self.tu is None: raise Exception('tu is None!')
        return self.tu
    
    def delTU_CEs(self):
        self.tu = None
        self.ces = [] 

    def getTokens(self):
        tu = self.getTU()
        if tu != None:
            return list(self.getTU().cursor.get_tokens())
        else: return None

    def getTokenLines(self):
        allTokens = self.getTokens()
        lineNum, index = 1, 0
        tempLine, tokenLines = [], []

        while index < len(allTokens):
            token = allTokens[index]
            if token.location.line == lineNum:
                tempLine.append(token)
                index += 1 
            else:
                tokenLines.append(tempLine)
                tempLine = []
                lineNum += 1

        if len(tempLine)!=0: # Add the leftover tokens
            tokenLines.append(tempLine)

        return tokenLines

    def getTokenSpellLines(self):
        tokenLines = self.getTokenLines()
        return [[token.spelling for token in line] for line in tokenLines]

    def getTokensAtLine(self, lineNum):
        fileTokens = self.getTokens()
        if fileTokens != None:
            return [t for t in fileTokens if t.location.line==lineNum]
        return None

    def getCEs(self):
        '''Get all compilation errors'''
        if len(self.ces) == 0:
            tu = self.getTU()
        
        return self.ces

    def getCE_lines(self):
        '''Get all line numbers, where compilation error occurred'''
        return list(set([ce.line for ce in self.getSevereErrors()]))


    def getCEsAtLine(self, lineNum):
        if self.getCEs() != None:
            return [ce for ce in self.ces if ce.line==lineNum]
        return None

    def addCEsTU(self):
        if len(self.ces) == 0 and self.tu != None:
            for diag in self.tu.diagnostics:
                ce = CompError.CompError()
                ce.initDiagnostics(diag)
                ce.findTokens(self.tu)
                
                self.ces.append(ce)
                #print ce
    
    def getSevereErrors(self):
        return [ ce for ce in self.getCEs()
                    if ce.severity == Diagnostic.Error or ce.severity == Diagnostic.Fatal
               ]
    
    def getWarnings(self):
        return [ce for ce in self.getCEs() if ce.severity == Diagnostic.Warning]

    def getNumErrors(self):
        return len(self.getSevereErrors())

    def getNumWarnings(self):
        return len(self.getWarnings())

    def cesToString(self):
        return '\n'.join([str(ce) for ce in self.getCEs()])
            
        
    def checkErrLineExists(self, givenCE):
        '''Check if the givenCE exists in this particular, at the level of line (ignore pos, just check for line and msg equivalence)'''
        for ce in self.getCEs():
            if ce.compareErrLine(givenCE):
                return True
        return False
    
    def compareErrsDiffLine(self, codeObj, lineNum):
        '''Given a lineNum, return #errsDiff btw self and codeObj at that lineNum'''
        ces1, ces2 = self.getCEs(), codeObj.getCEs()
        numErrSelf, numErrObj = 0,0
        if ces1 == None or ces2 == None:    return None
        
        lineSelfCEs = collections.defaultdict(lambda: [])
        for ce in self.ces: lineSelfCEs[ce.line].append(ce)
            
        lineObjCEs = collections.defaultdict(lambda: [])
        for ce in codeObj.ces: lineObjCEs[ce.line].append(ce)
        
        if lineNum in lineSelfCEs: numErrSelf = len(lineSelfCEs[lineNum])
        if lineNum in lineObjCEs: numErrObj = len(lineObjCEs[lineNum])
        
        return numErrSelf - numErrObj
            
         
    def compareErrs(self, codeObj):
        ces1, ces2 = self.getCEs(), codeObj.getCEs()
        numErr1, numErr2 = 0,0
        index1, index2 = 0,0
        flag = True
        if ces1 == None or ces2 == None:    return None
        
        while index1 < len(self.ces) or index2 < len(codeObj.ces):
            # Ignore note type of errors (present only in command line compile mode)        
            '''
            if  index1 < len(self.ces) and self.ces[index1].typeErr == 'note': 
                index1 += 1 
                continue             
            
            if index2 < len(codeObj.ces) and codeObj.ces[index2].typeErr == 'note':
                index2 += 1 
                continue
            '''
                        
            # No point in continuing compare if codeObj (or self) ran out of ces. Inc num errs & index (ordering imp - typeErr check needs to be above)
            if index1 >= len(self.ces):
                index2 += 1
                numErr2 += 1
                flag = False
                continue 
            
            if index2 >= len(codeObj.ces):
                index1 += 1
                numErr1 += 1
                flag = False
                continue
            
            if not self.ces[index1].compareErr(codeObj.ces[index2]):
                flag = False
                
            index1 += 1
            index2 += 1
            numErr1 += 1
            numErr2 += 1
            
        return flag, numErr1-numErr2
                                

Created on 29-Jan-2016  @author: umair Constructor takes: base64 content and compilation error objects. AutoAddCEs => Add CEs using the TU (clang api) object Needs a codeText (plain text code) # True => trigger self.codeIndent() on calling clangParse Pretty format the student's codeText (multi statements into multi lines) #print err # Add the leftover tokens Get all compilation errors Get all line numbers, where compilation error occurred #print ce Check if the givenCE exists in this particular, at the level of line (ignore pos, just check for line and msg equivalence) Given a lineNum, return #errsDiff btw self and codeObj at that lineNum # Ignore note type of errors (present only in command line compile mode) if  index1 < len(self.ces) and self.ces[index1].typeErr == 'note':                  index1 += 1                  continue                                       if index2 < len(codeObj.ces) and codeObj.ces[index2].typeErr == 'note':                 index2 += 1                  continue # No point in continuing compare if codeObj (or self) ran out of ces. Inc num errs & index (ordering imp - typeErr check needs to be above)