[IDE tool] Tidy

Working on new editor enhancements?
AZJIO
Addict
Addict
Posts: 2154
Joined: Sun May 14, 2017 1:48 am

[IDE tool] Tidy

Post by AZJIO »

Download

I'm making an analog of this tool.

Code: Select all

;- TOP
; AZJIO 2024
; DisableDebugger

EnableExplicit

;- ● #Constants
Enumeration
	#SYNTAX_Text
	#SYNTAX_Keyword
	#SYNTAX_Comment
	#SYNTAX_Constant
	#SYNTAX_String
	#SYNTAX_Function
	#SYNTAX_Asm
	#SYNTAX_Operator
	#SYNTAX_Structure
	#SYNTAX_Number
	#SYNTAX_Pointer
	#SYNTAX_Separator
	#SYNTAX_Label
	#SYNTAX_Module
EndEnumeration
; #SYNTAX_Text = 0
; #SYNTAX_Keyword = 1
; #SYNTAX_Comment = 2
; #SYNTAX_Constant = 3
; #SYNTAX_String = 4
; #SYNTAX_Function = 5
; #SYNTAX_Asm = 6
; #SYNTAX_Operator = 7
; #SYNTAX_Structure = 8
; #SYNTAX_Number = 9
; #SYNTAX_Pointer = 10
; #SYNTAX_Separator = 11
; #SYNTAX_Label = 12
; #SYNTAX_Module = 13

#Dll    = 0
#Input  = 0
#Output = 1


;- ● Declare
Declare.s LTrimChar(String$, TrimChar$ = #CRLF$ + #TAB$ + #FF$ + #VT$ + " ")
Declare CommentHandler(i)
Declare RemovingSpaceBeforeCRLF(i)


Structure Token
	s.s
	type.i
	len.i
EndStructure

;- ● Global
Global n
Global ArrSz = 1000
Global Dim Token.Token(ArrSz)


Global flgOpn, InputFile$, OutputFile$, *Buffer, Length, NotSuccess, g_Format

Define i, j, z, tmp, IsFind
Define StartTime


Procedure Callback(*Position, Length, Type)
	Protected tmp$, tmp2$, *c.Ascii
	If g_Format = #PB_Ascii
		Token(n)\s = PeekS(*Position, Length, #PB_Ascii)
	ElseIf g_Format = #PB_UTF8
		Token(n)\s = PeekS(*Position, Length, #PB_UTF8 | #PB_ByteLength)
	EndIf
	Token(n)\type = Type
	Token(n)\len = Length
	n + 1
	If n > ArrSz
		ArrSz * 2
		ReDim Token.Token(ArrSz) ; динамически увелиивает размер массива, чтобы вмещать последующие элементы
	EndIf
EndProcedure


;- ● обработка
If OpenLibrary(#Dll, #PB_Compiler_Home + "SDK\Syntax Highlighting\SyntaxHighlighting.dll")
	
; 	Добавляем параметоры ком строки чтобы использовать как инструмент
	If CountProgramParameters()
		InputFile$ = ProgramParameter(0)
		If Not (Asc(InputFile$) And FileSize(InputFile$) > 3 And Left(GetExtensionPart(InputFile$), 2) = "pb")
			InputFile$ = ""
		EndIf
	EndIf
	
	If Not Asc(InputFile$)
		InputFile$ = OpenFileRequester("Select PB File", "", "*.pb*|*.pb*|All Files|*.*", 0)
	EndIf
	
	If Asc(InputFile$)
		If MessageRequester("Перезаписать?", "Перезаписать текущий файл? (иначе в буфер обмена и файл с суффиксом _tidy)",
		                    #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
			OutputFile$ = InputFile$
			OutputFile$ = GetPathPart(InputFile$) + GetFilePart(InputFile$, #PB_FileSystem_NoExtension) + "_tidy." + GetExtensionPart(InputFile$)
		Else
			flgOpn = 1
			OutputFile$ = GetPathPart(InputFile$) + GetFilePart(InputFile$, #PB_FileSystem_NoExtension) + "_tidy." + GetExtensionPart(InputFile$)
		EndIf

		If ReadFile(#Input, InputFile$) And CreateFile(#Output, OutputFile$)
			g_Format = ReadStringFormat(#Input)
			WriteStringFormat(#Output, g_Format)
			
			Length = Lof(#Input) ; в байтах
			*Buffer = AllocateMemory(Length)

			If *Buffer
				ReadData(#Input, *Buffer, Length)

				CallFunction(#Dll, "SyntaxHighlight", *Buffer, Length, @Callback(), 0)
				FreeMemory(*Buffer)
				ArrSz = n - 1
				ReDim Token.Token(ArrSz) ; обрезаем до текущего размера
			EndIf

			CloseFile(#Input)
			
			StartTime = ElapsedMilliseconds()
; 			Debug ArrSz
			; 			Начинаем обработку
			; 			Так как идёт модернизация пробелов, то это не влияет на код
; 			удаляется пробел только вокруг запятых и скобкок, которые являются разделителями и не могут создать присоединение чисел и слов
			For i = 0 To ArrSz
				; 				Debug Token(i)\len
				If Token(i)\len > 2 ;  Linux > 1
					tmp =  Asc(Right(Token(i)\s , 1))
					If tmp = #CR Or tmp = #LF
						RemovingSpaceBeforeCRLF(i)
					EndIf
				EndIf
; 				If Token(i)\len > 2 And FindString(Token(i)\s, #LF$)
; 					RemovingSpaceBeforeCRLF(i)
; 				EndIf
				
				Select Token(i)\type
						
					Case #SYNTAX_Separator ; (),
; 						Debug "|" + Token()\s + "|"
						Select Token(i)\s
							Case "("
								If i + 1 > ArrSz
									Continue
								EndIf
								If Asc(Token(i + 1)\s) = ' '
									Token(i + 1)\s = LTrimChar(Token(i + 1)\s, " " + #TAB$)
								EndIf
								If i - 1 < 0
									Continue
								EndIf
								If Token(i - 1)\type = #SYNTAX_Function And Right(Token(i - 1)\s , 1) = " " ; если предыдущий токен функция то убираем пробелы 
									Token(i - 1)\s = RTrim(Token(i - 1)\s)
								EndIf
							Case ")"
								If i - 1 < 0
									Continue
								EndIf
								If Right(Token(i - 1)\s , 1) = " "
									Token(i - 1)\s = RTrim(Token(i - 1)\s)
								EndIf
							Case ","
								If i + 1 > ArrSz
									Continue
								EndIf
								tmp = Asc(Token(i + 1)\s)
								If Not (tmp = ' ' Or tmp = #CR Or tmp = #LF)
									Token(i + 1)\s = " " + Token(i + 1)\s
								EndIf
								If i - 1 < 0
									Continue
								EndIf
								If Right(Token(i - 1)\s , 1) = " "
									Token(i - 1)\s = RTrim(Token(i - 1)\s)
								EndIf
						EndSelect
					Case #SYNTAX_Operator ; +-/*=
						Select Token(i)\s
							Case "=", "<", ">"  ; , "<=", ">=", "<<", ">>", "<>" ; комбинированные операторы... с проверкой операторов вперёд и назад
								If i + 2 > ArrSz
									Continue
								EndIf
								tmp = Asc(Token(i + 1)\s)
								If (Asc(Token(i + 1)\s) = 0 And Token(i + 2)\type <> 7) Or Token(i + 1)\type <> 9 Or (Token(i + 1)\type = 9 And tmp <> ' ' And tmp <> 0 And tmp <> #TAB)
; 									tmp = Asc(Token(i + 1)\s)
									If Not (tmp = ' ' Or tmp = #CR Or tmp = #LF)
										Token(i + 1)\s = " " + Token(i + 1)\s
									EndIf
								EndIf
								If i - 2 < 0
									Continue
								EndIf
								tmp = Asc(Token(i - 1)\s)
								If (Asc(Token(i - 1)\s) = 0 And Token(i - 2)\type <> 7) Or Token(i - 1)\type <> 9 Or (Token(i + 1)\type = 9 And tmp <> ' ' And tmp <> 0 And tmp <> #TAB)
									If Right(Token(i - 1)\s , 1) <> " "
										Token(i - 1)\s + " "
									EndIf
								EndIf
							Case "+", "|", "!", "%", "~", "&"   ; не комбинированные операторы. Строки ASM являются цельными с "!"
								If i + 1 > ArrSz
									Continue
								EndIf
								tmp = Asc(Token(i + 1)\s)
								If Not (tmp = ' ' Or tmp = #CR Or tmp = #LF)
									Token(i + 1)\s = " " + Token(i + 1)\s
								EndIf
								If i - 1 < 0
									Continue
								EndIf
								If Right(Token(i - 1)\s , 1) <> " "
									Token(i - 1)\s + " "
								EndIf
							Case "-"
								z = 1
								For j = 1 To 5
									If Token(i - j)\type <> 9 ; #SYNTAX_Number и он же переносы строк и пробелы
										z = j
										Break
									EndIf
								Next
; 								IsFind = FindString(Token(i - z)\s , "=") ; если приравнивание или сравнение
; 								If Not IsFind
								If i - z < 0 ; защита 
									Continue
								EndIf
								If Not (FindString(Token(i - z)\s , "=") Or FindString(Token(i - z)\s , "<") Or FindString(Token(i - z)\s , ">") Or FindString(Token(i - z)\s , ","))
									If Right(Token(i - 1)\s , 1) <> " "
										Token(i - 1)\s + " "
									EndIf
									If i + 1 > ArrSz
										Continue
									EndIf
									tmp = Asc(Token(i + 1)\s)
									If Not (tmp = ' ' Or tmp = #CR Or tmp = #LF)
										Token(i + 1)\s = " " + Token(i + 1)\s
									EndIf
								Else
									If i - 1 < 0
										Continue
									EndIf
									If Right(Token(i - 1)\s , 1) <> " "
										Token(i - 1)\s + " "
									EndIf
								EndIf
							Case "*"
								If i + 1 > ArrSz ; защита 
									Continue
								EndIf
								tmp = Asc(Token(i + 1)\s)
								If tmp >= '0' And tmp <= '9'
									Token(i + 1)\s = " " + Token(i + 1)\s
								EndIf
								If i - 1 < 0 ; защита 
									Continue
								EndIf
								tmp = Asc(Right(Token(i - 1)\s , 1))
								If tmp <> ' ' And tmp <> '('
									Token(i - 1)\s + " "
								EndIf
						EndSelect
						
					Case #SYNTAX_Keyword ; if 
						If Token(i)\len > 11 And FindString(Token(i)\s, "EndProcedure")
; 							задаём между функциями разрыв в две пустые строки, если он меньше. Бывает лепят вплотную.
							tmp = CountString(Token(i)\s, #LF$)
							For j = 1 To 5
								If Token(i + j)\type = 9 ; #SYNTAX_Number и он же переносы строк, но так как после функции не будет число то это работает
									tmp + CountString(Token(i + j)\s, #LF$)
								Else
									Break
								EndIf
							Next
							If tmp < 3
								tmp = 3 - tmp
								For j = 1 To tmp
									Token(i)\s + #CRLF$
								Next
							EndIf
						EndIf
						
;- ██ текущий ██
					Case #SYNTAX_Comment
						CommentHandler(i)
; 					Case #SYNTAX_Number ; пробелы на самом деле
; 						If Token(i)\len > 2 ; на Linux больше одного > 1
; 							tmp =  Asc(Right(Token(i)\s , 1))
; 							If tmp = #CR Or tmp = #LF
; 								RemovingSpaceBeforeCRLF(i)
; 							EndIf
; 						EndIf
; 				tmp = Asc(Token(i)\s)
; 				If (tmp = #CR Or tmp = #LF) And i - 1 >= 0
; 					RemovingSpaceBeforeEnd(i - 1)
; 				ElseIf Token(i)\len > 3 And FindString(Token(i)\s, #LF$)
; 					RemovingSpaceBeforeCRLF(i)
; 				EndIf
						
				EndSelect
				
			Next
; 			EnableDebugger
			Debug "Прошло времени " + Str(ElapsedMilliseconds() - StartTime) + " мсек"
; 			DisableDebugger
; 			Запись
			For i = 0 To ArrSz
				WriteString(#Output, Token(i)\s, g_Format)
				; пишем номер (тип) рядом с токеном, чтоб видеть как размещаются пробелы
; 				WriteString(#Output, Token(i)\s + "|" + Str(Token(i)\type), g_Format)
			Next
			CloseFile(#Output)
		EndIf

	EndIf

	CloseLibrary(#Dll)
	
	If flgOpn
		RunProgram(OutputFile$)
	Else
		If DeleteFile(InputFile$)
			If Not RenameFile(OutputFile$, InputFile$)
				NotSuccess = 1
			EndIf
		EndIf
		If NotSuccess
			MessageRequester("", "Не удалось обновить  файл")
		EndIf
	EndIf
Else
	MessageRequester("", "Failed to open SyntaxHighlighting.dll")
	End
EndIf



Procedure RemovingSpaceBeforeCRLF(i)
    Protected *c.Character, flgSpace, *c0.Character, required
    *c = @Token(i)\s
    While *c\c
    	Select *c\c
    		Case ' ', #TAB, 160, $2002, $2003, $2004, $2005, $2006, $2009, $200A
    			If flgSpace = 0
    				*c0 = *c
    			EndIf
    			flgSpace + 1
    		Case #CR, #LF
    			If flgSpace
    				required = 1
    				Break
    			EndIf
    		Default
    			flgSpace = 0
    	EndSelect
    	*c + SizeOf(Character)
    Wend
    
    If required
    	While *c\c
    		*c0\c = *c\c
    		*c0 + SizeOf(Character)
    		*c + SizeOf(Character)
    	Wend
;     	If *c\c = 0
    		*c0\c = 0
;     	EndIf
    EndIf
EndProcedure




Procedure CommentHandler(i)
    Protected tmp, *c.Character, Text$, flgSpace, *c0.Character, tmp$
;     tmp = CountString(Token(i - 2)\s, #LF$) ; проверяем если в предыдущей строке перенос
;     If tmp And Asc(Token(i)\s) = ';'		; перенос начался с начала строки
    If i < 2 ; защита 
    	ProcedureReturn
    EndIf
    If Asc(Token(i - 1)\s) = 0 And CountString(Token(i - 2)\s, #LF$) And Asc(Token(i)\s) = ';'		; перенос начался с начала строки
    	*c = @Token(i)\s
    	*c0 = *c
    	; *c\c = ' '
    	*c + SizeOf(Character)
    	flgSpace + 1
    	While *c\c
;   		  	Debug Chr(*c\c)
    		If *c\c = ' ' Or *c\c = #TAB
    			flgSpace + 1
    		Else
    			; как только пошёл текст, то переставляеместавляем
    			If flgSpace = < 2
    				Break ; ничего не делаем, комментарий начался нормально "; "
    			EndIf
    			*c0\c = #TAB
    			*c0 + SizeOf(Character)
    			*c0\c = #TAB
    			*c - SizeOf(Character)
    			*c\c = ' '
    			*c - SizeOf(Character)
    			*c\c = ';'
    			Break
    		EndIf
    		*c + SizeOf(Character)
    	Wend
    Else
    	*c = @Token(i)\s
;     	*c0 = *c
    	
    	*c + SizeOf(Character)
    	While *c\c
    		Select *c\c
    			Case ' '
    				If flgSpace = 0
    					flgSpace + 1
    				ElseIf flgSpace = 1
    					*c0 = *c
    					flgSpace = 1
    				ElseIf flgSpace = -1
    					*c0\c = *c\c
    				EndIf
    			Case #TAB
    				If flgSpace = 0
    					flgSpace + 1
    					*c\c = ' '
    				ElseIf flgSpace = 1
    					*c0 = *c
    					flgSpace = 2
    				ElseIf flgSpace = -1
    					*c0\c = *c\c
    				EndIf
    			Default
    				If flgSpace = 0 And *c\c = '-'
    					tmp$ = LTrimChar(Text$, #TAB$ + " ")
    					If Asc(tmp$) = ';'
    						Text$ = tmp$
    						Break
    					EndIf
    				EndIf
    				If flgSpace = 0 Or flgSpace = 1
    					Break ; ничего не делаем, комментарий начался нормально
    				EndIf
    				flgSpace = -1
    				*c0\c = *c\c
    		EndSelect
    		If flgSpace = -1
    			*c0 + SizeOf(Character)
    		EndIf
    		*c + SizeOf(Character)
    	Wend
    	If flgSpace = -1
    		*c0\c = 0 ; если перезаписывали комментарий сдвигая к началу, то надо завершить его нулём
    	EndIf
	EndIf
EndProcedure

; https://www.purebasic.fr/english/viewtopic.php?t=79183
Procedure.s LTrimChar(String$, TrimChar$ = #CRLF$ + #TAB$ + #FF$ + #VT$ + " ")
    Protected *jc0, *c.Character, *jc.Character

    If Not Asc(String$)
        ProcedureReturn ""
    EndIf

    *c = @String$
    *jc0 = @TrimChar$

    While *c\c
        *jc = *jc0

        While *jc\c
            If *c\c = *jc\c
                *c\c = 0
                Break
            EndIf
            *jc + SizeOf(Character)
        Wend

        If *c\c
            String$ = PeekS(*c)
            Break
        EndIf
        *c + SizeOf(Character)
    Wend

    ProcedureReturn String$
EndProcedure
Last edited by AZJIO on Mon Apr 08, 2024 1:25 pm, edited 6 times in total.
Quin
Addict
Addict
Posts: 1132
Joined: Thu Mar 31, 2022 7:03 pm
Location: Colorado, United States
Contact:

Re: [IDE tool] Tidy

Post by Quin »

Nice work! I've been looking for a tool like this for a while now actually.
AZJIO
Addict
Addict
Posts: 2154
Joined: Sun May 14, 2017 1:48 am

Re: [IDE tool] Tidy

Post by AZJIO »

Added alignment of comments and removal of spaces in an empty line.
Added the removal of spaces at the end of each line. Added removal of non-standard spaces (2002-2006, etc.)
AZJIO
Addict
Addict
Posts: 2154
Joined: Sun May 14, 2017 1:48 am

Re: [IDE tool] Tidy

Post by AZJIO »

For Linux without using SyntaxHighlighting.dll
I checked on several sources, including adding a check to the code to ensure that the file has not been modified by removing spaces.

Code: Select all

;- TOP
; AZJIO

EnableExplicit

Define UserIntLang
; Define ForceLangSel

CompilerSelect #PB_Compiler_OS
	CompilerCase #PB_OS_Windows
		Global *Lang
		If OpenLibrary(0, "kernel32.dll")
			*Lang = GetFunction(0, "GetUserDefaultUILanguage")
			If *Lang And CallFunctionFast(*Lang) = 1049 ; ru
				UserIntLang = 1
			EndIf
			CloseLibrary(0)
		EndIf
	CompilerCase #PB_OS_Linux
		If ExamineEnvironmentVariables()
			While NextEnvironmentVariable()
				If Left(EnvironmentVariableName(), 4) = "LANG" And Left(EnvironmentVariableValue(), 2) = "ru"
					; LANG=ru_RU.UTF-8
					; LANGUAGE=ru
					UserIntLang = 1
					Break
				EndIf
			Wend
		EndIf
CompilerEndSelect


Global Dim Lng.s(11)
Lng(1) = "Error"
Lng(2) = "Error opening source file"
Lng(3) = "Error creating file"
Lng(4) = "Overwrite the file?"
Lng(5) = "Overwrite the current file? (otherwise to the clipboard and a file with the _tidy suffix)"
Lng(6) = "Success (time="
Lng(7) = " ms)"
Lng(8) = "Checking the correctness of files by removing spaces has been completed."
Lng(9) = "Error. The file is broken. (time="
Lng(10) = "Checking the correctness of the resulting file by removing spaces revealed a discrepancy with the original."
Lng(11) = "Failed to update file"

If UserIntLang
	Lng(1) = "Ошибка"
	Lng(2) = "Ошибка открытия исходника файла"
	Lng(3) = "Ошибка создания файла"
	Lng(4) = "Перезаписать файл?"
	Lng(5) = "Перезаписать текущий файл? (иначе в буфер обмена и файл с суффиксом _tidy)"
	Lng(6) = "Удачно. (выполнено за "
	Lng(7) = " мсек)"
	Lng(8) = "Проверка корректности удалением пробелов пройдена."
	Lng(9) = "Ошибка. Файл поврежден. (выполнено за "
	Lng(10) = "Проверка корректности результирующего файла методом удаления пробелов выявила несоответствие оригиналу."
	Lng(11) = "Не удалось обновить файл"
EndIf


#Char2 = SizeOf(Character)

; EnumerationBinary
; 	#Space
; 	#Operator
; 	#Comma
; 	#Equals
; EndEnumeration

; AZJIO (вариант из AutoIt3, а по ссылке есть упрощённые варианты)
; https://www.purebasic.fr/english/viewtopic.php?t=80994
Procedure.s TmpFile(DirName$ = "", Prefix$ = "~", Ext$ = ".tmp", RandomLength = 7)
    Protected TmpName$

    If RandomLength < 4 Or RandomLength > 130
        RandomLength = 7
    EndIf
    If Not Asc(DirName$) Or FileSize(DirName$) = -1
        DirName$ = GetTemporaryDirectory()
    EndIf

    If Not CheckFilename(Prefix$)
        Prefix$ = "~"
    EndIf
    If Not CheckFilename(Ext$)
        Ext$ = ".tmp"
    EndIf

    If Right(DirName$, 1) <> #PS$
        DirName$ + #PS$
    EndIf
    If Asc(Ext$) And Left(Ext$, 1) <> "."
        Ext$ = "." + Ext$
    EndIf

    Repeat
        TmpName$ = ""
        While Len(TmpName$) < RandomLength
            TmpName$ + Chr(Random(122, 97))
        Wend
        TmpName$ = DirName$ + Prefix$ + TmpName$ + Ext$
    Until FileSize(TmpName$) = -1

    ProcedureReturn TmpName$
EndProcedure


; https://www.purebasic.fr/english/viewtopic.php?t=79183
Procedure.s RTrimChar(String$, TrimChar$ = #CRLF$ + #TAB$ + #FF$ + #VT$ + " ")
    Protected Len2, Blen, i
    Protected *jc0, *c.Character, *jc.Character

    Len2 = Len(String$)
    Blen = StringByteLength(String$)

    If Not Asc(String$)
        ProcedureReturn ""
    EndIf

    *c = @String$ + Blen - #Char2
    *jc0 = @TrimChar$

    For i = Len2 To 1 Step - 1
        *jc = *jc0

        While *jc\c
            If *c\c = *jc\c
                *c\c = 0
                Break
            EndIf
            *jc + #Char2
        Wend

        If *c\c
            Break
        EndIf
        *c - #Char2
    Next

    ProcedureReturn String$
EndProcedure


Procedure.s ReadFileToVar(Path$)
	Protected id_file, Format, Text$

	id_file = ReadFile(#PB_Any, Path$)
	If id_file
		Format = ReadStringFormat(id_file)
		Text$ = ReadString(id_file, Format | #PB_File_IgnoreEOL)
		; Text$ = ReadString(id_file, #PB_UTF8 | #PB_File_IgnoreEOL)
		CloseFile(id_file)
	EndIf

	ProcedureReturn Text$
EndProcedure


Procedure CodeParser(InputFile$, OutputFile$)
	Protected flgOpnQt, c, tilda
	Protected id_file, id_file2, Format, Text$, *c.Character
	Protected indentNot, CountSpace, *c0, LengthFile.q, *m.Character, *mb.Character, *m0, RequiredNotSp, RequiredSp, RequiredNotSp2
	; MessageRequester("", InputFile$)

	id_file = ReadFile(#PB_Any, InputFile$)
	If Not IsFile(id_file)
		MessageRequester(Lng(1), Lng(2) + #CRLF$ + #CRLF$ + InputFile$)
		ProcedureReturn
	EndIf

	id_file2 = CreateFile(#PB_Any, OutputFile$)
	If Not IsFile(id_file2)
		CloseFile(id_file)
		MessageRequester(Lng(1), Lng(3) + #CRLF$ + #CRLF$ + OutputFile$)
		ProcedureReturn
	EndIf

; 	Case "            Строка в кавычках
; 	Case ;            Строка для игнора коментов
; 	Case ~           Строка в кавычках после тильды
; 		Case \            Экранирование кавычек в тильде
	LengthFile = Lof(id_file)
	*m0 = AllocateMemory(LengthFile + 2, #PB_Memory_NoClear) ; буфер выделяется по длине файла, так как код может написан в одну строку
	*m = *m0

	; цикл взят из удаления комментариев, поэтому в нём нет захвата строки и запоминание указателей.
	; Мод1 = Вместо удаления комментария цикл пробежки до конца строки
	If id_file And id_file2
		Format = ReadStringFormat(id_file)
		WriteStringFormat(id_file2, #PB_UTF8)
		While Not Eof(id_file)
			Text$ = ReadString(id_file, Format) ; читаем построчно, так легче анализировать
			indentNot = 0
; 			StartSpace = 1
			CountSpace = 0
			RequiredNotSp = 0
; 			Debug "|" + Text$ + "|" ; проверили, что строка читается без #CRLF$
; Debug Text$
			Text$ = RTrimChar(Text$, #TAB$ + " ") ; удаляем пустоты в конце строки
			*c = @Text$
			*c0 = *c
			*m = *m0
			; flgSemicolon = 0 ; сбросили флаг
			While *c\c
				Select *c\c
;- !
					Case '!' ; если ASM код, то тупо пропускаем эту строку (строки в кавычках в ASM в виде псевдокода)
						CountSpace = 0
							 ; 						ReadSource = 1
						If indentNot ; если логический знак "Исключающее ИЛИ"
							*m\c = *c\c ; пишем
						Else ; если в начале строки
							Repeat
								*m\c = *c\c ; пишем
								*m + #Char2
								*c + #Char2
							Until *c\c = 0 ; идём до конца строки
							*m\c = 0
; 							PokeS(*m0, Text$)
							Break
						EndIf
						
;- '
					Case 39 ; ' ' апостроф
						indentNot = 1
						CountSpace = 0
						*m\c = *c\c ; пишем апостроф
						*c + #Char2
						*m + #Char2
						If *c\c = 0
							*m\c = 0
							Break
						ElseIf *c\c = 39
							*m\c = *c\c ; пишем
							*m + #Char2
							*c + #Char2
							Continue
						EndIf
						Repeat
							*m\c = *c\c ; пишем
							*m + #Char2
							*c + #Char2
						Until *c\c = 0 Or *c\c = 39 ; идём до конца строки или до заканчивающегося апострофа
						If *c\c = 0
							*m\c = 0
							Break	; выпрыг, если конец строки или апостроф в ненадлежащем месте.
						EndIf
						*m\c = *c\c ; пишем
						
;- " "
					Case '"'
						indentNot = 1
						CountSpace = 0
						; Попалась кавычка, бежим до закрывающей кавычки
						Repeat ; прокручиваем до конца строки
							*m\c = *c\c ; пишем
							*m + #Char2
							*c + #Char2
						Until *c\c = 0 Or *c\c = '"' ; бежим либо до конца строки (код в это случае сломан), либо до закрывающей кавычки
						If *c\c = 0 ; обязательная защита от предотвращения захода за пределы строки, если код невалидный
							*m\c = 0
							Break
						EndIf
						*m\c = *c\c ; пишем
;-;
					Case ';'		; комент
; 						тут вместо цикла можно было бы применить PeekS - PokeS, но по сути они сделают тоже самое
						Repeat ; прокручиваем до конца строки
							*m\c = *c\c ; пишем
							*m + #Char2
							*c + #Char2
						Until *c\c = 0
						*m\c = 0
						Break
; 						indentNot = 1
; 						CountSpace = 0
; 						тут можно сделать алгоритм работы с комментариями, например проверять, что после точки с запятой один пробел,
; 						но это может "испортить" закомментированный код
;- ~
					Case '~'
						indentNot = 1
						CountSpace = 0
						tilda = 1 ; включаем флаг запуска/открытия тильды
						*m\c = *c\c ; пишем
						*m + #Char2
						*c + #Char2
						If *c\c <> '"'
							Continue ; случай если тильда используется как инвертирование числа, а не начало экранированной строки
						EndIf
						While *c\c
							Select *c\c
								Case '"'
									Select tilda
										Case 1, 3
											tilda = 2
; 											*m\c = *c\c ; пишем
; 											*m + #Char2
; 											*c + #Char2
; 											Continue
										Case 2
											; пришли к завершающей кавычке и срабатывает ниже условие flgOpnQt, так как нет Continue по тексту
											; tilda = 0
											*m\c = *c\c ; пишем закрывающую кавычку
											Break ; выпрыгиваем, чтобы не делать лишний сдвиг, мы прошли до закрывющей кавычки, сброс тильды не обязателен
									EndSelect
								Case '\'
									; этот механизм чисто для проверки экранированных кавычек после тильды
									; двойная проверка флагов изменяет поведение при дублировании кавычки
									Select tilda
										Case 2
											tilda = 3
										Case 3
											tilda = 2
									EndSelect
							EndSelect
							*m\c = *c\c ; пишем
							*m + #Char2
							*c + #Char2
						Wend
;- Space, #TAB
					Case ' ', #TAB ; проверяем повтор пробела или таба не являющегося отступом и на удаление
						If indentNot
							CountSpace + 1
							If RequiredNotSp
								RequiredNotSp = 0
								CountSpace + 1
							EndIf
						Else
							*m\c = *c\c ; если отступ то пишем этот пробел
						EndIf
						If CountSpace < 2
							*m\c = *c\c ; пишем пробел если он первый
						Else
							Repeat
								*c + #Char2
							Until *c\c = 0 Or *c\c <> 32 ; идём до конца строки или до заканчивающегося апострофа
							If *c\c = 0 ; обязательная защита от предотвращения захода за пределы строки, если код невалидный
								*m\c = 0
								Break
							EndIf
; 							*m\c = *c\c ; пишем
							*m - #Char2
							*c - #Char2
						EndIf
;- ,
					Case ',' ; проверяем символ вперёд и символ назад перед запятой и на исправление
						indentNot = 1
						If CountSpace ; если предыдущий был пробелом, то делаем буферу шаг назад
							CountSpace = 0
							*m - #Char2
						EndIf
						*m\c = *c\c ; пишем запятую
						*c + #Char2
						If *c\c And *c\c <> ' '; проверяем пробел вперёд, если его нет, то добавляем и возвращаем позицию назад
							*m + #Char2
							*m\c = ' '
						EndIf
						*c - #Char2
; 						Else
; ; 							Если кконец строки, то буфер тоже завершаем
; 							*m + #Char2
; 							*m\c = 0
; 							Break
						
; 					Case 'a' To 'z', 'A' To 'Z', '0' To '9', '_'
; 						indentNot = 1
; 						CountSpace = 0
; 						*c + #Char2
; 						While *c\c
; 							Select *c\c
; 								Case 'a' To 'z', 'A' To 'Z', '0' To '9', '_'
; 									*c + #Char2
; 								Default
; 									*c - #Char2
; 									Break
; 							EndSelect
; 						Wend
;- (
					Case '('
						indentNot = 1
						If CountSpace ; если предыдущий был пробелом, то делаем буферу шаг назад
							*m - #Char2
						EndIf
; 						*mb = *c - 5 * #Char2
						
						
						
; 						Проверка предшествующего "+ And Not Or"
						*mb = *c
						While *mb > *c0
							*mb - #Char2
							If *mb\c = ' '
								Continue
							EndIf
							If *mb\c = '+' Or *mb\c = '-' Or *mb\c = '/' Or *mb\c = '*'
								If CountSpace = 0
									*m - #Char2
									If *m\c <> ' '
										*m + #Char2
									EndIf
								EndIf
								If RequiredNotSp2
									RequiredNotSp2 = 0
								Else
									*m\c = ' '
									*m + #Char2
								EndIf
							ElseIf *mb\c = 'd'
								*mb - #Char2
								If *mb\c = 'n'
									*mb - #Char2
									If *mb\c = 'A'
										*mb - #Char2
										If *mb\c = ' '
											*m + #Char2
											*m\c = ' '
										EndIf
									EndIf
								EndIf
							ElseIf *mb\c = 't'
								*mb - #Char2
								If *mb\c = 'o'
									*mb - #Char2
									If *mb\c = 'N'
										*mb - #Char2
										If *mb\c = ' '
											*m + #Char2
											*m\c = ' '
										EndIf
									EndIf
								EndIf
							ElseIf *mb\c = 'r'
								*mb - #Char2
								If *mb\c = 'O'
									*mb - #Char2
									If *mb\c = ' '
										*m + #Char2
										*m\c = ' '
									EndIf
								EndIf
							EndIf
							Break
						Wend
						
						
						CountSpace = 0
						*m\c = *c\c ; пишем скобку
						*c + #Char2
						If *c\c = ' '
							RequiredNotSp = 1
						EndIf
						*c - #Char2
;- )
					Case ')'
						indentNot = 1
						If CountSpace ; если предыдущий был пробелом, то делаем буферу шаг назад
							CountSpace = 0
							*m - #Char2
						EndIf
						*m\c = *c\c ; пишем скобку
						
;- *
					Case '*'
						indentNot = 1
; 						If Not CountSpace And *m\c <> ' ' ; если предыдущий не пробел, то добавляем пробел буферу
; 							*m\c = ' '
; 							*m + #Char2
; 						EndIf
						CountSpace = 0
						
						If *c > *c0 ; предотвращаем выход за пределы памяти
							*c - #Char2
							*mb = *m - #Char2
							If *c\c <> '(' And *c\c <> ' ' And *c\c <> #TAB And *c\c <> '*' And *c\c <> '@' And *c\c <> '-' And *mb\c <> ' ' ; если перед "*" не разделитель "(" то добавляем пробел
								*m\c = ' '
								*m + #Char2
							EndIf
; 							Если предшествует закрывающая скобка, то нужен пробел после "*", так как указатель не может быть прилеплен к закрывающей скобке без оператора между ними
							If *c\c = ')'
								RequiredSp = 1
							ElseIf *c\c = ' '
								*c - #Char2
								If *c\c = ')'
									RequiredSp = 1
								EndIf
								*c + #Char2
							EndIf
							*c + #Char2
						EndIf
						
						
						
						*m\c = *c\c ; пишем *
						If RequiredSp
							RequiredSp = 0
							*m + #Char2
							*m\c = ' '
							CountSpace = 1
						Else
							*c + #Char2
							If *c\c And Not ((*c\c >= 'A' And *c\c <= 'Z') Or (*c\c >= 'a' And *c\c <= 'z') Or *c\c = ' ') ; проверяем что после запятой не буква, что было бы указателем
								*m + #Char2
								*m\c = ' '
								CountSpace = 1
							EndIf
							*c - #Char2
						EndIf
						
;- + / |
					Case '+', '/', '|'
						indentNot = 1
						If Not CountSpace And *m\c <> ' ' ; если предыдущий не пробел, то добавляем пробел буферу
							*m\c = ' '
							*m + #Char2
							RequiredSp = 1
						EndIf
						CountSpace = 0
						
; 						Перестраховка... Если предыдущий код добавил пробел, то этот пропускаем
						If Not RequiredSp
							If *c > *c0 ; предотвращаем выход за пределы памяти
								*c - #Char2
								*mb = *m - #Char2
								If  *c\c <> ' ' And *c\c <> #TAB And *mb\c <> ' ' ; если перед "*" не разделитель "(" то добавляем пробел
									*m\c = ' '
									*m + #Char2
; 									CountSpace = 1
								EndIf
								*c + #Char2
							EndIf
						EndIf
						RequiredSp = 0
						
						*m\c = *c\c ; пишем + |
						*c + #Char2
						If *c\c And *c\c <> ' ' ; проверяем пробел вперёд, если его нет, то добавляем и возвращаем позицию назад
							*m + #Char2
							*m\c = ' '
						EndIf
						*c - #Char2
						
;- = < >
					Case '=', '<', '>'
						indentNot = 1
						CountSpace = 0
; 						If Not CountSpace ; если предыдущий не пробел, то добавляем пробел буферу
							If *c > *c0 ; предотвращаем выход за пределы памяти
								*c - #Char2
								*mb = *m - #Char2
								If *c\c <> '<' And *c\c <> '>' And *c\c <> '=' And *c\c <> ' ' And *mb\c <> ' ' ; если перед = нет операторов сравнения <= или >= то добавляем пробел
									*m\c = ' '
									*m + #Char2
; 									CountSpace = 1
								EndIf
								*c + #Char2
							EndIf
; 						EndIf
						*m\c = *c\c ; пишем '=', '<', '>'
						*c + #Char2
						If *c\c And *c\c <> ' ' And *c\c <> '<' And *c\c <> '>' And *c\c <> '=' ; проверяем пробел вперёд, если его нет, то добавляем и возвращаем позицию назад
							*m + #Char2
							*m\c = ' '
						EndIf
						*c - #Char2
						
;- -
					Case '-'
						indentNot = 1
						CountSpace = 0
; 						If Not CountSpace ; если предыдущий не пробел, то добавляем пробел буферу
							If *c > *c0 ; предотвращаем выход за пределы памяти
								*c - #Char2
								*mb = *m - #Char2
								If *c\c <> ' ' And *mb\c <> ' ' ; если не пробел
									*m\c = ' '
									*m + #Char2
									CountSpace = 1
								EndIf
								*c + #Char2
							EndIf
; 						EndIf
						*m\c = *c\c ; пишем -
						
; 						Проверка предшествующего "="
						*mb = *c
						While *mb > *c0
							*mb - #Char2
							If *mb\c = ' '
								Continue
							EndIf
							*c + #Char2
							If *mb\c = '=' Or *mb\c = ','
								; Добавляем пробел после минуса "-" только если нет приравнивания "="
								If *c\c = ' ' ; проверяем пробел вперёд, если он есть, то ставим флаг на удаление и возвращаем позицию назад
									RequiredNotSp = 1
								ElseIf *c\c = '('
									RequiredNotSp2 = 1
								EndIf
							Else
								; Добавляем пробел после минуса "-" только если нет приравнивания "="
								If *c\c And *c\c <> ' ' ; проверяем пробел вперёд, если его нет, то добавляем и возвращаем позицию назад
									; здесь в цикле возвращаемся назад пока после пробела не встретим символ, если символ "=", то пробел не вставляем после "-"
									*m + #Char2
									*m\c = ' '
								EndIf
							EndIf
							*c - #Char2
							Break
						Wend
						
;- Default
					; Case '-'
					Default
; 						Если любой другой символ (ключевое слово и т.д.), то просто сохраняем в буфер как есть
						indentNot = 1
						CountSpace = 0
						*m\c = *c\c ; пишем
						; If Not flgOpnQt
						; EndIf
				EndSelect
				*c + #Char2
				*m + #Char2
			Wend
			*m\c = 0 ; завершаем строку нулём
; 			Debug *m
; 			Debug *m0
; 			Debug Text$
; 			WriteStringN(id_file2, Text$)
			WriteStringN(id_file2, PeekS(*m0))
		Wend
		CloseFile(id_file)
		CloseFile(id_file2)
	EndIf
;- End CodeParser

; 	If IsFile(id_file)
		; CloseFile(id_file)
; 	EndIf
; 	If IsFile(id_file2)
		; CloseFile(id_file2)
; 	EndIf
	
	FreeMemory(*m0)
EndProcedure

;- Global
Global Start, InputFile$, OutputFile$, Rewrite, NotSuccess
Define s1$, s2$, flgCompare = 1, StartTime


; 	Добавляем параметоры ком строки чтобы использовать как инструмент
If CountProgramParameters()
	InputFile$ = ProgramParameter(0)
	If Not (Asc(InputFile$) And FileSize(InputFile$) > 3 And Left(GetExtensionPart(InputFile$), 2) = "pb")
		InputFile$ = ""
	EndIf
EndIf

If Not Asc(InputFile$)
	InputFile$ = OpenFileRequester("Select PB File", "", "*.pb*|*.pb*|All Files|*.*", 0)
EndIf

If Asc(InputFile$)
; 	If MessageRequester("Перезаписать?", "Перезаписать текущий файл? (иначе в буфер обмена и файл с суффиксом _tidy)",
	If MessageRequester(Lng(4), Lng(5),
	                    #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
		OutputFile$ = TmpFile()
		Rewrite = 1
	Else
		Start = 1
		OutputFile$ = GetPathPart(InputFile$) + GetFilePart(InputFile$, #PB_FileSystem_NoExtension) + "_tidy." + GetExtensionPart(InputFile$)
	EndIf
	
	StartTime = ElapsedMilliseconds()
	CodeParser(InputFile$, OutputFile$)
	StartTime = ElapsedMilliseconds() - StartTime
	
	; 		сравнение методом удаления всех пробелов и табуляции, при этом файлы должны быть точной копией.
	; 		если проверка не пройдена то файл гарантированно сломан.
	; 		Возможно это в будущем не потребуется
	If flgCompare
		s1$ = ReadFileToVar(InputFile$)
		s2$ = ReadFileToVar(OutputFile$)
		s1$ = ReplaceString(s1$, " ", "")
		s1$ = ReplaceString(s1$, "	", "")
		s1$ = ReplaceString(s1$, #CRLF$, #LF$) ; для совместимости исходников Linux и Windows, ввиду разных переносов строк
		; 			s1$ = ReplaceString(s1$, #CR$, "")
		; 			s1$ = ReplaceString(s1$, #LF$, "")
; 		s1$ = RTrimChar(s1$, #CRLF$)
		s1$ = RTrim(s1$, #LF$) ; а потому что WriteStringN() добавляет лишний перенос строки
		
		s2$ = ReplaceString(s2$, " ", "")
		s2$ = ReplaceString(s2$, "	", "")
		s2$ = ReplaceString(s2$, #CRLF$, #LF$) ; для совместимости исходников Linux и Windows, ввиду разных переносов строк
		; 			s2$ = ReplaceString(s2$, #CR$, "")
		; 			s2$ = ReplaceString(s2$, #LF$, "")
; 		s2$ = RTrimChar(s2$, #CRLF$)
		s2$ = RTrim(s2$, #LF$) ; а потому что WriteStringN() добавляет лишний перенос строки
		
		
; Сравнение файлов в случае поиска проблем
; #File = 0
; If CreateFile(#File, "/tmp/1111.pb")
; 	WriteStringFormat(#File, #PB_UTF8)
; 	WriteString(#File, s1$, #PB_UTF8)
; 	CloseFile(#File)
; EndIf
; If CreateFile(#File, "/tmp/2222.pb")
; 	WriteStringFormat(#File, #PB_UTF8)
; 	WriteString(#File, s2$, #PB_UTF8)
; 	CloseFile(#File)
; EndIf

		
		If CompareMemoryString(@s1$, @s2$) = #PB_String_Equal
			CompilerIf #PB_Compiler_OS = #PB_OS_Windows
				MessageRequester(Lng(6) + Str(StartTime) + Lng(7), Lng(8), 4096) ; поверх всех окон
			CompilerElse
				MessageRequester(Lng(6) + Str(StartTime) + Lng(7), Lng(8))
			CompilerEndIf
		Else
			CompilerIf #PB_Compiler_OS = #PB_OS_Windows
				MessageRequester(Lng(9) + Str(StartTime) + Lng(7), Lng(10), 4096) ; поверх всех окон
			CompilerElse
				MessageRequester(Lng(9) + Str(StartTime) + Lng(7), Lng(10))
			CompilerEndIf
		EndIf
	EndIf

	If Start
		CompilerIf #PB_Compiler_OS = #PB_OS_Windows
			RunProgram(OutputFile$)
		CompilerElse
			RunProgram("xdg-open", OutputFile$, GetPathPart(OutputFile$)) 
		CompilerEndIf
	Else
		If DeleteFile(InputFile$)
			If Not RenameFile(OutputFile$, InputFile$)
				NotSuccess = 1
			EndIf
		EndIf
		If NotSuccess
			MessageRequester("", Lng(11))
		EndIf
	EndIf

; 	If Rewrite
		; CopyFile(OutputFile$, InputFile$)
; 	EndIf
; 	If Start
		; RunProgram(OutputFile$)
; 	EndIf

EndIf
Last edited by AZJIO on Fri Apr 12, 2024 7:35 am, edited 2 times in total.
AZJIO
Addict
Addict
Posts: 2154
Joined: Sun May 14, 2017 1:48 am

Re: [IDE tool] Tidy

Post by AZJIO »

Update
Added language support
Bug fixed: a symbol was added before the bracket. (Linux version has not yet been rebuilt)
Added rule "-" before comma, no space after "-". And also before the bracket, if equating or listing separated by commas.
Post Reply