
/* 
 プログラムの実行状況をトレースする
 オリジナルプログラムに手を入れる前処理を行う
 
 オリジナルプログラムの冒頭に
 	#define XXX {fprintf(stderr, "\n@@@ %d %d %d\n",__LINE__,i,j);};
	#define YYY fprintf(stdout,"\n@@@ %d %d %d @@@ ",__LINE__,i,j),
 といったマクロ定義を行い、各行に
 
 main()
 {
   XXX   printf("hello\n");
   XXX   while( j < 9 ) {
   XXX     printf("hello\n");
         }
 }
 
 というように各行前に行位置記録を入れたものに変換する。
 while については
   XXX   while( j < 9 ) { 
 ではなく
         while( YYY j < 9 ) { 
 とした方が
 シミュレーションの見た目は良くなるので
 そのあたりは手作業で対応する

 変換後のプログラムをコンパイルし、実行時に bash にて
 $ ./a.out 2> a.log
 のように実行すればログが得られる。
 変換後のプログラムをコンパイルする際に最適化しないこと
 （実行順序がおかしくなる）
 
 コメント /-* *-/ については事前に uncomment.c によって除去すべし
 
 */

#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>
#include  <unistd.h>
 
void usage() {
fprintf(stderr, "usage: tracer [-vneo] [-[ifcs] var]...\n");
  fprintf(stderr, "   v : verbose mode. (default)\n");
  fprintf(stderr, "   n : non verbose mode.\n");
  fprintf(stderr, "   e : logging destination is stderr. (default)\n");
  fprintf(stderr, "   o : logging destination is stdout.\n");
  fprintf(stderr, "   i : logging variable var as integer (use %%d form)\n");
  fprintf(stderr, "   f : logging variable var as float/real (use %%f form)\n");
  fprintf(stderr, "   c : logging variable var as char (use %%c form)\n");
  fprintf(stderr, "   s : logging variable var as string (use %%s form)\n");
  return;
}

int vmode=1;

/*
 例えば二つの変数をモニタするためには
 #define XXX {fprintf(stderr, "@@@ %d %d %d\n",__LINE__,i,j);};
 といった格好の文字列を合成する。
*/
char logfd[32]; /* stderr or stdout */

char str_def[256], monfmt[256], monvar[256];
char *str_lab="XXX";
char *str_pad="   "; /* パディング用空白 */

/* 
 先頭文字が宣言文、またはブロック { } 文字かどうかをチェック
 戻り値が 0 ならロギング処理付加、1 なら無視 
 （トレース実行には } はチェックした方が良いので } はログ対象とするか？
 　逆に { は不要なうえに main() 直後の { など付けられない場合がある
   ので { はログ対象にできないので注意。）
*/
int checkcom(char *s)
{
  if(strlen(s)==0) { return 1;
  } else if( strncmp(s, "int ", 4) == 0 ) { return 1; 
  } else if( strncmp(s, "char ", 5) == 0 ) { return 1; 
  } else if( strncmp(s, "extern ", 7) == 0 ) { return 1; 
  } else if( strncmp(s, "unsigned ", 9) == 0 ) { return 1; 
  } else if( strncmp(s, "double ", 7) == 0 ) { return 1; 
  } else if( strncmp(s, "float ", 6) == 0 ) { return 1; 
  } else if( strncmp(s, "struct ", 7) == 0 ) { return 1; 
  } else if( strncmp(s, "typedef ", 8) == 0 ) { return 1; 
  } else if( strncmp(s, "case ", 5) == 0 ) { return 1; 
  } else if( strncmp(s, "else ", 5) == 0 ) { return 1; 
  } else if( strncmp(s, "{", 1) == 0 ) { return 1; 
  } else if( strncmp(s, "}", 1) == 0 ) { return 1;
  };
  return 0;
}

/* 
 最初の空白・タブ以外の文字の位置を返す
 先頭なら 0
 全部空白・タブなら -1
*/
int checkline(char *s)
{
  int i, l;

  l=strlen(s);
  for(i=0; i<l; i++) {
    if( (s[i]!=' ' )&&( s[i]!='\t' )) break;
  };
  if(i==l) i= -1;

  return i;
}

/*
 入り口
 */
int main(int argc, char *argv[])
{
  int ch;
  extern char *optarg;
  extern int optind;
  char buf[256];
  int cmode, smode;
  int i, p;

  strcpy(logfd,"stderr"); /* デフォルト出力先は stderr */
  strcpy(monfmt,""); /* デフォルトでモニタする変数はない */
  strcpy(monvar,"");

  /* option recognise */
  while ((ch = getopt(argc, argv, "vneoi:f:c:s:")) != EOF) {
    switch((char)ch) {
    case 'v': /* verbose mode */
      vmode=1;
      break;
    case 'n': /* quiet mode */
      vmode=0;
      break;
    case 'e': /* stderr logging mode */
      strcpy(logfd,"stderr");
      break;
    case 'o': /* stdout logging mode */
      strcpy(logfd,"stdout");
      break;
    case 'i': /* monitoring variable as integer */
      strcat(monfmt," %d");
      sprintf(monvar,"%s,%s",monvar, optarg);
      break;
    case 'f': /* monitoring variable as float */
      strcat(monfmt," %f");
      sprintf(monvar,"%s,%s",monvar, optarg);
      break;
    case 'c': /* monitoring variable as character */
      strcat(monfmt," %c");
      sprintf(monvar,"%s,%s",monvar, optarg);
      break;
    case 's': /* monitoring variable as string */
      strcat(monfmt," %s");
      sprintf(monvar,"%s,%s",monvar, optarg);
      break;
    case '?': /* Help message */
    default:
      usage();
    fprintf(stderr,"tracer : unrecognized parameter exist.\n");
      exit(1);
    };
  };
  /* Skip the position of argc,argv[] */
  argc -= optind;
  argv += optind;

  if(argc!=0) {
    usage();
    fprintf(stderr,"tracer : unrecognized parameter exist.\n");
    exit(1);
  };
  
  /* #define の文字列を作り込む */
  sprintf(str_def, "#define XXX {fprintf(%s,\"\\n@@@ %%d%s @@@ \",__LINE__%s);};", 
	  logfd, monfmt, monvar);
  printf("%s\n", str_def);
  sprintf(str_def, "#define YYY fprintf(%s,\"\\n@@@ %%d%s @@@ \",__LINE__%s),",
	 logfd, monfmt, monvar);
  printf("%s\n", str_def);

  cmode=0; /* out of comment */
  smode=0; /* out of string */

  fgets(buf, sizeof(buf), stdin);
  while( !feof(stdin) ) {
    
    p=checkline(buf); /* 空白以外の文字の場所をチェック */
    switch(p) {
    case -1: /* 空行（または空白だけの行だった）はパス */
      break;
    case 0: /* 行頭から文字あり global section と見てパス */
      printf("%s %s", str_pad, buf);
      break;
    default: /* インデントあり。行頭に付加 */
      if( checkcom(&buf[p]) == 1 ) { /* 宣言文、中カッコだった */
	printf("%s %s", str_pad, buf); /* パス */
      } else {
	printf("%s %s", str_lab, buf); /* ロギング処理付加 */
      };
    };
    fgets(buf, sizeof(buf), stdin);
  };

  return 0;
}
