# 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
}
