/* $Id: loop.c,v 1.4 2009/02/08 16:43:04 kiesling Exp $ */

/*
  This file is part of Ctalk.
  Copyright © 2005-2009 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.
*/

#define FOR_INIT_BLOCK "\n\
{\n\
  %s\n\
  %s\n\
  %s\n\
}\n"

#define FOR_INC_BLOCK FOR_INIT_BLOCK

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"
#include "parser.h"
#include "ctrlblk.h"

extern CTRLBLK *ctrlblks[MAXARGS + 1];  /* Declared in control.c.         */
extern int ctrlblk_ptr;
extern int ctrlblk_pred, ctrlblk_blk;

int do_predicate;                       /* Tell ctrlblk_state () that 
					   we should not change the control
					   block state while parsing a 
					   do predicate in its own frame. */

void init_loops (void) {
  do_predicate = FALSE;
}

int loop_pred_end (MESSAGE_STACK messages, int ptr) {

  CTRLBLK *c;
  LIST *l;
  int i;
  char arg_push_buf[MAXMSG],
    method_buf[MAXMSG],
    arg_pop_buf[MAXMSG],
    blk_buf[MAXMSG];

  if (ctrlblk_ptr == MAXARGS)
    return ERROR;

  c = ctrlblks[ctrlblk_ptr + 1];

  /*
   *  Insert the initialization predicats of a for loop.
   */

  if (c -> stmt_type == stmt_for) {
    if (c -> for_init_methods != NULL) {

      /*
       *  Collate the initialization method calls.
       */
      for (l = c -> for_init_args, *arg_push_buf = 0; l; l = l -> next)
	strcat (arg_push_buf, (char *)l -> data);
      /*
       *  Kludge here - insert an extra semicolon for the method call
       *  due to the context registering as C context.
       *  Method_call also puts a __ctalk_to_c_int () wrapper around 
       *  the method call, but because we don't need the return value
       *  we can ignore it.      
       */
      for (l = c -> for_init_methods, *method_buf = 0; l; l = l -> next) {
	strcat (method_buf, (char *)l -> data);
        strcat (method_buf, ";\n");
      }
      for (l = c -> for_init_cleanup, *arg_pop_buf = 0; l; l = l -> next)
	strcat (arg_pop_buf, (char *)l -> data);

        sprintf (blk_buf, FOR_INIT_BLOCK, arg_push_buf, method_buf, arg_pop_buf);
        fileout (blk_buf, FALSE, c -> keyword_ptr);

      for (i = c -> for_init_start; i >= c -> for_init_end; i--) {
	++(message_stack_at (i) -> evaled);
	++(message_stack_at (i) -> output);
      }

    }
  }

  if (c -> stmt_type == stmt_do) {
    if (! c -> pred_expr_evaled && 
	expr_has_objects (messages, 
			  c -> pred_start_ptr - 1, 
			  c -> pred_end_ptr + 1)) {
      fileout 
	(fmt_default_ctrlblk_expr 
	 (messages, c -> pred_start_ptr - 1, 
	  c -> pred_end_ptr + 1, FALSE), FALSE, 
	       c -> pred_start_ptr - 1);
      c -> pred_expr_evaled = TRUE;
    }
  }
  return SUCCESS;
}

static void comma_op_buf (char *s) {

  char *p;

  while ((p = index (s, ';')) != NULL)
    *p = ',';

  while ((p = index (s, '\n')) != NULL)
    *p = ' ';

}

int loop_block_start (MESSAGE_STACK messages, int ptr) {

  CTRLBLK *c;
  char arg_push_buf[MAXMSG],
    blk_buf[MAXMSG],
    *expr_buf;
  int i;
  LIST *l;

  if (ctrlblk_ptr == MAXARGS)
    return ERROR;

  c = ctrlblks[ctrlblk_ptr + 1];

  /*
   *  If, while, and do statements only need braces around the loop
   *  block in case the interpreter places function calls within
   *  the block.
   */

  if ((c -> stmt_type == stmt_if)) {
    if (!c -> braces)
      fileout ("\n{\n", FALSE, c -> blk_start_ptr);
  }
      
  if (c -> stmt_type == stmt_while) {
    if ((!c -> braces) && (M_TOK(messages[c -> blk_start_ptr]) != SEMICOLON))
      fileout ("\n{\n", FALSE, ptr);

    /*
     *   Check that the expression contains objects or if the
     *   expression has a C variable and operator before the
     *   object, in which case the expression is evaluated and
     *   output in default_method () in rexpr.c.
     */
    if (! c -> pred_expr_evaled && 
	expr_has_objects (messages, 
			  c -> pred_start_ptr - 1, 
			  c -> pred_end_ptr + 1)) {

/*       fileout (fmt_libc_fn_rt_expr (messages, c -> pred_start_ptr - 1,  */
/* 			    c -> pred_end_ptr + 1), FALSE,  */
/* 	       c -> pred_start_ptr - 1); */
      fileout 
	(fmt_default_ctrlblk_expr 
	 (messages, c -> pred_start_ptr - 1, 
	  c -> pred_end_ptr + 1, FALSE), FALSE, 
	 c -> pred_start_ptr - 1);

      for (i = c -> pred_start_ptr - 1; i >= c -> pred_end_ptr + 1; i--) {
	++messages[i] -> evaled;
	++messages[i] -> output;
      }
      c -> pred_expr_evaled = TRUE;

    } /* if (expr_has_objects (messages, c -> pred_start_ptr - 1, ...  */
  } /* if (c -> stmt_type == stmt_while) */

  if (c -> stmt_type == stmt_do) {
    if (!c -> braces) 
       fileout ("\n{\n", FALSE, c -> blk_start_ptr + 1);
  }

  if (c -> stmt_type == stmt_for) {

    if (c -> braces == FALSE) {           /* Insert opening brace if  */
      fileout ("\n{\n", FALSE, ptr);  /*  necessary.     */
    }

    /*
     *  TO DO - Try replacing with format_rt_expr ().
     */
    if (c -> for_term_methods != NULL) { /*  Otherwise don't modify loops that
					     don't have objects in the
					     predicate. */
      expr_buf = collect_tokens (messages, c -> for_term_start, 
				 c -> for_term_end);

      messages[c -> for_term_start] -> name[0] = 0;

      /*
       *  TO DO - this is temporary until we figure out exactly
       *  what args to push in control.c, and then it should
       *  be only the CVAR registration calls.
       */
      for (l = c -> for_term_args, *arg_push_buf = 0; l; l = l -> next)
	if (!strncmp ((char *)l -> data, REGISTER_C_METHOD_ARG, 
		      strlen (REGISTER_C_METHOD_ARG)))
	  strcat (arg_push_buf, (char *)l -> data);

      if (strlen (arg_push_buf)) {
	comma_op_buf (arg_push_buf);
	sprintf (M_NAME(messages[c->for_term_start]), "%s ", arg_push_buf);
      }
      de_newline_buf (expr_buf);
      sprintf (blk_buf, "__ctalkToCInteger(%s,%d)", 
	       fmt_eval_expr (expr_buf), FALSE);
      free (expr_buf);
      strcat (arg_push_buf, blk_buf);
      fileout (arg_push_buf, FALSE, c -> for_term_start);
    }
  }

  return SUCCESS;
}

/*
 *   Here check all of the loops if they are nested.  If nested
 *   loops don't have braces, then the loops' blk_end_ptr will
 *   be the same - the semicolon that terminates the loop body
 *   statement.
 *
 *   "Else," clauses if in, "if... else," statements get braces
 *   here also if necessary.
 */

int loop_block_end (MESSAGE_STACK messages, int ptr) {

  CTRLBLK *c;
  char end_buf[MAXMSG];
  int blk_ptr;
  char arg_push_buf[MAXMSG],
    method_buf[MAXMSG],
    arg_pop_buf[MAXMSG],
    blk_buf[MAXMSG];
  LIST *l;

  if (ctrlblk_ptr == MAXARGS)
    return ERROR;

  for (blk_ptr = ctrlblk_ptr + 1; blk_ptr <= MAXARGS; blk_ptr++) {

    c = ctrlblks[blk_ptr];

    if (c -> blk_end_ptr != ptr)
      continue;

    if (c -> level < current_parser () -> level)
      break;

    /*
     *  If statements without braces also need braces
     *  around an else clause.  The, "else," keyword and
     *  the block are in the next frame, so scan for them individually.
     */

    if (c -> stmt_type == stmt_if) {
      if (c -> braces == FALSE) {
  
        /*
         *  If there's an, "else," clause, anchor the
         *  closing brace on the else keyword.  Otherwise,
         *  anchor it on the end of the statement.
         *
         *  Also anchor the opening brace of the, "else,"
         *  clause on the, "else," keyword.
         *
         *  Anchoring the braces in this manner allows
         *  the code generation in method_args () and
         *  elsewhere to only worry about placing library
         *  calls at the beginning of the source statement's 
         *  frame.
         *
         *  If_else_end (), below, handles the end of an,
         *  "else," clause, in case one of the method functions
         *  needs to anchor a library call on the close of
         *  a single-line, "else," block, which is the
         *  semicolon that terminates the statement.
         */

        if (c -> else_start_ptr == 0) {
          strcpy (end_buf, "\n}\n\n");
          fileout (end_buf, FALSE, ptr - 1);
        } else {
          strcpy (end_buf, "\n}\n ");
          fileout (end_buf, FALSE, c -> keyword2_ptr);

          strcpy (end_buf, " {\n");
          fileout (end_buf, FALSE, c -> keyword2_ptr - 1);
        }      
      }
    }

    if (c -> stmt_type == stmt_while) {
      /*
       *  Also handle case where loop is empty.
       */
      if (c -> braces == FALSE && (c -> blk_start_ptr != c -> blk_end_ptr)) {
        fileout ("\n}\n", FALSE, c -> blk_end_ptr - 1);
      }
    }

    if (c -> stmt_type == stmt_do) {
      *end_buf = 0;
      if (c -> pred_args_d) {
        strcpy (end_buf, "\n  {\n");
        for (l = c -> pred_args, *method_buf = 0; l; l = l -> next) {
          strcat (end_buf, "  ");
  	  strcat (end_buf, (char *)l -> data);
          if (!index ((char *)l -> data, ';'))
            strcat (end_buf, ";\n");
        }
        strcat (end_buf, "  }");
      }

      if (c -> braces == FALSE) {
        if (*end_buf) {
          strcat (end_buf, "\n}\n\n");
        } else {
          strcpy (end_buf, "\n}\n\n");
        }
        fileout (end_buf, FALSE, ptr - 1);
      }
    }

    if (c -> stmt_type == stmt_for) {
      if (c -> for_inc_methods != NULL) {

        for (l = c -> for_inc_args, *arg_push_buf = 0; l; l = l -> next)
  	  strcat (arg_push_buf, (char *)l -> data);
        /*
         * Same kludge as in the init block above, because the method's
         * context registers as C context.
         */
        for (l = c -> for_inc_methods, *method_buf = 0; l; l = l -> next) {
  	  strcat (method_buf, (char *)l -> data);
          strcat (method_buf, ";\n");
        }
        for (l = c -> for_inc_cleanup, *arg_pop_buf = 0; l; l = l -> next)
	  strcat (arg_pop_buf, (char *)l -> data);

        sprintf (blk_buf, FOR_INC_BLOCK, arg_push_buf, 
                 method_buf, arg_pop_buf);
        fileout (blk_buf, FALSE, 
	       ((c -> braces) ? ptr + 1: ptr - 1));

	strcpy (end_buf, ((c -> braces) ? "\n\n" : "\n}\n"));
        fileout (end_buf, FALSE, ptr - 1);
        loop_linemarker (messages, ptr - 1);    
      } else {
      /*
       *  Output a closing brace if necessary even if there are
       *  no predicates.
       */
        if (c -> braces == FALSE) {
          sprintf (end_buf, "\n}\n\n");
          fileout (end_buf, FALSE, ptr - 1);
          loop_linemarker (messages, ptr - 1);    
        }
      }
    }
  }

  return SUCCESS;
}

int if_else_end (MESSAGE_STACK messages, int ptr) {

  CTRLBLK *c;
  int blk_ptr;

  if (ctrlblk_ptr == MAXARGS)
    return ERROR;

  for (blk_ptr = ctrlblk_ptr + 1; blk_ptr <= MAXARGS; blk_ptr++) {

    c = ctrlblks[blk_ptr];

    if (c -> else_end_ptr != ptr)
      continue;

    if (c -> level < current_parser () -> level)
      break;

    if ((c -> braces == FALSE) && (c -> else_end_ptr)) {
      fileout ("\n}\n\n", FALSE, c -> else_end_ptr - 1);
    }
  }

  return SUCCESS;
}
