; Released under the Terms of EUPL 1.1 or later ; Also released under the Terms of GPL V3 or later ; (w) 2010 by derRaphael ; The software is hereby free of any guarantees ; It has been tested and developed under winxp and ahk 1.0.48.05 ; If it works, good for you. If as a result your wife wants to ; get divorced - not my beer. ; There are numerous bugs and many caveats ; the entire script is subject to change for a better functionality ; and feature design. for now only the core functionality is to ; be tested. ; Usage Limitation: Leave the Generated by Hint intact. ; http://www.autohotkey.com/forum/topic54846.html FileSelectFile, ScriptPath, 1, %A_ScriptDir%, Select an AHK Script if ( StrLen( ScriptPath ) := "" ) ScriptPath := A_ScriptFullPath FileRead, Script, % ScriptPath SplitPath,ScriptPath,,,,ProjectName version := "0.5.2" ; Build RegEx for latter advanced parsing and masking Gosub, BuildRegEx ; Mask all continuation sections ScriptCopy := MaskString( Script, ContinuationSections ) ; Mask all Quotations ScriptCopy := MaskString( ScriptCopy, BalancedQuotes, "MatchBQ" ) ; Mask all Continuation comments ScriptCopy := MaskString( ScriptCopy, ContinationComments ) ; Remove all Comments ScritpCopy := RegExReplace( ScriptCopy, ClassicComments ) funCount := 0 ; Iterate all functions While ( p := RegExMatch( ScriptCopy, CurlyBracketsBalanced, Match, ( !p ? 1 : p+StrLen( Match ) ) ) ) { ; Grab function position and parameter Needle := "sim)\b(?P\w+)\b\((?P[^\)]*)\)" . "(?(?=([^{\(\)}:+\-*~!\?,\.=""]*){)" ; if . "([^{\(\)}:+\-*~!\?,\.=""]*){|" ; then . "\s([\s]*;[^\r\n]+\r?\n)+\s*{)" ; else funPos := RegExMatch( Match, Needle , fun ) ; Check for Keywords If ( ! RegExMatch( funName, "i)(\W|\bif\b|\bdllcall\b|\bwhile\b)" ) && StrLen( funName ) != 0 ) { ; Grab function definition from real scriptsource funDef := SubStr( Script, p+funPos-1, StrLen( RegExReplace( fun, "[^{\(\)}:+\-*~!\?,\.=""]*{$" ) ) ) ; Match name RegExMatch( funDef, "^\b(?P\w+)\b", _ ) ; Match parameter RegExMatch( funDef, "sm)\((?P.*)\)$", _ ) funCount++ ; Store to global array funName%funCount% := _function ; store funnames is CR delimited list for latter internal linking funNames .= ( StrLen( funNames ) ? "`n" : "" ) _function funParams%funCount% := _params ; Match function funMatch%funCount% := SubStr( Script, p+funPos-1, StrLen( Match )-funPos+1 ) funDef%funCount% := funDef funAll%funCount% := SubStr( Script, p, StrLen( Match ) ) funAll := SubStr( Script, p, StrLen( Match ) ) RegExReplace( SubStr( funAll, 1, funPos ), "\n", "`n", funPosLine ) FoundComments := "" isComment := isContinuationComment := false Loop,Parse,funAll,`n,`r { if ( A_Index <= funPosLine ) { /* * TODO: FIXME: For empty Lines after descriptive comment block */ ; Rule #1 if its an empty line discard all previoulsly found comments if ( ! StrLen( trim( A_loopField ) ) && ! isContinuationComment ) { foundComments := "" isComment := false } ; Rule #2 set a comment marker if ( RegExMatch( A_loopField, "^\s*;" ) ) { isComment := true } ; Rule #3 set flag for Continuation comments "/* ... */" if ( RegExMatch( A_LoopField, "^\s*\/\*" ) ) { isContinuationComment := true } ; Add all found comments to a CR separated list if ( isComment || isContinuationComment ) { if ( StrLen( RegExReplace( A_LoopField, "(\W|\_|\$)") ) ) { foundComments .= ( StrLen( foundComments ) ? "`n" : "" ) . RegExReplace( A_LoopField, ".*?\s*;\s*" ) if ( ! isContinuationComment ) { isComment := false } } } if ( RegExMatch( A_LoopField, "^\s*\*\/" ) ) { isContinuationComment := false } } } funComments%funCount% := foundComments } } ; Empty variables for latter usage html := index := "" ; Loop thru all found functions Loop,% funCount { ; Add items to list of function names index .= "`t`t
  • " funName%A_Index% "()
  • `n" ; Clear up function definition and remove continuation sections, if any funDef := RegExReplace( funDef%A_Index%, "\r?\n" ) funDef := RegExReplace( funDef, "=\s*(\(.*?\))", "= {{ContinuationSection}}" ) p := "" if ( RegExMatch( funDef, "\((?P.*)\)", fun ) ) { ; Mask quotes in function definition funParamMasked := MaskString( funParam, BalancedQuotes ) ; Grab parameter and store in array While ( p := RegExMatch( funParamMasked, "[^,]+", m, ( !p ? 1 : p+StrLen(M) ) ) ) { p0 := A_Index p%A_Index% := SubStr( funParam, p, StrLen(M) ) } params := paramAppendix := "" ; Loop array Loop, % p0 { ; check for predefined values in the parameter of the current function if ( InStr( p%A_index%, "=" ) ) { ; add an optional indicator square bracket params := params " [" ( A_Index > 1 ? ", " : " " ) p%A_Index% ; sum up closings paramAppendix .= " ]" } Else { ; or just add the parameter params .= ( StrLen( params ) ? ", " : "" ) p%A_Index% } } ; add the appendix closing optional brackets params .= paramAppendix } ; build HTML anchor for easier navigation in the doc html .= "" ; add a function definition . "

    " RegExReplace(funDef,"\(.*") "(" Params ")

    `n" ; trim any remaining linebreaks the original function to include it into doc funMatch%A_Index% := RegExReplace( funMatch%A_Index%, "(\r?\n)*$" ) ; parse the function and beautify it by using span tags, links and stylesheets html .= TransScript( funMatch%A_Index%, funComments%A_Index% ) "`n" . "`n" . "`n" . "`n" } ; enclose the index by the neccessary unordered list tags index := "
      `n" index "`n`t
    `n" html := BuildHTML( ProjectName, index, html, funNames ) FileSelectFile, FileName, S16,,Name where To save the HTML If ( FileExist( FileName ) ) { FileDelete, % FileName } FileAppend,% html, % FileName run, % FileName ExitApp Return /** * Build the RegEx neccessary for masking and matchmaking */ BuildRegEx: ; regex found here ; http://stackoverflow.com/questions/133601/can-regular-expressions-be-used-to-match-nested-patterns CurlyBracketsBalanced= (LTrim RTrim Join Comments ms) ; multiline dotall (?>[^{}]*) ; match lookbehind anything but brackets { ; start with openingbracket (?>[^{}]*) ; match lookbehind anything but brackets (?R)* ; Recursion of entire expression (?>[^{}]*) ; match lookbehind anything but brackets } ; Closing Bracket ) ; based on the CurlyBracketsBalanced regex BracketsBalanced= (LTrim RTrim Join Comments ms) ; multiline dotall (?>[^\(\)]*) ; match lookbehind anything but brackets \( ; start with openingbracket (?>[^\(\)]*) ; match lookbehind anything but brackets (?R)* ; Recursion of entire expression (?>[^\(\)]*) ; match lookbehind anything but brackets \) ; Closing Bracket ) ; based on the CurlyBracketsBalanced regex SquareBracketsBalanced= (LTrim RTrim Join Comments ms) ; multiline dotall (?>[^\[\]]*) ; match lookbehind anything but brackets \[ ; start with openingbracket (?>[^\[\]]*) ; match lookbehind anything but brackets (?R)* ; Recursion of entire expression (?>[^\[\]]*) ; match lookbehind anything but brackets \] ; Closing Bracket ) ; regex found here ; http://wordaligned.org/articles/string-literals-and-regular-expressions BalancedQuotes= (LTrim RTrim Join Comments ms) ; multiline dotall (" ; start with a quote (?P ; Store in captchuring group BQ ([^"]|"")*) ; match either zero or more non-quotes or doublequotes ") ; end with a quote ) ContinuationSections= (LTrim RTrim Join Comments ms) ; multiline dotall ^\s* ; start with zero or more whitespaces \( ; followed by an opening parenthesis (?P.*?) ; nongreedy anything to ^\s* ; zero or more whitespaces \) ; followed by a closing parenthesis ) ContinationComments= (LTrim RTrim Join Comments ms) ; multiline dotall (\/\* ; match from 1st beginning /* .*? ; ungreedy any occurence of one or more chars ^\s*\*\/) ; to next occurence of */ ) ClassicComments= (Ltrim RTrim Join Comments ms) ; multiline dotall \s+(;.*?) ; one or more spaces before the semicolon and everything after (?=(\r?\n)) ; up to the lineend ) Return /** * This function masks a given string by a passed regex with a special maskchar * and returns the masked string * * @param Target - A string containing the source * @param Needle - A string containing the regex needle * @param _MatchName - usefull for only replacing parts and not entire match * @param MaskChar - Just for the case a different char is neccessary * * @return String */ MaskString( Target, Needle, _MatchName = "Match", MaskChar = "-") { ; loop untill no matches are found while ( p := RegExMatch( Target, Needle, Match, ( !p ? 1 : p+StrLen( Match ) ) ) ) { ; build replacement string, but keep linebreaks intact Replacement := RegExReplace( %_MatchName%, "ms)[^\r\n]", MaskChar ) if ( StrLen(Replacement) != 0 ) { ; Replace only one occurence in the target starting at position p Target := RegExReplace( Target, "ms)\Q" RegExSafe( %_MatchName% ) "\E", Replacement, 0, 1, p ) } } Return Target } /** * Avoids preliminary ends of quotation regex * * @param Needle * @return String */ RegExSafe( Needle ) { ; Mask any EscapeChars which might end preliminary an quotation RegEx Return RegExReplace( Needle, "ms)\\", "\E\\\Q" ) } /** * Translates the entire found funtion to HTML * * @param Function - the function name * @param comments - the corresponding comments ahead the definition * @return String */ TransScript( Function, Comments ) { local blah Local Pos, cLine, continuationSection, MaskedLine, commentLine, inlineComments, currentComment Local commentBlock, nComments, tableData, head, lemma, table, returnValue, out Local firstContLine, check0, hash function := RegExReplace( function, "\t", " " ) ; Strip comments to a nice list continuationSection := false Loop,Parse,function,`n,`r { MaskedLine := MaskString( A_LoopField, BalancedQuotes ) if ( RegExMatch( A_LoopField, "^\s*\(" ) ) { continuationSection := true } else if ( RegExMatch( A_LoopField, "^\s*\(" ) ) { continuationSection := false } if ( Pos := RegExMatch( MaskedLine, "\s*;(?P.*)", _ ) && ! continuationSection ) { commentLine .= ( StrLen( CommentLine ) ? "`n" : "" ) A_Index "/" Pos currentComment := RegExReplace( SubStr( A_LoopField, Pos ), ".*?\s*;\s*" ) if ( ! RegExMatch( currentComment, "^~" ) ) { inlineComments .= ( StrLen( inlineComments ) ? "`n" : "" ) "
  • " htmlEncode( currentComment ) "
  • `n" } } } commentBlock := ( strlen( inlineComments ) ? "
      " inlineComments "
    " : "" ) ; build Lemma nComments := RegExReplace( Comments, "ms)(^\s*|\s*$)" ) nComments := RegExReplace( nComments, "ms)\s*\*\/\s*" ) nComments := RegExReplace( nComments, "ms)\s*\/\*\s*" ) comments := "" Loop,Parse,nComments,`n,`r { comments .= ( StrLen( comments ) ? "`n" : "" ) trim( RegExReplace( A_LoopField, "^\s*\*+\s*" ) ) } Loop,Parse,Comments,`n { if ( RegExMatch( A_LoopField, "i)@param", _ ) ) { RegExMatch( A_LoopField, "i)\s*(@param)\s*(?P[^\s]+)(?P.*)", Param ) tableData .= "" . ParamName . "" . ( strlen( trim( x:=RegExReplace( ParamDefinition, "^\s*-\s*" ) ) ) ? x : "No definition available" ) . "`n" } else if ( RegExMatch( A_LoopField, "i)@return", _ ) ) { RegExMatch( A_LoopField, "i)\s*(@return)\s*(?P.*)", Return ) } Else { head .= ( StrLen( trim(A_LoopField) ) ? "
    " htmlEncode(A_LoopField) "
    `n" : "" ) } } ; Build Header if any if ( StrLen( trim(head) ) ) { lemma := "

    Abstract

    `n" head "`n" } ; Build Param Table if any if ( StrLen( trim( tableData ) ) ) { table := "

    Parameter

    `n" . "`n" . tableData . "
    `n" } ; Build commentBlock if any if ( StrLen( trim( CommentBlock ) ) ) { commentBlock := "

    Inline Comments

    " commentBlock } ; Build ReturnValue information if any if ( StrLen( trim( ReturnValue ) ) ) { returnValue := "

    Returns

    `n
    " htmlEncode( returnValue ) "
    " } out := "" ; beautify function continuationSection := false Loop,Parse,Function,`n,`r { CurrentLine := A_LoopField MaskedLine := MaskString( CurrentLine, BalancedQuotes, "MatchBQ" ) ; check for continuation section to skip the rest if ( RegExMatch( CurrentLine, "^\s*\(" ) ) { continuationSection := true firstContLine := true } else if ( RegExMatch( CurrentLine, "^\s*\)" ) ) { continuationSection := false } if ( !continuationSection || firstContLine ) { firstContLine := false ; make line appear as inline comment if ( Pos := RegExMatch( CurrentLine, "(?P.*?\s+)(?P;.*)", _ ) && !continuationComment ) { Line := elseMatches( _Start ) "" htmlEncode( _Comment ) "" } ; make line appear as inline comment else if ( RegExMatch( CurrentLine, "(?P^\s*)(?P;.*)", _ ) && !continuationComment ) { Line := elseMatches( _Start ) "" htmlEncode( _Comment ) "" } else if ( Pos := RegExMatch( CurrentLine, "(?P.*?)(?P\/\*.*)", _ ) ) { continuationComment := true Line := elseMatches( _Start ) "" htmlEncode( _Comment ) "" } else if ( Pos := RegExMatch( MaskedLine, "\*\/" ) ) { continuationComment := false line := RegExReplace( htmlEncode( CurrentLine ), "(^\s*)(.*)", "$1$2`n" ) } else if ( continuationComment ) { line := RegExReplace( htmlEncode( CurrentLine ), "(^\s*)(.*)", "$1$2`n" ) } Else { line := elseMatches( CurrentLine ) } } else if ( ContinuationSection ) { Content := htmlEncode( CurrentLine ) Content := ArrayMatch( Content ) /* * FIXME: Content := getEscapes( Content ) */ Line := "" Content "" } out .= "
    " line "
    `n" } out := "
    FunctionSource
    `n
    " out "
    " ; return the styled definition return lemma table commentBlock returnValue out } ; Matches Percent Signs and AHK-Style-Arrays ArrayMatch( Line ) { While( Pos := RegExMatch( Line, "((?$1", "", 1, Pos ) } ArrayNeedle := "(\w*%\w+%)" While( Pos := RegExMatch( Line, ArrayNeedle, Match, ( !pos ? 1 : pos+StrLen( Match )+25 ) ) ) { Line := RegExReplace( Line, ArrayNeedle, "$1", "", 1, Pos ) } return Line } ; matches any escape sequences getEscapes( Line ) { ; get escaped chars if ( Pos := RegExMatch( Line, "`" ) ) { Line := RegExReplace( Line, "(`.)", "$1" ) } Return Line } /** * Repetitive Matches of same kind for different Hiliting Tasks * * @param CurrentLine * @return String - The processed line */ ElseMatches( CurrentLine ) { Global BalancedQuotes MaskedLine := MaskString( CurrentLine, BalancedQuotes, "MatchBQ" ) Line := htmlEncode(currentLine) Line := getEscapes( Line ) ; match special chars SpecialCharsNeedle := "(((*)?*|\+|\-|\^|(<)?<" . "|(>)?>|(&)?&|\:|\!|\.|=)?=|=|," . "|\.|\+?\+|\-?\-|(*)?*|\~|(<)?<|(>)?>" . "|(/)?/|(&)?&|\!|\?|\[|\]|\{|\}|\(|\)|(\|)?\||\:)" if ( Pos := RegExMatch( MaskedLine, SpecialCharsNeedle ) ) { Line := RegExReplace( Line, SpecialCharsNeedle, "$1" ) } ; match quotes pos := "" While ( Pos := RegExMatch( Line, "("(""|(?!").)*")", match, ( !pos ? 1 : pos+StrLen( Match )+25 ) ) ) { Line := RegExReplace( Line, "("(""|(?!").)*")", "$1", "", 1, Pos ) } pos := "" NumberNeedle := "((0|1|2|3|4|5|6|7|8|9)+)" While ( Pos := RegExMatch( Line, NumberNeedle, match, ( !pos ? 1 : pos+StrLen( Match )+26 ) ) ) { Line := RegExReplace( Line, NumberNeedle, "$1", "", 1, Pos ) } Line := ArrayMatch( Line ) return Line } ; Encode a string to an html-Encoded String ; basically all Chars not being in range of a to z, A to Z, 0 to 9 (and few others) ; get 'translated' to an ASCII Value represented by a Numeric value of the char htmlEncode(str) { ; v 0.4.2 / (w) 21.02.2010 by derRaphael / zLib-Style release Loop,Parse,str if ( ! InStr( "abcdefghijklmnopqrstuvwxyz@;:$§!?\|^'~ ()[]{}#+-_.", A_LoopField ) ) data .= "&#" ASC(A_LoopField) ";" Else data .= A_LoopField return data } ; trims a string trim( str ) { return RegExReplace( str, "sm)^\s*|\s*$" ) } /** * Builds the HTML according to the found data * * @param ProjectTitle - The title of the file or Project * @param index - the previously generated ul-list * @param html - the previously generated html * @return String - The formatted ready to save HTML */ BuildHTML( ProjectTitle, index, OrgHTML, InternalFunDefs = "", LibraryFunDefs = "" ) { ; BuildHashTable of our Functions if ( StrLen( InternalFunDefs ) ) { Loop,Parse,InternalFunDefs,`n,`r { name := RegExReplace( A_LoopField, "(.*)", "$L1" ) hash := crc( name , StrLen( name ) ) _%hash% := A_LoopField _%hash%_link := "" A_LoopField "" } } ; At this point we have a lexed function body ; now lets tear apart all what is unneccessary to check agains our given referencelists Loop,Parse,OrgHTML,`n { Line := A_LoopField if ( RegExMatch( A_LoopField, "
    " ) )
    		{
    			; set mark for function definition
    			if ( RegExMatch( A_LoopField, "
    " ) )
    			{
    				functionStart := true
    			}
    			; remove any HTML tags
    			cLine := RegExReplace( A_LoopField, "<[^>]*>" )
    			; deHTML current line
    			Loop,
    				if ( pos := RegExMatch( cLine, "&#(?P\d+);", match, ( !pos ? 1 : pos+1 ) ) )
    				{
    					cLine := RegExReplace( cLine, "\Q" match "\E", chr(MatchNum), "", 1, pos )
    				}
    				else
    					break
    			; remove any ObscureChars and Quotes
    			cLine := RegExReplace( cLine, BalancedQuotes )
    			cLine := RegExReplace( cLine, "\W", " " )
    			cLine := RegExReplace( cLine, "\s+", " " )
    			cLine := trim( cLine )
    			
    			; Now we only have plain ascii letters left, which we may parse easily agains our libs
    			if ( StrLen( cLine ) )
    			{
    				cLine := RegExReplace( cLine, "(.*)", "$L1" )
    				
    				StringSplit,check,cLine,%A_Space%
    				Loop, % check0
    				{
    					if ( A_Index = 1 && functionStart )
    					{
    						cFun := RegExReplace( check1, "\W", "_" )
    						functionStart := false
    					}
    					
    					hash := CRC( check%A_Index%, Strlen( check%A_Index% ) )
    					if ( StrLen( _%hash% ) )
    					{
    						Line := RegExReplace( Line, "i)(" check%A_Index% ")", _%hash%_link )
    						if ( ! InStr( addenum%cFun%, _%hash%_link ) )
    						{
    							addenum%cFun% .= "
  • " _%hash%_link "
  • " } } } } } HTML .= ( StrLen( HTML ) ? "`n" : "" ) Line } Loop,Parse,InternalFunDefs,`n { cFun := RegExReplace( A_LoopField, "\W", "_" ) if ( RegExMatch( HTML, "\Q\E" ) ) { if ( StrLen( addenum%cFun% ) ) { Replacement := "
    Internal functions used
    `n" . "
      `n" addenum%cFun% "
    `n" . "
    `n" HTML := RegExReplace( HTML, "\Q\E", Replacement ) } } } FormatTime, Date, % A_Now " L1033", dddd MMMM d, yyyy HH:mm tt Out= (Join`n %ProjectTitle%

    Function Index

    %index%

    Functions en detail

    %HTML%
    - End of Documentation -
    Build on %Date% with dR's Doc-O-Matic %Version%
    ) return out } ; Code by Laszlo: ; Taken from http://www.autohotkey.com/forum/viewtopic.php?p=242953#242953 CRC(ByRef Buffer, Bytes=0, Start=-1) { Static CRC32, CRC32LookupTable If (CRC32 = "") { MCode(CRC32,"33c06a088bc85af6c101740ad1e981f12083b8edeb02d1e94a75ec8b542404890c82403d0001000072d8c3") VarSetCapacity(CRC32LookupTable, 1024) DllCall(&CRC32, "uint",&CRC32LookupTable, "cdecl") MCode(CRC32,"558bec33c039450c7627568b4d080fb60c08334d108b55108b751481e1ff000000c1ea0833148e403b450c89551072db5e8b4510f7d05dc3") } If Bytes <= 0 Bytes := StrLen(Buffer) Return DllCall(&CRC32, "uint",&Buffer, "uint",Bytes, "int",Start, "uint",&CRC32LookupTable, "cdecl uint") } ; Code by Laszlo: ; Taken from http://www.autohotkey.com/forum/viewtopic.php?p=242953#242953 MCode(ByRef code, hex) { ; allocate memory and write Machine Code there VarSetCapacity(code,StrLen(hex)//2) Loop % StrLen(hex)//2 NumPut("0x" . SubStr(hex,2*A_Index-1,2), code, A_Index-1, "Char") } esc::ExitApp