/* $Id: fnbuf.c,v 1.3 2008/11/02 18:47:41 kiesling Exp $ */

/*
  This file is part of ctalk.
  Copyright © 2005-2008 Robert Kiesling, ctalk@ctalklang.org.
  Permission is granted to copy this software provided that this copyright
  notice is included in all source code modules.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software Foundation, 
  Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "lex.h"

extern int library_input;                    /* Declared in class.c.      */
extern int error_line, error_column;         /* Declared in errorloc.c.   */
extern char input_source_file[FILENAME_MAX]; /* Declared in main.c.       */

static char *function_buffer[MAXMSG+1];      /* Buffer for fn statements, */
static int function_buffer_ptr;

int buffer_function_output = FALSE;


static int fn_start_line; /* Because functions are only buffered for source */
                          /* files, we should only have to record the line */
                          /* of the function start.  TO DO - Add line info  */
                          /* for each function_buffer[] statement.          */

extern int nolinemarker_opt; /* Declared in main.c                          */
extern I_PASS interpreter_pass;

extern char declarators[10][MAXLABEL]; /* Declared in cparse.c.             */
extern int n_declarators;

char fn_name[MAXLABEL];

void begin_function_buffer (void) {

  /* 
   *    Don't reset the pointer if already buffering; e.g., from
   *    the main () start message, determined in the first pass.
   */
  if (!buffer_function_output) {
    buffer_function_output = TRUE;
    fn_start_line = error_line;
    function_buffer_ptr = 0;
    strcpy (fn_name, declarators[n_declarators - 1]);
  }
}

void end_function_buffer (void) {
  buffer_function_output = FALSE;
  unbuffer_function_output ();
}

void buffer_function_statement (char *s) {
  if (!library_input && (interpreter_pass != c_fn_pass))
    function_buffer[function_buffer_ptr++] = strdup (s);
}

static MESSAGE *fn_messages[P_MESSAGES+2]; /* Function message stack.      */
                                           /* P_MESSAGES+2 avoids an       */
                                           /* overrrun issue on some older */
                                           /* gcc's.                       */
/*  static MESSAGE *fn_messages[P_MESSAGES+1]; */ /* Function message stack.      */
static int fn_message_ptr = P_MESSAGES;    /* Stack pointer.               */

MESSAGE **fn_message_stack (void) { return fn_messages; }

int get_fn_messageptr (void) { return fn_message_ptr; }

int fn_message_push (MESSAGE *m) {
  if (fn_message_ptr == 0) {
    _warning (_("fn_message_push: stack overflow.\n"));
    return ERROR;
  }
#ifdef FN_STACK_TRACE
  debug (_("fn_message_push %d. %s."), fn_message_ptr, m -> name);
#endif
  fn_messages[fn_message_ptr--] = m;
  return fn_message_ptr + 1;
}

MESSAGE *fn_message_pop (void) {
  MESSAGE *m;
  if (fn_message_ptr > P_MESSAGES) {
    _warning (_("fn_message_pop: stack underflow.\n"));
    return (MESSAGE *)NULL;
  }
#ifdef MACRO_STACK_TRACE
  debug (_("fn_message_pop %d. %s."), fn_message_ptr, 
	 fn_messages[fn_message_ptr+1] -> name);
#endif
  if (((fn_message_ptr + 1) <= P_MESSAGES) &&
      fn_messages[fn_message_ptr + 1] && 
      IS_MESSAGE(fn_messages[fn_message_ptr + 1])) {
    m = fn_messages[fn_message_ptr + 1];
    fn_messages[++fn_message_ptr] = NULL;
    return m;
  } else {
    fn_messages[++fn_message_ptr] = NULL;
    return NULL;
  }
}

/* 
 *  Collect the interpreted function, retokenize, insert the 
 *  init header, and output.
 */

void unbuffer_function_output (void) {

  int i,
    stack_start,
    stack_end;
  int need_main_init;
  int old_error_line,
    old_error_column,
    var_decl_end, 
    lookahead;
  long long buflength;
  char *tmpbuf;
#ifndef __DJGPP__
  char linebuf[FILENAME_MAX];
#endif
  Boolean need_init;
  enum {
    fn_decl,
    fn_var_decl,
    fn_body,
    fn_null
  } fn_state;
  MESSAGE *m;

  /* 
   *  TO DO - 
   *  Figure out how the line markers work in DJGPP 
   *  before using them.  
   */

#ifndef __DJGPP__
  if (interpreter_pass == parsing_pass && !input_is_c_header ()) {
    if (!nolinemarker_opt) {
      fmt_line_info (linebuf, fn_start_line, input_source_file, 1, FALSE);
      __fileout (linebuf);
    }
  }
#endif

  old_error_line = error_line;
  old_error_column = error_column;

  error_line = fn_start_line;
  error_column = 1;

  /* Calculate the length of the buffer for retokenization. */
  for (i = 0, buflength = 0LL; i < function_buffer_ptr; i++)
    buflength += strlen (function_buffer[i]);

  buflength += strlen (MAIN_ARGS);

  if ((tmpbuf = (char *)calloc (buflength + 1, sizeof (char))) == NULL)
    _error ("unbuffer_function_output: %s\n", strerror (errno));
			 
  /* Collect the function statements in the buffer. */

  /*  NOTE -
   *  The function closure may not be buffered, because 
   *  parser_pass uses it to end the function buffering 
   *  and call this function.  
   *  
   *  It may be output separately after the function is
   *  output.
   */

  /* TO DO -
     Make sure that "main" is always found, even if it is not in
     its own buffer. */
  for (i = 0, *tmpbuf = 0, need_main_init = 0; i < function_buffer_ptr; i++) {
    if (!strcmp (function_buffer[i], "main")) {
      need_main_init = TRUE;
      i = main_args (tmpbuf, i);
      i++;
      continue;
    }
    strcat (tmpbuf, function_buffer[i]);
    free (function_buffer[i]);
  }
  function_buffer_ptr = 0;

  if (input_is_c_header ()) {
    __fileout (tmpbuf);
    free (tmpbuf);
    return;
  }

  stack_start = P_MESSAGES;

  if ((stack_end = tokenize (fn_message_push, tmpbuf)) == ERROR) {
    free (tmpbuf);
    return;
  }

  free (tmpbuf);

  for (i = stack_start, fn_state = fn_null, need_init = True;
       i >= stack_end; i--) {

    switch (fn_messages[i] -> tokentype)
      {
      case LABEL:
      case CTYPE:
	/*
	 *  TO DO - The first clause can sometimes
	 *  be taken after a random identifier.  Try to 
	 *  locate the exceptions, or try fix 
	 *  is_c_fn_declaration_msg (). 
	 *
	 *  Note that is_c_var_declaration_msg () can change a 
	 *  C keyword's token to CTYPE, so handle those here
	 *  also.
	 */
	var_decl_end = ERROR;
	if (is_c_fn_declaration_msg (fn_messages, i)) {
 	  for ( ; fn_messages[i] && IS_MESSAGE (fn_messages[i]) &&
			fn_messages[i] -> tokentype != OPENBLOCK; i--) 
 	    __fileout (fn_messages[i] -> name);
	  if (fn_messages[i] && IS_MESSAGE (fn_messages[i]))
	    __fileout (fn_messages[i] -> name);
	} else {
	  if (is_c_var_declaration_msg (fn_messages, i, FALSE)) {
	    var_decl_end = find_declaration_end (fn_messages, i);

	    /*
	     *  After exiting from this loop, i is set incorrectly
	     *  for the next iteration.  It gets reset correctly
	     *  at the break, below.
	     */
 	    for ( ; (i >= var_decl_end) && fn_messages[i]; i--)
	      __fileout (fn_messages[i] -> name);

	    fn_state = fn_var_decl;
	    /*
	     * Look ahead to the next language message.  If the token
	     * does not begin a variable declaration, output the init
	     * block immediately.
	     *
	     * It should be okay to change fn_state to fn_body here,
	     * but this loop's states have been kept lazy, so there 
	     * are several tests whether the init block should be 
	     * output, and generate the block if the loop doesn't 
	     * recognize a construct.
	     */
 	    if ((lookahead = nextlangmsg (fn_messages, i + 1)) != ERROR) {
 	      if (!is_c_var_declaration_msg (fn_messages, lookahead, FALSE)) {
 		if (need_init) {
 		  need_init = False;
 		  generate_init (need_main_init);
#ifndef __DJGPP__
		  if (interpreter_pass == parsing_pass) {
		    if (!nolinemarker_opt) {
		      fmt_line_info (linebuf, 
				     fn_messages[i] -> error_line, 
				     input_source_file, 1, FALSE);
		      __fileout (linebuf);
		    }
		  }
#endif
 		}
 	      }
 	    }
	  } else {
	    if (fn_state != fn_body) {
	      fn_state = fn_body;
	      if (need_init) {
		need_init = False;
		generate_init (need_main_init);
#ifndef __DJGPP__
		if (interpreter_pass == parsing_pass) {
		  if (!nolinemarker_opt) {
		    fmt_line_info (linebuf, 
				   fn_messages[i] -> error_line, 
				   input_source_file, 1, FALSE);
		    __fileout (linebuf);
		  }
		}
#endif
	      }
	    }
	    /*
	     *  Handle a return block here, to make sure that 
	     *  it is output after the init block, even in 
	     *  an empty function.
	     */
	    if (!strcmp (fn_messages[i] -> name, "return") ||
		!strcmp (fn_messages[i] -> name, "exit")) {
 	      char buf[MAXMSG];
 	      sprintf (buf, "\n{ __ctalk_exitFn (%d); }\n",
		       ((need_main_init) ? TRUE : FALSE));
 	      __fileout (buf);
#ifndef __DJGPP__
	      if (interpreter_pass == parsing_pass) {
		if (!nolinemarker_opt) {
		  fmt_line_info (linebuf, 
				 fn_messages[i] -> error_line, 
				 input_source_file, 1, FALSE);
		  __fileout (linebuf);
		}
	      }
#endif
	    }
	    __fileout (fn_messages[i] -> name);
	  }
	}
	/*
	 *  If we needed to look past a var declaration above,
	 *  make sure that the stack pointer is set correctly
	 *  for the next iteration.
	 */
 	if (var_decl_end != ERROR)
 	  i = var_decl_end;
	break;
      default:
	__fileout (fn_messages[i] -> name);
	break;
      }
  }

  /*
   *  If the function is unbuffered and we still haven't output the
   *  init or exit blocks, output them here, before the frame with the
   *  function closure is output.
   */

  if (need_init) {
    need_init = False;
    generate_init (need_main_init);
  }

  while ((m = fn_message_pop ()) != NULL)
    delete_message (m);
  fn_message_ptr = P_MESSAGES;

  error_line = old_error_line;
  error_column = old_error_column;
}

/*
 *  If the function contains a return statement, then
 *  unbuffer_function_output () has output __ctalk_exitFn ()
 *  immediately before it.  
 *
 *  Parser_pass () calls fn_return_at_close () on the frame 
 *  that contains the closing brace of the function, which is 
 *  not included in the buffered function output.
 *
 *  This is a kludge.  There should be a way to keep track
 *  of function info the same way that METHOD types keep track
 *  of methods.  However, if there is a control structure at
 *  the end of a function, and no return, then the parser
 *  would see the last statement within the final clause of the
 *  control structure.  So the parser has to wait until the
 *  final closing brace is output.
 *
 *  Instead, for the moment, the function only looks back through 
 *  two semicolons for a return statement.  If a return statement 
 *  exists, then __ctalk_exitFn () was output when the function
 *  was unbuffered.  If there is no return statement, output
 *  __ctalk_exitFn () here.
 *
 *  FIXME! - Also make sure that there are __ctalk_exitFn () blocks 
 *  for all return statements within the function.
 */
int fn_return_at_close (MESSAGE_STACK messages, int ptr, int main_exit) {

  int i,
    n_semicolons,
    stack_begin;
  Boolean have_return;

  if (interpreter_pass == method_pass)
    return SUCCESS;

  stack_begin = stack_start (messages);

  for (i = ptr, n_semicolons = 0, have_return = False; 
       (i <= stack_begin) && n_semicolons < 2; i++) {
    if (messages[i] -> tokentype == SEMICOLON)
      ++n_semicolons;
    if (!strcmp (messages[i] -> name, "return") ||
	!strcmp (messages[i] -> name, "exit"))
      have_return = True;
  }

  if (!have_return)
    generate_fn_return (messages, ptr, ((main_exit) ? TRUE : FALSE));
  return SUCCESS;
}

int main_args (char *buf, int ptr) {
  int i;
  Boolean in_args;

  for (i = ptr, in_args = False; ; i++) {

    if (!strcmp (function_buffer[i], "(")) {
      strcat (buf, function_buffer[i]);
      free (function_buffer[i]);
      strcat (buf, MAIN_ARGS);
      in_args = True;
      continue;
    }

    if (!strcmp (function_buffer[i], ")")) {
      strcat (buf, function_buffer[i]);
      free (function_buffer[i]);
      return i;
    }

    if (in_args == False)
      strcat (buf, function_buffer[i]);

    free (function_buffer[i]);

  }
  return ERROR;
}

int input_is_c_header (void) {
  char *dot;
  if (((dot = rindex (__source_filename (), '.')) != NULL)&&
      (*(dot + 1) == 'h'))
    return TRUE;
  return FALSE;
}
