unit UnitTextAnalyze; interface uses SysUtils, Classes, Windows, Forms; type TPawnParseResult = class public Constants: TStringList; Defined: TStringList; CVars: TStringList; Included: TStringList; MethodsDefault: TStringList; Events: TStringList; Stocks: TStringList; Natives: TStringList; Forwards: TStringList; Variables: TStringList; CallTips: TStringList; AutoComplete: TStringList; HighlightKeywords: TStringList; constructor Create; reintroduce; procedure DestroyResult; end; function ParseCodePawn(eCode: TStringList; FileName: string; IsRecursive: Boolean = False): TPawnParseResult; function UpdateIncPath(eInput: string): string; var eCPUSpeed: Integer = 1; implementation uses UnitCodeExplorerUpdater, UnitCodeUtils, UnitfrmSettings, UnitMainTools, UnitfrmMain; var eLookedUpIncluded: TStringList; function UpdateIncPath(eInput: string): string; begin eInput := StringReplace(Trim(eInput), '/', '\', [rfReplaceAll]); if ExtractFileExt(eInput) = '' then eInput := eInput + '.inc'; if FileExists(ExtractFilePath(frmSettings.txtPawnCompilerPath.Text) + eInput) then Result := ExtractFilePath(frmSettings.txtPawnCompilerPath.Text) + eInput else if FileExists(ExtractFilePath(frmSettings.txtPawnCompilerPath.Text) + 'include\' + eInput) then Result := ExtractFilePath(frmSettings.txtPawnCompilerPath.Text) + 'include\' + eInput else if (FileExists(ExtractFilePath(ActiveDoc.FileName) + eInput)) then Result := ExtractFilePath(ActiveDoc.FileName) + eInput else Result := ''; end; function ParseCodePawn(eCode: TStringList; FileName: string; IsRecursive: Boolean = False): TPawnParseResult; var i, k: integer; eString, eTemp, eBackup: string; eStr, ePreEvents: TStringList; eStartLine, eBracesOpen: Integer; eTimeToSleep: Integer; eAddingEnum: Integer; eTempResult: TPawnParseResult; eProcedureAdded: Boolean; eCActive: Boolean; eTempBool: Boolean; begin Result := TPawnParseResult.Create; if not IsRecursive then eLookedUpIncluded.Clear; eStr := TStringList.Create; ePreEvents := TStringList.Create; eBracesOpen := 0; eStartLine := -1; eTimeToSleep := 0; eAddingEnum := 0; eCActive := False; for i := 0 to eCode.Count - 1 do begin if (Application.Terminated) or (not Started) or (frmMain.pnlLoading.Visible) or (not frmMain.trvExplorer.Visible) then exit; eBackup := Trim(eCode[i]); eString := RemoveStringsAndComments(Trim(eCode[i]), True, True); if (Pos('/*', eBackup) = 1) or (Pos('*/', eBackup) <> 0) then begin eCActive := (Pos('/*', eBackup) = 1); if (eCActive) and (Pos('*/', eBackup) <> 0) then begin eCActive := False; continue end; end; if (eBackup = '') or (Pos('//', eBackup) = 1) or (eCActive) then continue; eProcedureAdded := False; Inc(eTimeToSleep, 1); if eTimeToSleep = eCPUSpeed then begin Sleep(1); eTimeToSleep := 0; end; { Constants and Variables } if (IsAtStart('new', eString, False)) or (IsAtStart('const', eString, False)) or (IsAtStart('stock', eString, False)) then begin // const or variable if (eBracesOpen = 0) and (not IsRecursive) and (Pos('(', eString) = Pos(')', eString)) then begin // we don't need braces so delete them... while (CountChars(eString, '{') <> 0) and (CountChars(eString, '}') <> 0) and (Pos('{', eString) < Pos('}', eString)) do eString := StringReplace(eString, '{' + Between(eString, '{', '}') + '}', '', [rfReplaceAll]); while (CountChars(eString, '[') <> 0) and (CountChars(eString, ']') <> 0) and (Pos('[', eString) < Pos(']', eString)) do eString := StringReplace(eString, '[' + Between(eString, '[', ']') + ']', '', [rfReplaceAll]); // done? okay, split all items if there are more than one; and if not, it's okay... eStr.Text := StringReplace(Copy(eString, Pos(#32, eString)+1, Length(eString)), ',', #13, [rfReplaceAll]); for k := 0 to eStr.Count - 1 do begin if (Trim(eStr[k]) <> '') and (eStr[k] <> '}') then begin eTemp := Trim(RemoveSemicolon(eStr[k])); if (IsAtStart('const', eTemp, False)) then begin Delete(eTemp, 1, 5); eTemp := Trim(eTemp); eTempBool := True; end else eTempBool := (IsAtStart('const', eString, False)); if (Pos(':', eTemp) <> 0) then eTemp := Copy(eTemp, Pos(':', eTemp) + 1, Length(eTemp)); if (Pos('=', eTemp) <> 0) then begin // constant Result.Constants.AddObject(Copy(eTemp, 1, Pos('=', eTemp) - 1), TObject(i)); Result.AutoComplete.Add(Copy(eTemp, 1, Pos('=', eTemp) - 1)); end else begin // variable if (eTempBool) then Result.Constants.AddObject(eTemp, TObject(i)) else Result.Variables.AddObject(eTemp, TObject(i)); Result.AutoComplete.Add(eTemp); end; end; end; eString := RemoveStringsAndComments(Trim(eCode[i]), True, True); continue; end; end; { Included } if (IsAtStart('#include', eBackup)) then begin eString := StringReplace(eBackup, '/', '\', [rfReplaceAll]); if Between(eString, '<', '>') <> '' then eString := Between(eString, '<', '>') else if Between(eString, '"', '"') <> '' then eString := Between(eString, '"', '"'); eString := Trim(eString); Result.Included.AddObject(eString, TObject(i)); // Recursive update if (eLookedUpIncluded.IndexOf(eString) = -1) then begin eLookedUpIncluded.Add(eString); eTemp := UpdateIncPath(eString); if (eString <> '') and (FileExists(eTemp)) then begin // Load code and parse eTempResult := nil; try eStr.LoadFromFile(eTemp); if Application.Terminated then exit; eTempResult := ParseCodePawn(eStr, ExtractFileName(eTemp), True); // Assign parsed values Result.AutoComplete.AddStrings(eTempResult.AutoComplete); Result.CallTips.AddStrings(eTempResult.CallTips); Result.HighlightKeywords.AddStrings(eTempResult.HighlightKeywords); // free except // mmmm.. burger end; if Assigned(eTempResult) then eTempResult.DestroyResult; // wait Sleep(20); end; end; continue; end; { CVars } if (IsAtStart('register_cvar', eString)) and (not IsRecursive) then begin if Between(eString, '"', '"') <> '' then Result.CVars.AddObject(Between(eBackup, '"', '"'), TObject(i)); continue; end; { Defined } if (IsAtStart('#define', eString)) then begin eString := Copy(eString, 8, Length(eString)); eString := Trim(eString); Result.CallTips.Add(eString + '-> ' + FileName + ', defined constant'); if Pos(#32, eString) <> 0 then eString := Copy(eString, 1, Pos(#32, eString) - 1); if Pos(' ', eString) <> 0 then eString := Copy(eString, 1, Pos(' ', eString) - 1); Result.Defined.AddObject(eString, TObject(i)); Result.AutoComplete.Add(eString); continue; end; { Events (Part 1) } if (IsAtStart('register_event(', eString)) and (not IsRecursive) then begin if CountChars(eBackup, '"') >= 4 then begin eTemp := StringReplace(eBackup, '"' + Between(eBackup, '"', '"') + '"', '', []); ePreEvents.Add(Between(eBackup, '"', '"')); end; continue; end; { Functions (1), this is adapted from AMXX-Edit v2 [see TextAnalyze.pas] } eBracesOpen := eBracesOpen + CountChars(eString, '{'); eBracesOpen := eBracesOpen - CountChars(eString, '}'); if Pos('{', eString) <> 0 then begin { Enums -> } if eAddingEnum = 1 then begin eAddingEnum := 2; Delete(eString, 1, Pos('{', eString) + 1); end else begin if eStartLine = -1 then begin eProcedureAdded := True; eStartLine := i; end; end; { <- Enums } end; if (Pos('}', eString) <> 0) then begin { Enums -> } if eAddingEnum <> 0 then eAddingEnum := 0; { <- Enums } if (eStartLine <> -1) then begin if (eBracesOpen = 0) and (not IsAtStart('new', Trim(eCode[eStartLine]))) then begin if Trim(RemoveStringsAndComments(eCode[eStartLine], True, True)) = '{' then eStartLine := eStartLine - 1; eTemp := Trim(RemoveSemicolon(Trim(eCode[eStartLine]))); // Analyze type k := 0; if IsAtStart('public', eTemp) then k := 1 else if IsAtStart('stock', eTemp) then k := 2 else if IsAtStart('native', eTemp) then k := 3 else if IsAtStart('forward', eTemp) then k := 4 else if Pos('enum', LowerCase(eTemp)) = 1 then // no method k := 5; // Remove type if Pos('@', eTemp) = 1 then begin eTemp := Copy(eTemp, 2, Length(eTemp)); k := 1; end else begin if (Pos(#32, eTemp) <> 0) and (Pos(#32, eTemp) < Pos('(', eTemp)) then eTemp := Copy(eCode[eStartLine], Pos(#32, eCode[eStartLine]) + 1, Length(eCode[eStartLine])) else if (Pos(#9, eTemp) <> 0) and (Pos(#9, eTemp) < Pos('(', eTemp)) then eTemp := Copy(eTemp, Pos(#9, eTemp) + 1, Length(eTemp)); end; if eTemp[Length(eTemp)] = '{' then eTemp := Trim(Copy(eTemp, 1, Length(eTemp) - 1)); // Remove return-type if (Pos(':', eTemp) <> 0) and (Pos(':', eTemp) < Pos('(', eTemp)) then Delete(eTemp, 1, Pos(':', eTemp)); if Pos('operator', eTemp) = 1 then k := 6; eTemp := RemoveSemicolon(eTemp); if k < 5 then begin case k of 0: Result.CallTips.Add(eTemp + '-> ' + FileName + ', function'); 1: Result.CallTips.Add(eTemp + '-> ' + FileName + ', public function'); 2: Result.CallTips.Add(eTemp + '-> ' + FileName + ', stock'); 3: Result.CallTips.Add(eTemp + '-> ' + FileName + ', native'); 4: Result.CallTips.Add(eTemp + '-> ' + FileName + ', forward'); end; end; // Copy function-name if Pos('(', eTemp) <> 0 then eTemp := Copy(eTemp, 1, Pos('(', eTemp) - 1); eTemp := Trim(eTemp); if (Result.AutoComplete.IndexOf(eTemp) <> -1) then begin eStartLine := -1; eBracesOpen := 0; continue; end; if k < 5 then begin Result.AutoComplete.Add(eTemp); Result.HighlightKeywords.Add(eTemp); end; if eTemp <> '' then begin case k of 0: begin if not IsRecursive then Result.MethodsDefault.AddObject(eTemp, TObject(eStartLine)); // Default Method end; 1: begin k := ePreEvents.IndexOf(eTemp); if k <> -1 then begin Result.Events.AddObject(eTemp, ePreEvents.Objects[k]); ePreEvents.Delete(k); end else Result.MethodsDefault.AddObject(eTemp, TObject(eStartLine)); end; 2: Result.Stocks.AddObject(eTemp, TObject(eStartLine)); 3: Result.Natives.AddObject(eTemp, TObject(eStartLine)); 4: Result.Forwards.AddObject(eTemp, TObject(eStartLine)); end; end; eStartLine := -1; eBracesOpen := 0; end; end; end else if (eAddingEnum = 2) and (Pos('enum', LowerCase(eString)) <> 1) then begin if Pos(' ', eString) <> 0 then eString := Copy(eString, 1, Pos(' ', eString) - 1); if Pos(',', eString) <> 0 then eString := Copy(eString, 1, Pos(',', eString) - 1); if Pos(' ', eString) <> 0 then eString := Copy(eString, 1, Pos(' ', eString) - 1); if Pos(':', eString) <> 0 then eString := Copy(eString, 1, Pos(':', eString) - 1); Result.AutoComplete.Add(eString); end; { Enums } if IsAtStart('enum', eString) then begin if Pos('{', eString) <> 0 then eAddingEnum := 2 // Add values immediately else eAddingEnum := 1; // Wait for next brace and add then end; { Functions (2) } if (IsAtStart('forward', eString)) or (IsAtStart('public', eString)) or (IsAtStart('native', eString)) or (IsAtStart('stock', eString)) then begin if (not eProcedureAdded) and (Pos('(', eString) <> 0) and (Pos(')', eString) <> 0) then begin eTemp := StringReplace(Trim(eBackup), #9, #32, [rfReplaceAll]); eTemp := Trim(RemoveSemicolon(eTemp)); if eTemp[Length(eTemp)] = '{' then eTemp := Trim(Copy(eTemp, 1, Length(eTemp) - 1)); // Remove type if (Pos(#32, eTemp) <> 0) and (Pos(#32, eTemp) < Pos('(', eTemp)) then eTemp := Copy(eTemp, Pos(#32, eTemp) + 1, Length(eTemp)); if (Pos(#9, eTemp) <> 0) and (Pos(#9, eTemp) < Pos('(', eTemp)) then eTemp := Copy(eTemp, Pos(#9, eTemp) + 1, Length(eTemp)); // Remove return-type if (Pos(':', eTemp) <> 0) and (Pos(':', eTemp) < Pos('(', eTemp)) then Delete(eTemp, 1, Pos(':', eTemp)); eTemp := RemoveSemicolon(eTemp); if (Pos('enum', eTemp) = Pos('operator', eTemp)) and (Pos('enum', eTemp) = 0) then Result.CallTips.Add(eTemp + '-> ' + FileName + ', ' + Trim(Copy(eString, 1, Pos(#32, eString) -1))); // Copy function-name if Pos('(', eTemp) <> 0 then eTemp := Copy(eTemp, 1, Pos('(', eTemp) - 1); eTemp := Trim(eTemp); if (Pos('enum', eTemp) = Pos('operator', eTemp)) and (Pos('enum', eTemp) = 0) then begin Result.AutoComplete.Add(eTemp); Result.HighlightKeywords.Add(eTemp); end; if eTemp <> '' then begin if IsAtStart('forward', eString) then Result.Forwards.AddObject(eString, TObject(i)) else if IsAtStart('public', eString) then begin k := ePreEvents.IndexOf(eTemp); if k <> -1 then begin Result.Events.AddObject(eTemp, ePreEvents.Objects[k]); ePreEvents.Delete(k); end else Result.MethodsDefault.AddObject(eTemp, TObject(i)); end else if IsAtStart('native', eString) then Result.Natives.AddObject(eTemp, TObject(i)) else if IsAtStart('stock', eString) then Result.Stocks.AddObject(eTemp, TObject(i)) else if (Pos('enum', eTemp) = Pos('operator', eTemp)) and (Pos('enum', eTemp) = 0) then Result.MethodsDefault.AddObject(eTemp, TObject(i)); end; end; end; end; ePreEvents.Free; eStr.Free; end; { TPawnParseResult } constructor TPawnParseResult.Create; begin inherited Create; Constants := TStringList.Create; Defined := TStringList.Create; CVars := TStringList.Create; Included := TStringList.Create; MethodsDefault := TStringList.Create; Events := TStringList.Create; Stocks := TStringList.Create; Natives := TStringList.Create; Forwards := TStringList.Create; Variables := TStringList.Create; CallTips := TStringList.Create; AutoComplete := TStringList.Create; HighlightKeywords := TStringList.Create; end; procedure TPawnParseResult.DestroyResult; begin AutoComplete.Free; CallTips.Free; Constants.Free; CVars.Free; Defined.Free; Events.Free; Forwards.Free; HighlightKeywords.Free; Included.Free; MethodsDefault.Free; Natives.Free; Stocks.Free; Variables.Free; Free; end; initialization eLookedUpIncluded := TStringList.Create; finalization eLookedUpIncluded.Free; end.