AutoHotkey Community

It is currently May 27th, 2012, 9:34 am

All times are UTC [ DST ]




Post new topic Reply to topic  [ 8 posts ] 
Author Message
 Post subject: AHK preprocessor
PostPosted: August 29th, 2006, 8:07 pm 
Offline

Joined: April 12th, 2005, 6:01 pm
Posts: 30
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%


Last edited by arbe on September 3rd, 2006, 9:50 am, edited 2 times in total.

Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 30th, 2006, 1:52 pm 
Offline

Joined: March 2nd, 2004, 3:36 pm
Posts: 10720
Very nice script. This should be useful to those craving preprocessor capabilities. It will also be useful to me when the time comes to build in such features.

Thanks.


Report this post
Top
 Profile  
Reply with quote  
 Post subject: Re: AHK preprocessor
PostPosted: August 30th, 2006, 8:34 pm 
Offline

Joined: April 17th, 2005, 7:47 pm
Posts: 289
Location: Sauerland
Hey, nice work! Image
I will use it to write similar - but not identical - scripts for several users, so I only have to control one "master" script.

2 suggestions:
    an option to delete the out-commented lines
    a preprocessor command like #ELSEIFDEF (Kernighan & Ritchie forgot this in C and caused billions of unnecessarily nested and inscrutable preprocessor lines :wink: )

    Code:
    Now:                        Golden future:

    #IFDEF A                    #IFDEF A
      MsgBox A                    MsgBox A
    #ELSE                       #ELSEIFDEF B
    #IFDEF B                      MsgBox B
      MsgBox B                  #ELSEIFDEF C
    #ELSE                         MsgBox C
    #IFDEF C                    #ELSEIFDEF D
      MsgBox C                    MsgBox D
    #ELSE                       #ELSE
    #IFDEF D                      MsgBox another letter
      MsgBox D                  #ENDIF
    #ELSE
      MsgBox another letter
    #ENDIF
    #ENDIF
    #ENDIF
    #ENDIF


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 31st, 2006, 9:18 am 
Offline

Joined: December 27th, 2005, 1:46 pm
Posts: 6837
Location: France (near Paris)
I haven't used it yet, but it looks like well implemented, and its usefulness starts to show...
I like Rabiator suggestions...
I will add another: like many preprocessors, it would be nice to be able to set a define on the command line:
preprocessor.exe [-run] [-strip] [-DFOO ...] scriptPath
I see you process a #INCLUDE command, but it already exists in AutoHotkey, perhaps you should rename it.

_________________
Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 2nd, 2006, 9:20 am 
Offline

Joined: April 12th, 2005, 6:01 pm
Posts: 30
Chris, Rabiator, and PhiLho,

thanks for your interest!

I liked the suggestion about deleting not defined lines and modified the script. Rabiator, please be aware that AHK's debug messages show line numbers that do not match those in your source files anymore, when you delete those lines. For compiled scripts this should not be a problem. The good thing about this feature is supporting the 'need to know'-principle.

Your suggestion of #ELSEIFDEF is a nice idea. But please be aware that this is a basic version of an AHK preprocessor intended mainly for my personal use. As noted in the script, even nested #IFDEFs are not supported. Are your preprocessing conditions really that complicated that you can not just go for this?
Code:
#IFDEF A
msgbox A
#ENDIF

#IFDEF B
msgbox B
#ENDIF


PhiLho, I have decided against command line defines due to the alternative of having a define file that includes your main script at the end. That way, you can have several sets of defines for one script that are easy to edit.
Code:
#define a
#define b
#include c:\my_master_script.ahk

Regarding your comment about #INCLUDEs, I think you misunderstood the script a bit. It is necessary to handle the original AHK command. I did not intent to extend any functionality here, since Chris implemented include-preprocessing already. But in order to handle 'distributed scripts' with defines, I need to recursively preprocess all include files. You might just want to give it a try by preprocessing one of your scripts that has includes.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 2nd, 2006, 5:56 pm 
Offline

Joined: April 17th, 2005, 7:47 pm
Posts: 289
Location: Sauerland
arbe wrote:
Rabiator, please be aware that AHK's debug messages show line numbers that do not match those in your source files anymore, when you delete those lines.

The intention was to hide information among users if you share the generated scripts. The lines have not to be deleted completely, the CR could remain, so the line numbers were consistent.

Quote:
... #ELSEIFDEF ... for my personal use. ... Are your preprocessing conditions really that complicated that you can not just go for this?

Of course not, but it's very hard without #ELSEIFDEF. :wink: :wink:
Please don't feel constrained to integrate these suggestions, I'm pleased that you built this functionality as it is. 8)


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 3rd, 2006, 9:54 am 
Offline

Joined: April 12th, 2005, 6:01 pm
Posts: 30
Rabiator wrote:
The lines have not to be deleted completely, the CR could remain, so the line numbers were consistent.

Thanks for the hint. You are absolutely right, I added the option -clearLines.

Rabiator wrote:
Please don't feel constrained to integrate these suggestions, I'm pleased that you built this functionality as it is. 8)

I'm glad it's not only useful for me! But since I don't see the necessity for nested IFDEFs or ELSEIFDEFs, I will not implement them in the near future. So please feel free to modify the code yourself, if you like.


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: September 4th, 2006, 10:44 pm 
Offline

Joined: April 17th, 2005, 7:47 pm
Posts: 289
Location: Sauerland
arbe wrote:
I added the option -clearLines.
Thanks. 8)


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 8 posts ] 

All times are UTC [ DST ]


Who is online

Users browsing this forum: Bing [Bot], Google Feedfetcher, nomissenrojb, Stigg and 19 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group