/***************************************************************************

NAME
    ecto_b98.c - glue code for linking .b98 files with .i files

***************************************************************************/
// SPDX-FileCopyrightText: (C) Alex Smith
// SPDX-License-Identifier: GPL-2.0-or-later

/* This is an expansion library designed to link with the Funge-98 interpreter
   cfunge (when compiled as a library); this is glue code which hooks into the
   INTERCAL control structure, and uses functions in glue code which hooks
   into the Funge control structure in cfunge. The Befunge program itself that
   is interpreted is stored in yet another .cio file, and accessed via the
   extern variables ick_iffi_befungeString, ick_iffi_markerposns, and
   ick_iffi_markercount. The name ecto_b98 is magical, and causes this library
   to be included when a .b98 files is encountered on the command line (as
   well as the cfunge library itself, which has the magical name
   libick_ecto_b98.a).

   The library itself works by using label names that have a special meaning
   to ick, along with ICK_EC_FUNCs, to cause a compiled C-INTERCAL program to
   also search in the Befunge program for line labels, COME FROMs, and NEXT
   FROMs, as well as in the INTERCAL program itself. It also allows the
   Befunge program to use INTERCAL control flow structures (even to itself),
   by monitoring flags set in a Funge-98 fingerprint (IFFI, which accounts
   for most of the variable names) and simulating the required structure
   (such as FORGET or NEXT) somewhere where cfunge itself will be unaffected
   and unaware of what is going on.

   In general, INTERCAL control flow structures affect only the position and
   delta of the IP in the Befunge program, not anything else. (In particular,
   the Befunge stack is unaffected.)
 */

#include <stdio.h>
#include <assert.h>
#include <ick_ec.h>

extern int ick_printflow;

struct ick_ipposdeltatype
{
  long long ix, iy, dx, dy;
};

/* This is called from IFFI. Remember to keep matching
   extern over there in sync. */
void ick_interpreter_main_loop(void);

/* This is just a forward decl. */
void ick_iffi_handle_control(void);

/* These are implemented in IFFI. */
extern void ick_interpreter_run(void);
extern void ick_iffi_interpreter_one_iteration(void);
extern void ick_save_ip_pos_delta(struct ick_ipposdeltatype*);
extern void ick_restore_ip_pos_delta(const struct ick_ipposdeltatype*);

unsigned short ick_iffi_forgetcount=0;
int ick_iffi_nexting=0;
int ick_iffi_comingfrom=0;
int ick_iffi_nextingfrom=0;
int ick_iffi_sucking=0;
int ick_iffi_resuming=0;
uint32_t ick_iffi_linelabel=0;
int ick_iffi_breakloop=0; /* becomes 1 when IFFI's loaded or mark-mode ends */
int ick_iffi_inmarkmode=0;

extern const unsigned char* ick_iffi_befungeString;
extern long long ick_iffi_markerposns[][2];
extern int ick_iffi_markercount;

ICK_EC_FUNC_START(ick_interpreter_main_loop)
{
  ick_startup( {ick_interpreter_run();} ); /* could be put anywhere, is put here
					     for convenience */

  /* we enter here when running at startup code and continue until IFFI is loaded */

  if(0)
  {
  ick_l2_ICK_EC_PP_2: /* this will be automatically replaced with a valid label,
			 as will the next occurence of it, by ick when it does
			 linking (i.e. this is not an infinite loop). */
    if(ick_global_linelabel != 0x70001ff1 &&
       ick_global_linelabel != 0x70001ff2)
      goto ick_l2_ICK_EC_PP_2;
    /* If control reaches this point, a COME FROM or NEXT has just completed,
       and the IP already in the correct place. Continue, outside markmode. */
    ick_iffi_nexting = 0;
    ick_iffi_comingfrom = 0;
    ick_iffi_nextingfrom = 0;
    ick_iffi_sucking = 0;
    ick_iffi_inmarkmode = 0;
    ick_global_checkmode = 0;
    /* If a NEXT has just completed, do a suckpoint-check on that line label
       (in case someone COMEs FROM the target of the NEXT). */
    if(ick_global_linelabel == 0x70001ff2)
    {
      auto struct ick_ipposdeltatype ippd;
      ick_save_ip_pos_delta(&ippd);
      ick_iffi_sucking = 0;
      ick_checksuckpoint(ick_iffi_linelabel); /* may not return */
      ick_restore_ip_pos_delta(&ippd);
    }
  }

ick_iffi_iml_rerun:

  ick_iffi_breakloop=0;
  while(!ick_iffi_breakloop)
  {
    ick_iffi_interpreter_one_iteration();
    if (ick_iffi_forgetcount && !ick_iffi_breakloop)
    {
      ick_forget(ick_iffi_forgetcount);
      ick_iffi_forgetcount = 0;
    }
  }
  /* There are several reasons why we could reach this point. Most of them,
     we just fall off the end of the function and whatever called us handles
     the program flow from there. However, if the loop broke due to a RESUME,
     line label or NEXT, we need to handle it. */
  if(ick_iffi_resuming)
  {
    unsigned short iifctemp = ick_iffi_forgetcount;
    ick_iffi_resuming = 0;
    ick_iffi_forgetcount = 0;
    ick_resume(iifctemp); /* never returns */
  }
  if(ick_iffi_nexting)
  {
    /* To handle a NEXT, we need to save the relevant IP information (position
       and delta, which are the only things saved/restored on the NEXT stack)
       onto the C stack. This is done in a struct ick_ipposdatatype, using the
       functions available for such save/restore. */
    auto struct ick_ipposdeltatype ippd;
    ick_save_ip_pos_delta(&ippd);
    ick_iffi_nexting = 0;
    ick_next(ick_iffi_linelabel); /* may not return */
    ick_restore_ip_pos_delta(&ippd);
    goto ick_iffi_iml_rerun; /* continue the main loop if we got here */
  }
  if(ick_iffi_sucking && ! ick_iffi_inmarkmode)
  {
    /* Handle a line-label encountered in normal execution. */
    auto struct ick_ipposdeltatype ippd;
    ick_save_ip_pos_delta(&ippd);
    ick_iffi_sucking = 0;
    ick_checksuckpoint(ick_iffi_linelabel); /* may not return */
    ick_restore_ip_pos_delta(&ippd);
    goto ick_iffi_iml_rerun; /* continue the main loop if we got here */
  }
}
ICK_EC_FUNC_END

ICK_EC_FUNC_START(ick_iffi_handle_control)
{
  static int recursing=0;
  int markerno, dirno, nextcheck;
  struct ick_ipposdeltatype ippd;

  /* The label names here are magical internal identifiers. That means that this
     is not in fact an infinite loop, but is instead a handler for sucking in
     from line labels. The 0x70001ff1 is a magic number used specifically by
     this code. */
  if(0)
  {
  ick_l1_ICK_EC_PP_1:
    if(ick_global_linelabel > 65535)
      goto skipsearch; /* there can't be a match, don't check to avoid recursion */
    nextcheck = 0;
    if(ick_printflow)
      fprintf(stderr,"[cfcheck:%d]\n",(int)ick_global_linelabel);
  }

  if(0)
  {
  ick_l2_ICK_EC_PP_2:
    if(ick_global_linelabel > 65535)
      goto skipsearch; /* there can't be a match, don't check to avoid recursion */
    nextcheck = 1;
    if(ick_printflow)
      fprintf(stderr,"[llcheck:%d]\n",(int)ick_global_linelabel);
  }

  assert(!recursing);

  recursing=1;

  ick_save_ip_pos_delta(&ippd);

  markerno=ick_iffi_markercount;
  while(markerno--)
  {
    dirno=4;
    while(dirno--)
    {
      struct ick_ipposdeltatype tippd;
      tippd.ix=ick_iffi_markerposns[markerno][0];
      tippd.iy=ick_iffi_markerposns[markerno][1];
      tippd.dx=(dirno == 3 ? -1 : dirno == 1 ? 1 : 0);
      tippd.dy=(dirno == 0 ? -1 : dirno == 2 ? 1 : 0);
      if(ick_printflow)
	fprintf(stderr, "Checking for %s at (%lld, %lld) going %c...\n",
		nextcheck ? "line label" : "COME FROM/NEXT FROM",
		tippd.ix, tippd.iy, dirno["^>v<"]);
      tippd.ix += tippd.dx;
      tippd.iy += tippd.dy;
      ick_restore_ip_pos_delta(&tippd);
      ick_iffi_inmarkmode = 1;
      ick_local_checkmode = ick_global_checkmode;
      ick_global_checkmode = 0;
      ick_interpreter_main_loop();
      ick_global_checkmode = ick_local_checkmode;
      ick_iffi_inmarkmode = 0;
      if((ick_iffi_nextingfrom||ick_iffi_comingfrom) &&
	ick_iffi_linelabel == ick_global_linelabel && !nextcheck)
      {
	if(ick_printflow)
	  fprintf(stderr,"%s FROM found!\n",ick_iffi_comingfrom?"COME":"NEXT");
	/* Error out on multiple COME FROM/NEXT FROM with the same target. */
	if(ick_global_goto) ick_lose(ICK_IE555, -1, (char*)0);
	ick_global_goto=0x70001FF1; /* we found a suckpoint */
	if(ick_iffi_nextingfrom) ick_global_checkmode = 3; /* do a next from */
	/* if it's a come-from, the checkmode is already correct */
	ick_save_ip_pos_delta(&ippd);
      }
      if(ick_iffi_sucking && ick_iffi_linelabel == ick_global_linelabel &&
	 nextcheck)
      {
	if(ick_printflow)
	  fprintf(stderr,"Line label found!\n");
	/* Another part of the code is NEXTing to this line label. */
	ick_save_ip_pos_delta(&ippd);
	nextcheck = 2; /* when this loop ends, goto the main loop */
      }
      ick_iffi_nextingfrom = 0;
      ick_iffi_comingfrom = 0;
      ick_iffi_sucking = 0;
    }
  }

  if(ick_printflow) fprintf(stderr,"Checks finished.\n");

  /* Set the IP to what we want it to be on resume, or otherwise return it to
     its original value. (Returning it to its original value is unneccesary
     but harmless.) */
  ick_restore_ip_pos_delta(&ippd);
  recursing=0;
  ick_iffi_linelabel = ick_global_linelabel;
  if(nextcheck == 2)
    ick_dogoto(0x70001FF2,-1,0);
skipsearch:
  if(nextcheck)
    goto ick_l2_ICK_EC_PP_2;
  else
    goto ick_l1_ICK_EC_PP_1;
}
ICK_EC_FUNC_END
