#include-once
#include <File.au3>
#include <Array.au3>


If $CmdLine[0] Then
	Local $sFile = $CmdLine[1], $fAll = 0, $fString = 1
	Switch $CmdLine[0]
		Case 3
			$fAll    = $CmdLine[2]
			$fString = $CmdLine[3]
		Case 2
			$fAll    = $CmdLine[2]
	EndSwitch
Else
	Exit
EndIf

ConsoleWrite('+>' & @HOUR & ':' & @MIN & ':' & @SEC & ' Start: Get_Unused_Vars' & @CRLF)
Local $vReturn = _GetUnusedVarsConst($sFile, $fAll, $fString)
Local $error = @error
ConsoleWrite('+>' & @HOUR & ':' & @MIN & ':' & @SEC & ' Ende: Get_Unused_Vars' & @CRLF)
If @error Then ConsoleWrite('!Fehler: Datei "' & $sFile & ' existiert nicht.' & @CRLF)
If $fString = 0 Then
	_ArrayDisplay($vReturn)
Else
	ConsoleWrite($vReturn & @CRLF)
EndIf


;===============================================================================
; Function Name:   _GetUnusedVarsConst
; Description:     Ermittelt ungenutzte Variablen/Konstanten in einem Skript
;                  Optional können alle Variablen mit Anzahl der Vorkommen ermittelt werden
;                  Ignoriert werden:
;                            Variablen in Kommentarzeilen
;                            Variablen in Kommentarblöcken
;                            Zählervariablen (For-Schleife)
; Parameter(s):    $sFile    Pfad zur Skriptdatei
;                  $fAll     1 - Rückgabe aller Variablen; 0(Standard) - einmalige Variablen
;                  $fString  1(Standard) - Rückgabe in Konsole; 0 - Anzeige Array
; Return Value(s): Erfolg    Standard  Array/String mit nur einmalig auftauchender Variable
;                            Optional  Array/String mit allen Variablen
;                            Ausgabe:  Funktionsbereich - Variable - Anzahl - Position:Zeile(n)
;                  Fehler    0         @error = 1      $sFile nicht vorhanden
; Author(s):       BugFix (bugfix@autoit.de)
;===============================================================================
Func _GetUnusedVarsConst($sFile, $fAll=0, $fString=1)
	Local $oVars     = ObjCreate('Scripting.Dictionary')
	Local $oFuncVars = ObjCreate('Scripting.Dictionary')
	Local $aFuncs[1][2] = [[0]] ; [['Funktionsname','var1/Anz1/Zeile(1.),Zeile(2.)..' & '|' & 'var1/Anz1/Zeile(1.),Zeile(2.)..' & '|'  & 'var(n)/Anz(n)/Zeile(1.),Zeile(2.)..' ]]
	Local $aFile, $tmp, $aSplit, $aMatch, $inFunc = False, $counterFor = '', $1stChar, $iPos, $sSign, $fSet, $iSemicolon, $skip, $sFuncName
	If Not FileExists($sFile) Then Return SetError(1,0,0)
	_FileReadToArray($sFile, $aFile)
	For $i = 1 To $aFile[0]
		; Kommentarblöcke werden übersprungen
		; auskommentierte Zeilen werden übersprungen
		$1stChar = StringLeft(StringStripWS($aFile[$i],1), 1)
		If $1stChar = ';' Then ContinueLoop
		If (StringInStr($aFile[$i], '#cs') Or StringInStr($aFile[$i], '#comments-start')) Then $skip = True
		If (StringInStr($aFile[$i], '#ce') Or StringInStr($aFile[$i], '#comments-end')) Then $skip = False
		If $skip Then ContinueLoop
		Select
			Case StringLeft(StringStripWS($aFile[$i], 1), 4) = 'Func'
				; Beginn Funktionscode
				; Funktionsnamen auslesen u. in Func-Array eintragen
				; Funktionszähler +1
				$inFunc = True
				$aMatch = StringRegExp($aFile[$i], '^(?:[F|f]unc\s+)([_a-zA-Z0-9]+)', 1)
				$sFuncName = $aMatch[0]
				; Variablen aus Funktionsheader (auch mehrzeilig) auslesen und in $oFuncVars erfassen
				; steht eine Variable in einem Kommentar hinter dem Header wird sie ignoriert
				$aMatch = StringRegExp($aFile[$i], '(?:[F|f]unc\s+[\d\w]+\()[$,\w\s]*(?:\)?)', 1) ; Codeteil vor mgl. Kommentar oder '_'
				$aMatch = StringRegExp($aMatch[0], '(\$[\d\w]+)', 3)
				If IsArray($aMatch) Then __VarObjectAdd($oFuncVars, $aMatch, $counterFor, $i)
			Case StringLeft(StringStripWS($aFile[$i], 1), 7) = 'EndFunc'
				; Ende Funktionscode
				; in $oFuncVars gesammelte Variablen/Anzahl in $aFuncs übertragen
				; optional ($fAll=True) werden alle Variablen mit Anzahl Vorkommen übertragen
				; standardmäßig nur Variablen mit einmaligem Vorkommen
				If $oFuncVars.Count Then
					__SetToFuncArray($aFuncs, $oFuncVars, $fAll, $sFuncName)
					$oFuncVars.RemoveAll
				EndIf
				$inFunc = False
			Case Else ; Codezeilen innerhalb oder außerhalb einer Funktion, durch $inFunc markiert
				; prüfen auf For-Schleife, Selektion Zählervariable
				If StringLeft(StringStripWS($aFile[$i], 1), 3) = 'For' Then
					$aMatch = StringRegExp($aFile[$i], '(?:[F|f]or\s+)(\$[\d\w]+)', 1)
					$counterFor = $aMatch[0]
				EndIf
				; prüfen auf Ende For-Schleife, Zurücksetzen Zählervariable, nächste Zeile
				If StringLeft(StringStripWS($aFile[$i], 1), 4) = 'Next' Then
					$counterFor = ''
					ContinueLoop
				EndIf
				; prüfen ob ';' enthalten und wieviel
				StringReplace($aFile[$i], ';', '')
				$iSemicolon = @extended
				$fSet = False
				If $iSemicolon Then
					; Anzahl der ';' gibt keine Auskunft ob Kommentar od. Wertzuweisung
					; wenn Wertzuweisung, muß vor und nach dem ; ein ' oder " sein (Stringbegrenzer)
					; ist eines der Zeichen enthalten könnte es eine Wertzuweisung sein
					; es braucht nur das letzte Vorkommen geprüft werden, wenn nicht in Stringbegrenzern ist es Kommentar
					; wenn Kommentar - diesen Teil abtrennen
					; ABER: wenn letzter Stringbegrenzer vor Semikolon == mit einem Stringbegrenzer im Kommentar ==> Kommentar wird nicht erkannt!
					If StringInStr($aFile[$i], '"') Or StringInStr($aFile[$i], "'") Then ; sind Stringbegrenzer vorhanden
						$sSign = ''
						$iPos = StringInStr($aFile[$i], ';', 1, -1) ; letztes Vorkommen ermitteln
						$aSplit = StringSplit($aFile[$i], '')
						For $j = $iPos -1 To 1 Step -1 ; rückwärts iterieren ab Semikolon
							If $aSplit[$j] = '"' Or $aSplit[$j] = "'" Then
								$sSign = $aSplit[$j]
								ExitLoop
							EndIf
						Next
						; wenn Stringbegrenzer links gefunden
						If $sSign <> '' Then ; vorwärts iterieren ab Semikolon
							For $j = $iPos +1 To $aSplit[0]
								If $aSplit[$j] = $sSign Then ; davor und danach dasselbe ' od. " ==> also Zuweisung
									$fSet = True
									ExitLoop
								EndIf
							Next
						EndIf
					EndIf
					If Not $fSet Then ; ist Kommentar, nicht Zuweisung
						$aFile[$i] = StringLeft($aFile[$i], StringInStr($aFile[$i], ';', 1, -1) -1)
					EndIf
				EndIf
				; Variablen selektieren und an $oFuncVars oder $oVars übergeben
				$aMatch = StringRegExp($aFile[$i], '(\$[\d\w]+)', 3)
				If IsArray($aMatch) Then
					If $inFunc = True Then
						__VarObjectAdd($oFuncVars, $aMatch, $counterFor, $i)
					Else
						__VarObjectAdd($oVars, $aMatch, $counterFor, $i)
					EndIf
				EndIf
		EndSelect
	Next
	If $oVars.Count Then __SetToFuncArray($aFuncs, $oVars, $fAll)
	Local $aRet[1][4] = [['Funktionsbereich','Variable','Anzahl','Zeile(n)']], $aSplit2, $sRet = 'Funktionsbereich - Variable - Anzahl - Zeile(n)' & @CRLF
	For $i = 1 To UBound($aFuncs) -1
		$aSplit = StringSplit($aFuncs[$i][1], '|')
		For $j = 1 To $aSplit[0]
			$aSplit2 = StringSplit($aSplit[$j], '/')
			If @error Then ContinueLoop
			$sRet &= $aFuncs[$i][0] & ' - ' & $aSplit2[1] & ' - ' & $aSplit2[2] & ' - ' & $aSplit2[3] & @CRLF
			If $fString = 0 Then
				ReDim $aRet[UBound($aRet)+1][4]
				$aRet[UBound($aRet)-1][0] = $aFuncs[$i][0]
				$aRet[UBound($aRet)-1][1] = $aSplit2[1]
				$aRet[UBound($aRet)-1][2] = $aSplit2[2]
				$aRet[UBound($aRet)-1][3] = $aSplit2[3]
			EndIf
		Next
	Next
	If $fString = 0 Then Return $aRet
	Return $sRet
EndFunc  ;==>_GetUnusedVarsConst

Func __VarObjectAdd(ByRef $oDict, $aMatch, $counterFor, $line)
	Local $aSplit
	For $i = 0 To UBound($aMatch) -1
		If $aMatch[$i] = $counterFor Then ContinueLoop ; Zählervariablen werden nicht gezählt
		If $oDict.Exists($aMatch[$i]) Then
			$aSplit = StringSplit($oDict.Item($aMatch[$i]), '/')
			$oDict.Item($aMatch[$i]) = $aSplit[1] +1 & '/' & $aSplit[2] & ',' & $line
		Else
			$oDict.Add($aMatch[$i], 1 & '/' & $line)
		EndIf
	Next
EndFunc  ;==>__VarObjectAdd

Func __SetToFuncArray(ByRef $aFuncs, ByRef $oDict, $fAll, $sFuncName='')
	Local $aSplit, $colKeys, $sVarCount = ''
	$colKeys = $oDict.Keys
	For $key In $colKeys
		If $sFuncName <> '' Then
			ReDim $aFuncs[UBound($aFuncs)+1][2]
			$aFuncs[UBound($aFuncs)-1][0] = $sFuncName
		EndIf
		If $fAll = 0 Then
			If Not StringRegExp($oDict.Item($key), '(1/\d+)') Then ContinueLoop
		EndIf
		$aSplit = StringSplit($oDict.Item($key), '/')
		$sVarCount &= $key & '/' & $aSplit[1] & '/' & $aSplit[2] & '|'
		If $sFuncName <> '' Then
			$aFuncs[UBound($aFuncs)-1][1] = StringTrimRight($sVarCount, 1)
			$sVarCount = ''
		EndIf
	Next
	If $sFuncName = '' Then
		ReDim $aFuncs[UBound($aFuncs)+1][2]
		$aFuncs[UBound($aFuncs)-1][0] = 'Out_Of_Func'
		$aFuncs[UBound($aFuncs)-1][1] = StringTrimRight($sVarCount, 1)
	EndIf
EndFunc  ;==>__SetToFuncArray
