igor-public/doc/doxygen-filter-ipf.awk

243 lines
6.2 KiB
Awk

# Proof of concept implementation for using doxygen to document Igor Pro procedures
# This awk script serves as input filter for Igor procedures and produces a C-ish version of the declarations
# Tested with Igor Pro 6.34A and doxygen 1.8.7
#
# Thomas Braun: 9/2014
# Version: 0.23
# Supported Features:
# -Functions
# -Macros
# -File constants
# -Menu items are currently ignored
# TODO
# - don't delete the function/macro subType
BEGIN{
# allows to bail out for code found outside of functions/macros
DO_WARN=0
IGNORECASE=1
output=""
warning=""
menuEndCount=0
}
# Remove whitespace at beginning and end of string
# Return the whitespace in front of the string in the
# global variable frontSpace to be able
# to reconstruct the indentation
function trim(str)
{
if(match(str, /^[[:space:]]+/))
{
frontSpace = substr(str, 1, RLENGTH)
str = substr(str, RLENGTH + 1)
}
else
frontSpace = ""
gsub(/[[:space:]]+$/,"",str)
return str
}
# Split an already trimmed line into words
# Returns the number of words
function splitIntoWords(str, a, numEntries)
{
return split(str,a,/[[:space:],&*]+/)
}
# Split params into words and prefix each with "__Param__$i"
# where $i is increased for every parameter
# Returns the concatenation of all prefixed parameters
function handleParameter(params, a, i, iOpt, str, entry)
{
numParams = splitIntoWords(params, a)
str=""
entry=""
iOpt=numParams
for(i=1; i <= numParams; i++)
{
# convert igor optional parameters to something doxygen understands
# igor dictates that the optional arguments are the last arguments,
# meaning no normal argument can follow the optional arguments
if(gsub(/[\[\]]/,"",a[i]) || i > iOpt)
{
iOpt = i
entry = a[i] " = defaultValue"
}
else
entry = a[i]
str = str "__Param__" i " " entry
if(i < numParams)
str = str ", "
}
return str
}
{
# split current line into code and comment
if(match($0,/\/\/.*/))
{
code=substr($0,0,RSTART-1)
comment=substr($0,RSTART,RLENGTH)
}
else
{
code=$0
comment=""
}
# remove whitespace from front and back
code=trim(code)
# begin of macro definition
if(!insideFunction && !insideMacro && ( match(code,/^Window/)|| match(code,/^Proc/) || match(code,/^Macro/) ) )
{
insideMacro=1
gsub(/^Window/,"void",code)
gsub(/^Macro/,"void",code)
gsub(/^Proc/,"void",code)
# add opening bracket, this also throws away any function subType
gsub(/\).*/,"){",code)
}
# end of macro definition
else if(!insideFunction && insideMacro && ( match(code,/^EndMacro$/) || match(code,/^End$/) ) )
{
insideMacro=0
code = "}"
}
# begin of function declaration
else if(!insideFunction && ( match(code,/[[:space:]]function[\/[[:space:]]/) || match(code,/^function[\/[[:space:]]/) ) )
{
insideFunction=1
# remove whitespace between function and return type flag
gsub(/function[[:space:]]*\//,"function/",code)
# different return types
gsub(/function /,"variable ",code)
gsub(/function\/df/,"dfr",code)
gsub(/function\/wave/,"wave",code)
gsub(/function\/c/,"complex",code)
gsub(/function\/s/,"string",code)
gsub(/function\/t/,"string",code) # deprecated definition of string return type
gsub(/function\/d/,"variable",code)
# add opening bracket, this also throws away any function subType
gsub(/\).*/,"){",code)
# do we have function parameters
if(match(code,/\(.*[a-z]+.*\)/))
{
paramStr = substr(code,RSTART+1,RLENGTH-2)
paramStrWithTypes = handleParameter(paramStr, params)
paramsToHandle = numParams
# print "paramStr __ " paramStr
# print "paramStrWithTypes __ " paramStrWithTypes
# print "paramsToHandle __ " paramsToHandle
code = substr(code,0,RSTART) "" paramStrWithTypes "" substr(code,RSTART+RLENGTH-1)
}
}
else if(insideFunction && paramsToHandle > 0)
{
numEntries = splitIntoWords(code,entries)
# printf("Found %d words in line \"%s\"\n",numEntries,code)
for(i=2; i <= numEntries; i++)
for(j=1; j <= numParams; j++)
{
variableName = entries[i]
if( entries[i] == params[j] )
{
paramsToHandle--
# now replace __Param__$i with the real parameter type
if(entries[1] == "struct")
paramType = entries[2]
else
paramType = tolower(entries[1])
# add asterisk for call-by-reference parameters
if(match(code,/\&/))
paramType = paramType "*"
output = gensub("__Param__" j " ",paramType " ","g",output)
# printf("Found parameter type %s at index %d\n",paramType,j)
}
}
}
# end of function declaration
else if(insideFunction && match(code,/^end$/))
{
insideFunction=0
code = "}"
}
# structure declaration
if(!insideFunction && !insideMacro && ( match(code,/[[:space:]]structure[[:space:]]/) || match(code,/^structure[[:space:]]/) ) )
{
insideStructure=1
gsub(/structure/,"struct",code)
code = code "{"
}
if(insideStructure && match(code,/EndStructure/))
{
insideStructure=0
code = "}"
}
# menu definition
# submenues can be nested in menus. Therefore we have to keep track
# of the number of expected "End" keywords
if(!insideFunction && !insideMacro && ( match(code,/\yMenu\y/) || match(code,/\ySubMenu\y/) ))
{
menuEndCount++
insideMenu=1
}
if(insideMenu && match(code,/\yEnd[[:space:]]*/))
{
menuEndCount--
if(menuEndCount == 0)
{
insideMenu=0
code = ""
}
}
# global constants
gsub(/\ystrconstant\y/,"const string",code)
gsub(/\yconstant\y/,"const variable",code)
# prevent that doxygen sees elseif as a function call
gsub(/\yelseif\y/,"else if",code)
# code outside of function/macro definitions is "translated" into statements
if(!insideFunction && !insideMacro && !insideMenu && code != "" && substr(code,0,1) != "#")
{
if(code != "}" && !insideStructure && DO_WARN)
warning = warning "\n" "warning " NR ": outside code \"" code "\""
code = code ";"
}
if(!insideMenu)
{
output = output frontSpace code comment
}
output = output "\n"
}
END{
print output
if(DO_WARN)
print warning
}