Hi AHK-community!
We recently had a discussion about the use of preprocessing an AHK file (see
http://www.autohotkey.com/forum/topic4573.html).
I built a preprocessor that supports the following code:
Code:
#DEFINE test
#IFDEF test
msgbox This code is executed.
#ELSE
msgbox This code is commented out.
#ENDIF
#IFNDEF test
msgbox This code is commented out.
#ENDIF
The major use-case it supports is compiler switches for a published and a private version of your script. But you can think of many other scenarios like debugging, logging, personalization, etc.
It does also handle #INCLUDE, but the restriction is that it only supports absolute pathes.
Of course, in a scripting language like AHK one can do the same with dynamic code. But I dislike turning off hotkeys via the hotkey command etc. I think my preprocessor is a much cleaner solution.
How does it work? Compile the script below and drag&drop your script enhanced with the commands on the executable. The preprocessed files are located in the same directory as the source files, but carry the double extension .PREPROCESSED.ahk instead of .ahk only. By commenting out undefined sections instead of deleting them, the line numbers stay the same as in the source files. Remember that your .ahk source files are not of valid AHK-syntax anymore (due to #DEFINE commands being unknown to AHK). You can only run and compile the preprocessed versions, but you should make your changes only to the original source files (since preprocessed files are of temporary nature and will be overwritten in every preprocessing step).
This is the code of the preprocessor:
Code:
; Autohotkey preprocessor by arbe
; Syntax: preprocessor.exe [-run] [-deleteLines | -clearLines] scriptPath
#NoEnv
; Check syntax and store variables passed on command line
terminateDueToBadSyntax() {
msgbox Syntax: preprocessor.exe [-run] [-deleteLines | -clearLines] scriptPath
exitapp
}
if 0 = 0
terminateDueToBadSyntax()
Loop %0%
{
param := %A_Index%
if param = -run
followupAction = run
else if param = -deleteLines ; delete not defined lines instead of out-commenting them (attention: AHK debug messages will not show the same line numbers as in the source files anymore).
deleteLines := true
else if param = -clearLines ; does not delete but clear (blank out) undefined lines
clearLines := true
else
scriptPath := param
}
if scriptPath=
terminateDueToBadSyntax()
IfNotExist %scriptPath%
terminateDueToBadSyntax()
; recursive main preprocessing function call. Returns the output path of preprocessed script.
preprocess(inPath)
{
global defines
global deleteLines
global clearLines
StringTrimRight outPath, inPath, 4
outPath = %outPath%.PREPROCESSED.ahk
FileDelete %outPath%
Loop read, %inPath%, %outPath%
{
; strip of comments for internal representation and trim line:
line := A_LoopReadLine
temp := InStr(line, ";")
if temp > 0
StringLeft line, line, temp-1
line = %line%
; extract commands:
indexAfterCommand := InStr(line, A_Space)
if indexAfterCommand > 0
StringLeft commandName, A_LoopReadLine, indexAfterCommand-1
else
commandName := A_LoopReadLine
StringTrimLeft commandParameter, line, indexAfterCommand
defineObject=|%commandParameter%|
; check commands
lineText=;%A_LoopReadLine% ; default for the following commands is blankout
if commandName = #DEFINE
{
IfNotInString defines, defineObject
defines=%defines%%defineObject%
}
else if commandName = #IFDEF
{
; check for nested if
if modeIf
{
MsgBox Nested IFDEF not supported by preprocessor.`nLine %A_Index% in %inPath%
ExitApp
}
modeIf := true
modeValue := Instr(defines, defineObject)
}
else if commandName = #IFNDEF
{
; check for nested if
if modeIf
{
MsgBox Nested IFNDEF not supported by preprocessor.`nLine %A_Index% in %inPath%
ExitApp
}
modeIf := true
modeValue := !Instr(defines, defineObject)
}
else if commandName = #ELSE
{
; check for bad syntax
if !modeIf
{
MsgBox #ELSE without #IFDEF or #IFNDEF encountered by preprocessor.`nLine %A_Index% in %inPath%
ExitApp
}
modeValue:=!modeValue
}
else if commandName = #ENDIF
{
; check for bad syntax
if !modeIf
{
MsgBox #ENDIF without #IFDEF or #IFNDEF encountered by preprocessor.`nLine %A_Index% in %inPath%
ExitApp
}
modeIf := false
}
else if commandName = #INCLUDE
{
if (modeIf AND !modeValue) ; handle disabled includes via ifdef
lineText=`;%A_LoopReadLine%
else
{
IfExist %commandParameter%
{
temp := preprocess(commandParameter)
lineText=#INCLUDE %temp%
}
else
{
MsgBox Include file %commandParameter% not found. Preprocessor does only support absolute pathes.`nLine %A_Index% in %inPath%
ExitApp
}
}
}
else
{
if (modeIf AND !modeValue)
{
if deleteLines
lineText=#MARKEDFORDELETION
else if clearLines
lineText=
else
lineText=`;%A_LoopReadLine%
}
else
lineText=%A_LoopReadLine%
}
if lineText<>#MARKEDFORDELETION
FileAppend %lineText%`n
}
return outPath
}
; parse files and write output recursively (#input)
temp := preprocess(scriptPath)
; if asked for, run next step
if followupAction=run
Run %temp%