Browse Source

Improved validation and somewhat nicer file output. Can use *.qproj file

netuttki 11 months ago
parent
commit
265cccce79
1 changed files with 296 additions and 252 deletions
  1. 296 252
      tools/callvalidator.py

+ 296 - 252
tools/callvalidator.py

@@ -3,126 +3,38 @@
 # tries to determine whether calls like gt 'location', 'event' are valid or not.
 
 from os import listdir
-from os.path import isfile, join
+from os.path import join
 import sys
 import re
-import io
-import time
+from io import open
+from time import perf_counter_ns
 import xml.etree.ElementTree as ET
+from textwrap import wrap
+
+runtime = [0,0,0,0,0,0,0,0,0,0]
+runtime[0] = perf_counter_ns()
 
 idir = ''
-callList = []
+validLocationCalls = []
+locationFileList = []
+missingLocations = []
 locationCallList = []
-neverCalledLocations = []
 callFileList = []
-calledFileList = []
+calledLocationList = []
 validate = 'all'
+locationsToIgnore = ['boystat', 'exp_gain']
+fileToIgnore = ["%s.qsrc" % loc for loc in locationsToIgnore]
 
-def loadValidationList(validationListFile):
-    result = []
-    try:
-        tree = ET.parse(validationListFile)
-        root = tree.getroot()
-        for file in root.iter('File'):
-            result.append(join(file.attrib['folder'], file.attrib['name']))
-            return result
-    except:
-        pass
-    #endtry
-
-    try: 
-        with io.open(validationListFile, 'r', encoding='utf-8') as ifile:
-            lines = ifile.readlines()
-            index = 1
-            for line in lines:
-                temp = line.strip(" \r\n\t").split(";")
-                file = temp[0].strip(" \t")
-                folder = idir
-                if ".qsrc" not in file:
-                    raise SyntaxError("Incorrect file content: '<filename>[; <folder>] expected, found %s." % (index, line),(validationListFile, index, 1, 'if ".qsrc" not in temp[0].strip(" \t"):', index, len(line)))
-                if len(temp) == 2:
-                    folder = temp[1].strip(" \t")
-                result.append(join(folder,file))
-                index += 1
-            return result
-    except SyntaxError as e:
-        raise SystemExit("Invalid list filed: %s" % e.msg)
-    #endtry
-#enddef
-
-
-def functionEmptyOrNamed(callArray):
-    if len(callArray) < 3:
-        return ''
-    else:
-        return callArray[2].strip(" \t\n")
-    #endif
-#enddef
-
-def processLines(lines, fileName):
-    findPattern = "(gt|gs|xgt|xgs)\s*('|\")\w+('|\")(,\s*('|\")\w+('|\"))?"
-    blockEndPattern = "[^}]*}$"
-    commentBlock = False
-    lineNo = 0
-    locationCalls = {"calllocation": lines[0].strip(' #\n'), "fileName": fileName, "calls": []}
-    for line in lines:
-        line = line.strip(" \t\n")  
-        if (not commentBlock) and line.strip("!").startswith("{"):
-            commentBlock = True
-        if commentBlock and (line == '}' or re.search(blockEndPattern, line) != None):
-            commentBlock = False
-        if not commentBlock and not line.startswith("!"): 
-            match = re.search(findPattern, line)
-            if match != None:
-                temp = match.group().replace('\t', '').replace(' ', '').replace("','","|").replace('","','|').strip(" '\n").replace("'","|").replace('"','|').split('|')
-                temp = [t.strip(" '\"") for t in temp]
-                if temp[1].lower() not in ['boystat', 'exp_gain']:
-                    calltype = temp[0]
-                    loc = temp[1]
-                    fun = functionEmptyOrNamed(temp)
-                    if ("%s.qsrc" % loc) not in calledFileList:
-                        calledFileList.append("%s.qsrc" % loc)
-                    calledLocation = {"location": loc, "function": fun, "valid": 0, "reason": "%s.qsrc doesn't exist." % loc}
-                    callLine = {"callId": 0, "calltype": calltype, "call": "%s '%s', '%s'" % (calltype, loc, fun), "lineNo": lineNo} 
-                    if calledLocation not in callList:
-                        callList.append(calledLocation)
-                    callLine['callId'] = callList.index(calledLocation)    
-                    locationCalls['calls'].append(callLine) 
-                #endif
-            #endif
-        #endif
-        lineNo += 1
-    #endfor
-
-    locationCallList.append(locationCalls)
-#enddedf 
-
-def validateCalls(lines):
-    calls = [c for c in callList if c['location'].lower() == lines[0].strip(' #\n').lower()]
-    
-    if validate == 'all' and (calls == None or len(calls) == 0):
-        neverCalledLocations.append(lines[0].strip(' #\n').lower())
+commentLine = "^(\s*!+)"
+blockCommentStart = "(^\s*!+.*{)"
+blockCommentEnd = "(}[^{]*$)"
+blockComment = False
+callLinePattern = "(gt|gs|xgt|xgs)(?:\s*(?:'|\"))(\w+)(?:'|\")(?:(?:,\s*)(?:'|\")(\w+)(?:'|\"))?"
+findCallLine = "(?:^\s*!+.*{)|(?:^[^!]*)(gt|gs|xgt|xgs)\s*(?:'|\")(\w+)(?:'|\")(?:(?:,\s*)(?:'|\")(\w+)(?:'|\"))?|(?:}[^{]*$)"
+calledLinePattern = "(?:^\s*!+.*{)|(?:(?:if|elseif)?\s*\(?\$?(?:ARGS|args)\[(?:0|i)\])(?:\s*(?:=|!|<|>|>=|=>|<=|=<)\s*(?:'|\"))(\w+)(?:'|\")|(?:}[^{]*$)"
+findLocationValues = "(?:(?:if|elseif)?\s*\(?\$?(?:ARGS|args)\[(?:0|i)\])(?:\s*(?:=|!|<|>|>=|=>|<=|=<)\s*(?:'|\"))(\w+)(?:'|\")"
 
-    for call in calls:
-        if call['function'] == 'start' or call['function'] == 'Start' or call['function'] == '':
-            call['valid'] = 1
-            call['reason'] = ""
-        else:
-            call['reason'] = "No $ARGS[0] or ARGS[i] expecting '%s'" % call['function']
-            findPattern = "\$*(args|ARGS)\[(0|i)\]\s*=\s*('|\")%s('|\")" % call['function']
-            for line in lines:
-                match = re.search(findPattern, line)
-                if match != None:
-                    call['valid'] = 1
-                    call['reason'] = ''
-                    break
-            #endfor
-        #endif
-    #endfor
-#enddef
-runtime = [0,1,2,3,4,5,6,7,8,9,10,11,12]
-runtime[0] = time.perf_counter_ns()
-runtime_text = ["Setup","Build callfile","Build call list","Index call list","Validate calls","Validation finished","Build report [invalid calls]","Sort invalid calls","Build report [location list]","Sort location list","Generate report file","Build report file","Report finished, all done"]
+runtime_text = ["Setup started","Started callfile build","","","Sorting and ordering the lists for the files","","All is finished"]
 
 assert (len(sys.argv) == 2 or len(sys.argv) == 3 or len(sys.argv) == 4), "usage:\ncallvalidator.py source=<src_input_dir> [file=<filename> | list=<listfilename>] [folder=<folder>]"
 
@@ -134,7 +46,8 @@ else:
     vdir = idir
 
 validationTarget = ''
-runtime[1] = time.perf_counter_ns()
+runtime[1] = perf_counter_ns()
+print("%s: %d: %d" % (runtime_text[0], runtime[1], runtime[1]-runtime[0] ))
 if len(sys.argv) > 2 and "file=" in str(sys.argv[2]):
     validationTarget = sys.argv[2].replace("file=", "")
     callFileList = [join(vdir, sys.argv[2].replace("file=", ""))]
@@ -142,159 +55,290 @@ if len(sys.argv) > 2 and "file=" in str(sys.argv[2]):
     
 if len(sys.argv) > 2 and "list=" in str(sys.argv[2]):
     validationTarget = sys.argv[2].replace("list=", "")
-    callFileList = loadValidationList(join(sys.argv[3].replace("folder=", ""), sys.argv[2].replace("list=", "")))
+    validationListFile = join(sys.argv[3].replace("folder=", ""), sys.argv[2].replace("list=", ""))
+    try:
+        tree = ET.parse(validationListFile)
+        root = tree.getroot()
+        for file in root.iter('Location'):
+            callFileList.append(join(file.attrib['folder'] if "folder" in file.attrib.keys() else idir, "%s.qsrc" % file.attrib['name'].replace('$', '_')))
+    except:
+        try: 
+            with open(validationListFile, 'r', encoding='utf-8') as ifile:
+                lines = ifile.readlines()
+                for index, line in enumerate(lines):
+                    temp = line.strip(" \r\n\t").split(";")
+                    file = temp[0].strip(" \t")
+                    if ".qsrc" not in file:
+                        raise SyntaxError()
+                        raise SyntaxError("Incorrect file content: '<filename>[; <folder>] expected, found %s." % line,(validationListFile, index, 1, 'if ".qsrc" not in temp[0].strip(" \t"):', index, len(line)))
+                    if len(temp) == 2:
+                        callFileList.append(join(temp[1].strip(" \t"),file))
+                    else:
+                        callFileList.append(join(idir,file))
+                #endfor
+            #endwith
+        except SyntaxError as e:
+            raise SystemExit("Invalid list filed: %s" % e.msg)
+        #endtry   
+    #endtry  
     validate = 'list'
+#endif <- "list="
 
 if validate == 'all':
     validationTarget = idir
     callFileList = [join(idir,f) for f in listdir(idir) if ".qsrc" in f]
-
-# build a list of all the calls happening
-runtime[2] = time.perf_counter_ns()
-for file in callFileList:
-    with io.open(file, 'rt', encoding='utf-8') as ifile:
+#endif
+
+# Build the list of valid calls
+locationFileList = [f.lower() for f in listdir(idir) if ".qsrc" in f and f.lower() not in fileToIgnore]
+runtime[2] = perf_counter_ns()
+runtime_text[2] = "Building valid calls list [%d files]" % len(locationFileList)
+print("%s: %d: %d" % (runtime_text[1], runtime[2], runtime[2]-runtime[1] ))
+for file in locationFileList:
+    with open(join(idir, file), 'rt', encoding='utf-8', newline="\n") as ifile:
         lines = ifile.readlines()
-        processLines(lines, ifile.name)
-    #endwith
-#endfor
-runtime[3] = time.perf_counter_ns()
-for call in callList:
-        call['callId'] = callList.index(call)
-
-runtime[4] = time.perf_counter_ns()
-# validating that all the calls are for valid locations
-for file in calledFileList:
-    if file not in ['boyStat.qsrc', 'exp_gain.qsrc']:
-        try:
-            with io.open(join(idir, file), 'rt', encoding='utf-8') as ifile:
-                lines = ifile.readlines()
-                validateCalls(lines)
-        except IOError:
-            pass
-        #endwith
-    #endif
-#endfor
-
-runtime[5] = time.perf_counter_ns()
-# create the call validity file and a list of files that call invalid locations
-timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
-txtname = 'call_validity [%s] - %s.txt' % (validationTarget, time.strftime('%Y%m%d%H%M%S', time.localtime()))
-htmlname = 'call_validity [%s] - %s.html' % (validationTarget, time.strftime('%Y%m%d%H%M%S', time.localtime()))
-
-callheads = []
-callIds = []
-invalidCalls = []
-noInvLoc = 0
-noNonExistingLoc = 0
-noInvFun = 0
-noInvCall = 0
-noLocNeverCalled = len(neverCalledLocations)
-
-runtime[6] = time.perf_counter_ns()
-currLoc = ''
-for call in callList:
-    if call['valid'] == 0:
-        if currLoc != call['location']:
-            noInvLoc += 1
-            if ".qsrc doesn't exist" in call['reason']:
-                noNonExistingLoc += 1
-        callIds.append(call['callId'])
-        invalidCalls.append(call)
-    #enfif
-#endfor
-runtime[7] = time.perf_counter_ns()
-invalidCalls = sorted(invalidCalls, key=lambda k: (k['location'].lower(), k['function'].lower()))
-noInvFun = len(invalidCalls)
-
-runtime[8] = time.perf_counter_ns()
-for locationCall in locationCallList:
-    calls = locationCall['calls']
-    Ids = [call['callId'] for call in calls] 
-
-    if any(x in Ids for x in callIds):
-        callheads.append(locationCall)
-    #endfor
-#endfor
-
-runtime[9] = time.perf_counter_ns()
-callheads = sorted(callheads, key=lambda k: (k['calllocation'].lower()))
-noInvCall = len(callheads)
-
-runtime[10] = time.perf_counter_ns()
-location = ''
-txtInvalidLines = []
-for call in invalidCalls:
-    if location != '' and location.lower() != call['location'].lower():
-        txtInvalidLines.append('\n')
+        location = lines[0].strip(' #\r\n').strip(' ').lower()
+        blockComment = False
+        for lineNo, line in enumerate(lines):
+            #getting locations
+            workline = line.strip(' \t\r\n')
+            locationsInLine = []
+            isCommentedLine = blockComment
+            match = re.search(calledLinePattern, workline)
+            if match != None:
+                potLine = re.findall(findLocationValues, workline)
+                if not blockComment and re.search(blockCommentStart, workline) != None: 
+                    blockComment = True
+                    isCommentedLine = True
+                if not blockComment and re.search(commentLine, workline) != None: isCommentedLine = True
+                if potLine != None: locationsInLine = potLine
+                if blockComment and re.search(blockCommentEnd, workline) != None: blockComment = False                    
+            #endif
 
-    txtInvalidLines.append("    '%s', '%s' : %s\n" % (call['location'], call['function'], call['reason']))
-    location = call['location']
+            for match in locationsInLine: 
+                record = {"location": location, "function": match, "isCommentLine": isCommentedLine}
+                recordInv = {"location": location, "function": match, "isCommentLine": not commentLine}
+                if record not in validLocationCalls and recordInv not in validLocationCalls: 
+                    validLocationCalls.append({"location": location, 
+                                            "function": match, 
+                                            "isCommentLine": isCommentedLine})
+                elif recordInv in validLocationCalls:
+                    # if commentLine of recordInv is TRUE then set it to false.
+                    # if commentLine of recordInv is FALSE then leave it alone
+                    if recordInv["isCommentLine"]:
+                        index = validLocationCalls.index(recordInv)
+                        validLocationCalls[index]['isCommentLine'] = False
+                #endif
+            #endfor <- match in locationsInLine
+        #endfor <- lineNo, line
+    #endwith
 #endfor
 
-txtLocationLines = []
-for head in callheads:            
-    txtLocationLines.append("  ---- %s [%s]:\n" % (head["calllocation"], head["fileName"]))
-    for call in head['calls']:
-        if call['callId'] in callIds:
-            target = callList[call['callId']]
-            txtLocationLines.append("    invalid call on line %04d: %s '%s', '%s'\t: %s\n" % (call['lineNo'], call['calltype'], target['location'], target['function'], target['reason']))
-        #endif
-    #endfor
-    txtLocationLines.append('\n')
-#endfor        
+# build a list of all the calls happening
+runtime[3] = perf_counter_ns()
+runtime_text[3] = "Building call list [%d files]" % len(callFileList)  
+print("%s: %d: %d" % (runtime_text[2], runtime[3], runtime[3]-runtime[2] ))
+for file in callFileList:
+    with open(file, 'rt', encoding='utf-8', newline="\n") as ifile:
+        lines = ifile.readlines()
 
-runtime[11] = time.perf_counter_ns()
-try:
-    with io.open(txtname, 'w', encoding='utf-8') as ofile: 
-        ofile.write("----- Summary -----\n")
-        ofile.write("  Locations called incorrectly    : %03d\n" % noInvLoc)
-        ofile.write("  Non-existing locations called   : %03d\n" % noNonExistingLoc)
-        ofile.write("  Number of incorrect calls       : %03d\n" % noInvFun)
-        ofile.write("  Locations making incorrect calls: %03d\n" % noInvCall)
-        if validate == 'all':
-            ofile.write("  Locations never called          : %03d\n" % noLocNeverCalled)
-        ofile.write("\n")
-        if validate == 'list':
-            ofile.write("----- Validated files -----\n")
-            if len(callFileList) > 10:
-                ofile.write("  The list contains %d files. Please see the list in %s.\n" % (len(callFileList), validationTarget))
-            else:                
-                for file in callFileList:
-                    ofile.write("  %s\n" % file)
+        location = lines[0].strip(' #\r\n').strip(' ')
+        for lineNo, line in enumerate(lines):
+            workline = line.strip(' \t\n\r')
+            match = None
+            temp_match = re.search(findCallLine, workline)
+            if temp_match != None and not [res for res in locationsToIgnore if res in workline.lower()]:
+                potLine = re.search(callLinePattern, workline)
+                if not blockComment and re.search(blockCommentStart, line) != None: blockComment = True
+                if not blockComment and potLine != None: match = potLine
+                if blockComment and re.search(blockCommentEnd, line) != None: blockComment = False                    
             #endif
-            ofile.write('\n')
-        #endif
-
-        ofile.write("----- List of Invalid calls -----\n")
-        ofile.write("\n")
-        
-        for line in txtInvalidLines:
-            ofile.write(line)
-
-        ofile.write('\n')
-
-        ofile.write("----- List of Locations and invalid calls they make -----\n")
-        ofile.write('\n')
-        
-        for line in txtLocationLines:
-            ofile.write(line)
-
-        if validate == 'all':
-            ofile.write("----- List of Locations that are never called -----\n")
-
-            for line in neverCalledLocations:
-                ofile.write("%s\n" % line)                    
-
+            
+            if match != None:
+                valid = 0
+                reason = ""
+                searchRecordF = {"location": match.group(2).lower(), "function": '' if match.group(3) == None else match.group(3), "isCommentLine": False}
+                searchRecordT = {"location": match.group(2).lower(), "function": '' if match.group(3) == None else match.group(3), "isCommentLine": True}
+                if searchRecordF['function'] == '':
+                    if searchRecordF not in validLocationCalls: validLocationCalls.append(searchRecordF)
+                    valid = 1
+                    reason = ""
+                elif "%s.qsrc" % searchRecordF['location'].lower() not in locationFileList:
+                    valid = -1
+                    reason = "Location `%s` doesn't exist - The file `%s.qsrc` was not found in the specified folder." % (searchRecordF['location'], searchRecordF['location'])
+                    if searchRecordF['location'] not in missingLocations: missingLocations.append(searchRecordF['location'])
+                elif searchRecordT in validLocationCalls:
+                    valid = -2
+                    reason = "`%s, %s` exists but is either commented out or is in a comment block" % (searchRecordT['location'], searchRecordT['function'])
+                elif searchRecordF in validLocationCalls:
+                    valid = 1
+                    reason = ""
+                else:
+                    valid = -3 
+                    reason = "Location `%s` exists but couldn't find any entrypoint expecting `%s` as $ARGS[0]. Please confirm whether this is a bug or not." % (searchRecordF['location'], searchRecordF['function'])
+                #endif    
+
+                locationCallList.append({"callinglocation": location, 
+                                        "file": ifile.name, 
+                                        "lineNo": lineNo, 
+                                        "calltype": match.group(1), 
+                                        "location": match.group(2).lower(), 
+                                        "function": '' if match.group(3) == None else match.group(3), 
+                                        "valid": valid, 
+                                        "reason": reason})    
+            #endif# <- Match != None
+        #endfor <- line in lines
     #endwith
-except IOError as e:
-    raise SystemExit("ERROR: call validity file was not created! REASON: %s" % e.strerror)
-#endtry_except
+#endfor
 
-runtime[12] = time.perf_counter_ns()
+runtime[4] = perf_counter_ns()
+print("%s: %d: %d" % (runtime_text[3], runtime[4], runtime[4]-runtime[3] ))
+
+missingLocations = sorted(missingLocations)
+validLocationCalls = sorted(validLocationCalls, key=lambda k: (k['location'].lower(), k['function']))
+invalidCallsMade = [call for call in locationCallList if call['valid'] < 0]
+invalidCallsMade = sorted(invalidCallsMade, key = lambda k: (k['callinglocation'].lower(), k['lineNo']))
+invalidLocations = []
+invalidCallsToMissingLocation = 0
+invalidCallsToCommentedCode = 0
+invalidCallsToMissingFunction = 0
+invalidLocationMissing = 0
+invalidCommentLine = 0
+invalidFunctionMissing = 0
+locationsMakingInvalidCalls = 0
 
-max = len(runtime)
-index = 0
-while index < max:
-    print("%s: %s" % (runtime_text[index], runtime[index]))
-    index += 1
+currLoc = ''
+for call in invalidCallsMade:
+    record = {"location": call['location'], "function": call['function'], "valid": call['valid'], "reason": call['reason']}
+    if call['callinglocation'] != '':
+        locationsMakingInvalidCalls += 1
+        currLoc = call['callinglocation']
+    if call['valid'] == -1: invalidCallsToMissingLocation += 1
+    if call['valid'] == -2: invalidCallsToCommentedCode += 1
+    if call['valid'] == -3: invalidCallsToMissingFunction += 1
+    if record not in invalidLocations: 
+        invalidLocations.append(record)
+        if record['valid'] == -1: invalidLocationMissing += 1
+        if record['valid'] == -2: invalidCommentLine += 1
+        if record['valid'] == -3: invalidFunctionMissing += 1
+invalidLocations = sorted(invalidLocations, key=lambda k: (k['location'].lower(), k['function'])) 
+
+runtime[5] = perf_counter_ns()
+print("%s: %d: %d" % (runtime_text[4], runtime[5], runtime[5]-runtime[4] ))
+
+with open('valid-calls-by-location.txt', 'w', encoding='utf-8') as ofile:
+    currLoc = ''
+    ofile.write("---- List of valid calls by location\n")
+    for call in validLocationCalls:
+        if call['location'] != currLoc:
+            ofile.write("\n")
+            ofile.write("  ---- %s -------------\n" % call['location'])
+            currLoc = call['location']
+        ofile.write("    '%s': Commented Line: %s\n" % (call['function'], call['isCommentLine']))
+
+with open('valid-calls-by-location.md', 'w', encoding='utf-8') as ofile:
+    ofile.write("## Valid Calls per Location")
+    currLoc = ''
+    for call in validLocationCalls:
+        if call['location'] != currLoc:
+            currLoc = call['location']
+            ofile.write("\n")
+            ofile.write("### %s" % currLoc)
+            ofile.write("\n")
+            ofile.write('| "Function" | Is Comment |\n')
+            ofile.write("| ---------- | ---------- |\n")
+        ofile.write("| `'%s'` | %s |\n" % (call['function'], call['isCommentLine']))
+    ofile.write("\n")
+
+with open('invalid-calls-missing-locations.txt', 'w', encoding='utf-8') as ofile:
+    
+    ofile.write("----- Summary ------------------------------------------\n")
+    ofile.write("  Locations called incorrectly         : {:>3}\n".format(len(invalidLocations)))
+    ofile.write("    Location/File doesn't exist        : {:>3} [{:>3.2f}%%]\n".format(invalidLocationMissing,(invalidLocationMissing/len(invalidLocations))*100))
+    ofile.write("    Commented out code                 : {:>3} [{:>3.2f}%%]\n".format(invalidCommentLine, (invalidCommentLine/len(invalidLocations))*100))
+    ofile.write("    Value not expected/handled         : {:>3} [{:>3.2f}%%]\n".format(invalidFunctionMissing, (invalidFunctionMissing/len(invalidLocations))*100))
+    ofile.write("--------------------------------------------------------\n")
+    ofile.write("  Locations making incorrect calls     : {:>3}\n".format(locationsMakingInvalidCalls))
+    ofile.write("  Total number of invalid calls        : {:>3}\n".format(len(invalidCallsMade)))
+    ofile.write("    Calls made to non-existing location: {:>3} [{:>3.2f}%%]\n".format(invalidCallsToMissingLocation, (invalidCallsToMissingLocation/len(invalidCallsMade))*100))
+    ofile.write("    Calls made to commented location   : {:>3} [{:>3.2f}%%]\n".format(invalidCallsToCommentedCode, (invalidCallsToCommentedCode/len(invalidCallsMade))*100))
+    ofile.write("    Calls made with unexpected value   : {:>3} [{:>3.2f}%%]\n".format(invalidCallsToMissingFunction, (invalidCallsToMissingFunction/len(invalidCallsMade))*100))
+    ofile.write("--------------------------------------------------------\n")
+    ofile.write("  Missing locations called             : {:>3}\n".format(len(missingLocations)))
+    ofile.write("--------------------------------------------------------\n")
+    ofile.write("\n")
+    ofile.write("\n")
+    ofile.write("{:^80}\n".format("---------- List of Invalid Calls by location ----------"))
+    currLoc = ''
+    for call in invalidLocations:
+        if call['location'] != currLoc:
+            ofile.write("\n")
+            ofile.write("  ---- %s -------------\n" % call['location'])
+            currLoc = call['location']
+        wrapis = wrap("  {:<34}: {}\n".format("'%s'" % call['function'], call['reason']), 125, subsequent_indent="{:<31}".format(' '), break_long_words=True)
+        for line in wrapis:           
+            ofile.write("%s\n" % line)
+    #endfor <- call in invalidLocations
+
+    ofile.write("\n")
+    ofile.write("{:^125}\n".format("---------- Calls made by location ----------"))
+    currLoc = ''
+    for call in invalidCallsMade:
+        if call['callinglocation'] != currLoc:
+            ofile.write("\n")
+            ofile.write("  ---- %s [%s]-------------\n" % (call['callinglocation'], call['file']))
+            currLoc = call['callinglocation']
+        ofile.write("Line {:>4}: {} '{}', '{}'\n".format(call['lineNo'], call['calltype'], call['location'], call['function']))
+        for line in wrap(call['reason'].replace("`", "'"), 90, initial_indent='    ', subsequent_indent='    ', break_long_words=True):
+            ofile.write("%s\n"%line)
+    #endfor <- call in invalidCallsMade
+
+    ofile.write("\n")
+    ofile.write("{:^80}\n".format("---------- List of Missing Locations ----------"))
+    
+    ofile.write("\n")
+    for location in missingLocations:
+        ofile.write("  {:<25} [{}] \n".format(location, join(idir, "%s.qsrc" % location)))
+#endwith
+
+with open('invalid-calls-missing-locations.md', 'w', encoding='utf-8') as ofile:
+    ofile.write("## List of Invalid Calls\n")    
+    currLoc = ''
+    for call in invalidLocations:            
+        if call['location'] != currLoc:
+            currLoc = call['location']
+            ofile.write("\n")
+            ofile.write("### %s" % currLoc)
+            ofile.write("\n")
+            ofile.write('| "Function" | Is Comment |\n')
+            ofile.write("| ---------- | ---------- |\n")
+        ofile.write("| `%s` | %s |\n" % (call['function'], call['reason']))
+    ofile.write("\n")
+    ofile.write("---\n")
+    ofile.write("\n")
+    ofile.write("## Calls made by locations\n")
+    currLoc = ''
+    for call in invalidCallsMade:
+        if call['callinglocation'] != currLoc:
+            currLoc = call['callinglocation']
+            ofile.write("\n")
+            ofile.write("### %s\n" % call['callinglocation'])
+            ofile.write("\n")
+            ofile.write("| File | Line No. | Call | Reason |\n")
+            ofile.write("| ---- | -------- | ---- | ------ |\n")
+        ofile.write("| {} | {:>4} | `{} '{}', '{}'` | {} |\n".format(call['file'], call['lineNo'], call['calltype'], call['location'], call['function'], call['reason']))
+    ofile.write("\n")
+    ofile.write("---\n")
+    ofile.write("\n")
+    ofile.write("## List of Missing Locations\n")
+    ofile.write("\n")
+    ofile.write ("| Location | File |\n")
+    ofile.write ("| -------- | ---- |\n")
+    for location in missingLocations:
+        ofile.write("| %s | %s |\n" % (location, join(idir, "%s.qsrc" % location)))
+#end
+
+runtime[6] = perf_counter_ns()
+print("%s: %d: %d" % (runtime_text[5], runtime[6], runtime[6]-runtime[5] ))
+print(runtime_text[6])