//
// Copyright (C) 1991 Texas Instruments Incorporated.
//
// Permission is granted to any individual or institution to use, copy, modify,
// and distribute this software, provided that this complete copyright and
// permission notice is maintained, intact, in all copies and supporting
// documentation.
//
// Texas Instruments Incorporated provides this software "as is" without
// express or implied warranty.
//
// Created: MBN 06/05/89 -- Initial design and implementation.
// Changed: MJF 05/07/90 -- Fixed keyargs in constructors and member functions.
//
// The KEYARGS  defmacro implements a KEY argument  feature  in C++ similar to
// the &key of Common Lisp. To use, include  the "misc.h" header file and make
// sure  that you have the  environment variable "cppC"  to  point to the COOL
// preprocessor in  /tan/u1/cool/cpp/cpp.   Then, define your function(s) with
// KEYARGS and use, as in the example below:
// 
//     KEYARGS Boolean set_val (int size, KEY: int low = 0, int high = 100) {
//     ...
//     ...
//     }
// 
// This defines  the  function  set_value returning a  Boolean   with two  key
// arguments `low' and `high'. The  first argument  in this case happens to be
// required, but basically this argument list could be  any valid C++ argument
// list. Later  in the application, a call  to  this function  could look like
// this:
// 
//    if (set_value (512, high=1024) == TRUE) {
//       ...
//       ...
//    }
// 
// This says "make a call to set_value, using 512 for  the first  argument and
// using the value 1024 for the key argument  `high'. The value of `low' would
// be  the default value  0. Thus, the  above usage  would be  replaced in the
// program with:
// 
//    if (set_value (512, 0, 1024) == TRUE) {
//       :::
//       :::
//    }
// 
// This file implements the expansion of KEYARGS function definition.
//
//   KEYARGS ftype fname (parms) [{...}];
//
// where parms = [KEY:] type name [= default] ,  parms
//
// is replaced with:
//   type fname (new_parms) [{...}];
//
// where new_parms = type name [= default] , new_parms
//
// followed by the #pragma definition:
//   #pragma defmacro fname <cool/expand_key> delimiter=) condition=(
//                          key_arg eparms
// where key_arg is index to first key argument
// and eparms  = name [= default] , new_eparms
//
// The "#pragma defmacro fname" will invoke the file <cool/expand_key>
// which is called to expand any calls of "fname" with key argments.
//
// As far as the programmer is concerned, there is no  need for s/he  to write
// any #pragma  statements or be  concerned about the  defmacro facility.  The
// KEY: usage simply appears to be  part of  the  language.  In addition, this
// functionality should work in all cases, such as private methods of a class,
// inlines, and function prototypes in a header file.
//

#include <cool/String.h>

#if defined(DOS)
extern "C" {
#include <ctype.h>
#include <stdlib.h>	// defines exit() and abort()
}
#else
#include <ctype.h>
#include <stdlib.h>	// defines exit() and abort()
#endif

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

//
// Skip whitespace characters, returning the next character
//
int
skipws(char c)
{
  while (isspace(c))
    cin.get(c);
  return(c);
}

//
// Copy the next alphanumeric token to the end of string.
// The argument, c, is the first character.
// Returns the next non-whitespace character.
// Dies if at least one character isn't copied.
//
int gettoken (char c, String& string) {
  int is_bad = TRUE;
  c = skipws (c);
  while (isalnum (c) || (c == '$') || (c == '_') || c == '*') {
    string += c;
    cin.get (c);
    is_bad = FALSE;
  }
  if (is_bad) {
    fprintf (stderr, "KEYARGS: Illegal character '%c' in token\n", c);
    exit (1);
  }
  return (skipws(c));
}

int main (void) {
  int arg_count = 3;		// Arguments start after these three paramters:
				//    <cool/expand_key> key_arg_index fname
  int key_arg_index = 0;	// Argument index to where KEY: starts
  char c;			// Current delimiter character
  String cname;			// Class name
  String fname;			// Function name
  String type;			// Function return type
  String parms;			// Function parameters
  String new_func_defn;		// Modified function definition
  String temp;			// Buffer of stuff to ignore

  cout << "\n";				// Start on a new line
  c = gettoken (' ', temp);		// Skip the macro name, "KEYARGS"
  if (!isalpha (c)) {			// Next char should be alphanumeric
    fprintf(stderr,
	    "KEYARGS: Syntax error: Illegal character '%c' after KEYARGS identifier\n",
	    c);
    exit (1);
  }  

  c = gettoken (c, type);	// Get function type
  if (c == ':'|| c== '(') {	// Either function name, class constructor
    fname = type;		// or member function with no type
    type = "";
  }  else {			// function name with type
    c = gettoken (c, fname);	// Get function name 
  }

  if (c == ':') {		// class_name::member_function
    cin.get(c);
    if (c != ':') {
      fprintf(stderr,
	      "KEYARGS: Syntax error: Illegal character '%c' after ':'\n",
	      c);
      exit (1);
    }
    cname = fname + "::";
    fname.clear();
    c = gettoken (' ', fname);	// Get member function name or constructor
  } else {
    cname = "";
  }

  if (c != '(') {		// Check for argument list
    fprintf (stderr,
	    "KEYARGS: Syntax error, missing '(' after function name %s\n",
	    (char*) fname);
    exit (1);
  }

  new_func_defn = type + ' ' + cname + fname + " ("; // new function definition
  parms = fname + " ";

  while (TRUE) {			// While there are still characters
    temp.clear ();
    c = gettoken (' ', temp);		// Get next token in parm list
    if (temp == "KEY" && c == ':') {    // If found KEY: in parm list
      c = ' ';
      key_arg_index = arg_count;	// Save index to first key argument
      continue;				// and continue
    }
    else
      new_func_defn += temp;		// Add the argument type to func def

    if (c == ',' && key_arg_index == 0) {  //If not a key arg
      parms += " , ";
      new_func_defn += ", ";
      arg_count++;
      continue;
    }

    temp.clear();
    c = gettoken (c, temp);		// Get the argument name
    parms += " ";
    parms += temp;			// Add the argument name to parmlist
    new_func_defn += " ";
    new_func_defn += temp;		// Add the argument name to func def
    arg_count++;			// Advance past "name"

    if (key_arg_index && c != '=') {	// If after KEY:,  but no =value 
      fprintf (stderr,
            "KEYARGS: Syntax error, missing '=' after keyword argument '%s'\n",
	    (char*) temp);
      exit (1);
    }

    if (c == '=') {			// A default value provided
      temp.clear();
      c = gettoken (' ', temp);		// Get the default argument value
      parms += " = ";			// Add default value to parmlist
      parms += temp;
      new_func_defn += " = ";		// Add the default value to func def
      new_func_defn += temp;
      arg_count += 2;			// Advance past "=" and "value"
    }

    if (c == ')') {			// Finished argument list
      new_func_defn += ")";
      break;
    }

    if  (c != ',') {
     fprintf (stderr, "KEYARGS: Syntax error, missing comma after argument\n");
     exit (1);
    }
    else {
      parms += " , ";				// Add "," to parmlist
      new_func_defn += ", ";			// and func def
      arg_count++;				// Advnace past ","
    }
  }

  cout << "\n#undef " << fname << "\n";	    // Undef any macro definition
  cout << new_func_defn << "\n";	    // Expand new func definition
  cout << "#pragma defmacro " << fname << " <cool/expand_key> delimiter=)";
  cout << " condition= ( " << key_arg_index << " " << parms << "\n";

  return 0;
}
