/* 1615, Wed 21 Feb 01 (PST)

   SRL_EMIT.C:  PME code emitter for SRL compier

   Copyright (C) 1998-2002 by Nevil Brownlee,
   CAIDA | University of Auckland */

/*
 * $Log: srl_emit.c,v $
 * Revision 1.1.1.2.2.10  2002/02/23 01:57:42  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.6  2001/02/22 00:28:42  nevil
 * Emit an o0 line after && nodes in expressions; this stops the optimiser
 *    from tryng to treat them as part of a following || group.
 * Update copyright notice.
 *
 * Revision 1.1.1.2.2.5  2000/08/08 19:45:00  nevil
 * 44b8 release
 *
 * Revision 1.1.1.2.2.3  2000/06/06 03:38:33  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2  1999/10/03 21:06:35  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.4  1999/09/22 05:34:11  nevil
 * Implement command-line defines
 * - Initialise scanner in init_symbol_table()
 * - Add get_command_define() to scanner.  This dummies up a define
 *      statement then calls push_include to invoke it
 * - Call get_command_define from main when we see a -D option
 *
 * Revision 1.1.1.1.2.3  1999/01/27 04:26:17  nevil
 * Minor corrections to fix compiler warnings
 *
 * Revision 1.1.1.1.2.2  1999/01/08 01:38:42  nevil
 * Distribution file for 4.3b7
 *
 * Revision 1.1.1.1.2.1  1998/12/16 02:59:10  nevil
 * Make compiler distinguish between 'save attrib' and 'save attrib = 0'
 * These both used to produce a rule which saved the whole attrib value!
 *
 * Revision 1.1.1.1  1998/11/16 03:57:33  nevil
 * Import of NeTraMet 4.3b3
 *
 * Revision 1.1.1.1  1998/11/16 03:22:03  nevil
 * Import of release 4.3b3
 *
 * Revision 1.1.1.1.2.2  1998/11/12 23:43:55  nevil
 * Changed emit_imperative to force PushPkt action when a save statement
 * uses a distribution-valued attribute.
 *
 * Revision 1.1.1.1.2.1  1998/11/11 23:14:48  nevil
 * Only include malloc.h if we HAVE_MALLOC_H
 *
 * Revision 1.1.1.1  1998/10/28 20:31:33  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.2.1  1998/10/22 21:40:39  nevil
 * Moved srl from src/manager to its own subdirectory
 *
 * Revision 1.1.3.2.2.1  1998/10/20 03:04:24  nevil
 * Nicolai:  Fixed bug with fclose(command) being called even when
 * command is NULL -- which caused srl to crash on certain compiles
 *
 * Revision 1.1.3.2  1998/10/18 23:44:14  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:28  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.1.1.1  1998/08/24 12:09:29  nguba
 * NetraMet 4.2 Original Distribution
 */

#if HAVE_CONFIG_H
#include <ntm_conf.h>
#endif

#define PRINT_INTERMEDIATE  0
#define OPT_EXPR_TRACE      0
#define OPT_LABEL_TRACE     0
#define RET_ACTION_TRACE    0
#define OR_OPTIMISE_TRACE   0

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#if HAVE_MALLOC_H
# include <malloc.h>
#endif
#include <sys/time.h>
#include <time.h>
#include <stdarg.h>
#include <unistd.h>

#include "rtfm_atr.h"
#include "srl.h"


int lbl_lookup(char *id)  /* Return 0 if id was not in id[] */
{                     /* Otherwise returns 1, lbl_index */
   int id_len, j;
   unsigned int hash;
   struct lbl_info *lip, *ip;

   hash = id_len = strlen(id);
   for (j = 0; j != id_len; ++j) hash = (hash << 1) + id[j];
   lbl_index = hash & LBLTHASHMASK;
   ip = &lbl_table[lbl_index];  lip = NULL;
   if (ip->id[0] != 0) {  /* At least one entry for this hash */
      if (strcmp(ip->id,id) == 0)
         return 1;
      else {
         for (;;) {
            lip = ip;
            if (lip->next == 0) break;  /* End of chain */
            ip = &lbl_table[lbl_index = lip->next]; 
            if (strcmp(ip->id,id) == 0)
               return 1;
	    }
         } 
      }
   /* Not there, put it in */
   if (lip != NULL) {  /* Add to hash chain */
      if (lbl_top == LBLTSIZE) {
         err_msg(ET_ERR, "More than %d labels",LBLTSIZE);
         }
      else {  /* Add new identifier */
         lbl_index = lbl_top++;
         lip->next = lbl_index;
         ip = &lbl_table[lbl_index];
	 }
      }
   memset(ip, 0, sizeof(struct lbl_info));
   strncpy(ip->id, id, id_len);
   ip->id[id_len] = '\0';
   return 0;  /* Not already in lbl_table, now added */
   }

void dump_label_table(void)
{
   int j;
   struct lbl_info *lp;
   if (1) {
      printf("Label table ---------------------\n");
      for (j = 0; j != lbl_top; ++j) {
         lp = &lbl_table[j];
         if (lp->id[0] != 0 && lp->type == TOK_LABEL) {
            printf("%3d, %5d:  %s  prev=%d, ngo=%d, in_expr=%d, go_ix=%d\n",
               j, lp->next, lp->id, lp->prev, lp->ngotos, 
               lp->in_expr, lp->goto_ix);
	    }
         }
      }
   }

void add_label(char *id, int type, int subtype)
{
   struct lbl_info *lp;
   lbl_lookup(id);
   lp = &lbl_table[lbl_index];
   lp->type = type;  lp->subtype = subtype;
   }

void init_label_table()
{
   int j;

   lbl_top = LBLTHASHBASE;
   for (j = 0; j != LBLTHASHBASE; ++j)
      lbl_table[j].id[0] = '\0';

   add_label("succeed", TOK_PMEACT, RA_SUCCEED);           /*  0 PME actions */
   add_label("ignore", TOK_PMEACT, RA_IGNORE);             /*  1 */
   add_label("nomatch", TOK_PMEACT, RA_NOMATCH);           /*  2 */
   add_label("count", TOK_PMEACT, RA_COUNT);               /*  3 */
   add_label("countpkt", TOK_PMEACT, RA_COUNTPKT);         /*  4 */
   add_label("return", TOK_PMEACT, RA_RETURN);             /*  5 */
   add_label("gosub", TOK_PMEACT, RA_GOSUB);               /*  6 */
   add_label("gosubact", TOK_PMEACT, RA_GOSUBACT);         /*  7 */
   add_label("assign", TOK_PMEACT, RA_ASSIGN);             /*  8 */
   add_label("assignact", TOK_PMEACT, RA_ASSIGNACT);       /*  9 */
   add_label("goto", TOK_PMEACT, RA_GOTO);                 /* 10 */
   add_label("gotoact", TOK_PMEACT, RA_GOTOACT);           /* 11 */
   add_label("pushto", TOK_PMEACT, RA_PUSHTO);             /* 12 */
   add_label("pushtoact", TOK_PMEACT, RA_PUSHTOACT);       /* 13 */
   add_label("pushpktto", TOK_PMEACT, RA_PUSHPKTTO);       /* 14 */
   add_label("pushpkttoact", TOK_PMEACT, RA_PUSHPKTTOACT); /* 15 */
   add_label("popto", TOK_PMEACT, RA_POPTO);               /* 16 */
   add_label("poptoact", TOK_PMEACT, RA_POPTOACT);         /* 17 */

   add_label("next", TOK_LABEL, RA_NEXT);  /* Special targets */
      lbl_table[lbl_index].declared = 1;
   add_label("0", TOK_LABEL, 0);
      lbl_table[lbl_index].declared = 1;
   }

static char *actions[] = {
   "",             /*  0 = RA_SUCCEED (never used) */
   "ignore",       /*  1 = RA_IGNORE */
   "nomatch",      /*  2 = RA_NOMATCH */
   "count",        /*  3 = RA_COUNT */
   "countpkt",     /*  4 = RA_COUNTPKT */
   "return",       /*  5 = RA_RETURN */
   "gosub",        /*  6 = RA_GOSUB */
   "gosubact",     /*  7 = RA_GOSUBACT */
   "assign",       /*  8 = RA_ASSIGN */
   "assignact",    /*  9 = RA_ASSIGNACT */
   "goto",         /* 10 = RA_GOTO */
   "gotoact",      /* 11 = RA_GOTOACT */
   "pushto",       /* 12 = RA_PUSHTO */
   "pushtoact",    /* 13 = RA_PUSHTOACT */
   "pushpktto",    /* 14 = RA_PUSHPKTTO */
   "pushpkttoact", /* 15 = RA_PUSHPKTTOACT */
   "popto",        /* 16 = RA_POPTO */
   "poptoact",     /* 17 = RA_POPTOACT */
   };


void emit_rule(FILE *f, char *body, int which, char *target);
#define  NULL_TEST      "0 & 0 = 0"
#define  ASM_NULL_TEST  "null & 0 = 0"

static FILE *code;
static FILE *inter[MXIFGRP+2];  /* 1 for outer block, 1 for end marker */
static FILE *command;  /* For NeMaC commands */
static if_grp_ix;
static char fn_prefix[FNAME_LN+1];

static int  /* Label numbers for .. */
   s_ln,    /*   Success within an expression */
   f_ln;    /*   Failure within an expression */

int intermediate_open(char *prefix)
{     /* Open intermediate file(s), initialise emitters */
   char fn[FNAME_LN+1];
   int j;

   for (j = 0; j != MXIFGRP+2; ++j) inter[j] = NULL;
   strcpy(fn_prefix, prefix);
   s_ln = f_ln = 0;

   sprintf(fn, "%s.sri.0", prefix);
   if ((inter[if_grp_ix = 0] = fopen(fn, "w")) == NULL)
      return 0;
   command = NULL;

   init_label_table();
   return 1;
   }

void intermediate_close(void)
{
   int j;

   emit_rule(inter[0],  /* Prevent fall-through into rest of rules */
      NULL_TEST, RA_NOMATCH, "0");

   for (j = 0; inter[j] != NULL; ++j)
      fclose(inter[j]);
   if (command != NULL) fclose(command);
   if (verbose) dump_label_table();
   }

void emit_comment(char *fmt, ...)
{
   char output[INPUT_LN+20], *op;
   va_list ap;
   va_start(ap, fmt);
   vsprintf(output, fmt, ap);
   for (op = output; *op != '\0'; ++op) ;
   if (op[-1] != '\n') strcpy(op, "\n");
   fprintf(inter[if_grp_ix], "c%s", output);
   }

void emit_opt_level(int level)
{
   if (level >= 0)
      fprintf(inter[if_grp_ix], "o%d\n", level);
   else {  /* Mark break between expressions to be optimised */
      fprintf(inter[if_grp_ix], "o*\n");
      }
   }

void emit_NeMaC_command(char *fmt, ...)
{
   char output[INPUT_LN+20];
   va_list ap;

   if (command == NULL) {
      sprintf(output, "%s.cmd", fn_prefix);
      if ((command = fopen(output, "w")) == NULL)
         return;
      }
   va_start(ap, fmt);
   vsprintf(output, fmt, ap);
   fprintf(command, "c%s", output);
   }


static int prev_label = 0;  /* Last emit_() call was emit_label() */
static struct lbl_info *lp;  /* Last label declared */

static int in_expression = 0;
static char rule_kind = 'i';
   /* 'e' in expression, 'i' for imperative, 'r' for return rules */
static int first_rule = 1;

void actual_emit_label(FILE *f, char *lbl)
{
   int r;

   r = lbl_lookup(lbl);
   lp = &lbl_table[lbl_index];
   if (r &&  /* Found in LT */
         lp->type == TOK_LABEL && lp->declared != 0)
      err_msg(ET_ERR, "Label %s multiply declared", lbl);
   else  /* New label */
      lp->type = TOK_LABEL;
   if (prev_label) {  /* Build chain of labels to same statement */
      lp->prev = prev_label;
      }
   ++lp->declared;
   fprintf(f, "%c%s:\n", rule_kind, lbl);
   prev_label = lbl_index;
#if OPT_LABEL_TRACE 
   printf("+++ actual_emit(): lbl=%s, if_grp_ix=%d\n", lbl, if_grp_ix);
#endif
   }

void emit_label(char *lbl)
{
  actual_emit_label(inter[if_grp_ix], lbl); 
  }

void emit_goto(char *target)
{
   int r;
   struct lbl_info *tp;

   r = lbl_lookup(target);
   tp = &lbl_table[lbl_index];
   if (!r)  /* Wasn't in LT */
      tp->type = TOK_LABEL;
   ++tp->ngotos;

   if (!asmint) fprintf(inter[if_grp_ix],
      "%c  %s: %d, %s;\n", 'i', NULL_TEST, RA_GOTO, target);
   else fprintf(inter[if_grp_ix],
      "%c  %s: %s, %s;\n", 'i', ASM_NULL_TEST, actions[RA_GOTO], target);
   }

int param_attrib(struct symbol *sp)
{
   return FTV1-1 + sp->d.arg.reg;
   }


void emit_rule(FILE *f, char *body, int which, char *target)
{
   int r, null_body, goto_ix;
   struct lbl_info *tp;

   if (first_rule) {
      if (rule_kind == 'i') {  /* First rule is an imperative */
         if (!asmint) fprintf(f,
            "i  %s: %d, next;\n", NULL_TEST, RA_GOTOACT);
         else fprintf(f,
            "i  %s: %s, next;\n", ASM_NULL_TEST, actions[RA_GOTOACT]);
         }
      first_rule = 0;
      }

   r = lbl_lookup(target);
   tp = &lbl_table[lbl_index];
   if (!r)  /* Wasn't in LT */
      tp->type = TOK_LABEL;
   ++tp->ngotos;

   null_body = strcmp(body,NULL_TEST) == 0;
   if (null_body && asmint)
      body = ASM_NULL_TEST;
   if (!asmint) fprintf(f,
      "%c  %s: %d, %s;\n", rule_kind, body, which, target);
   else fprintf(f,
      "%c  %s: %s, %s;\n", rule_kind, body, actions[which], target);

   if (which == RA_GOTO && null_body && tp->subtype != RA_NEXT)
      goto_ix = lbl_index;
   else goto_ix = 0;

   if (prev_label) {  /* Undo label chain */
      for (;;) {
         lp->in_expr = in_expression;
         lp->goto_ix = goto_ix;
#if OPT_LABEL_TRACE 
         printf("+++ emit_rule(): if_grp_ix=%d, lp->id=%s, lp->goto_ix=%d\n",
           if_grp_ix, lp->id, lp->goto_ix);
#endif
         if (lp->prev == 0) break;
         lp =  &lbl_table[lp->prev];
	 }
      }
   prev_label = 0;
   }


unsigned char *sprintoperand(unsigned char *s, struct pt_node *n)
{
   unsigned char b[50], c[50], *cp;
   if (!asmint) sprintf(b, "%d & ", n->d.operand.attrib);
   else sprintf(b, "%s & ", attribs[attr_ix[n->d.operand.attrib]].name);
   s = strmov(s, b);
   s = sprintvalue(s, n->d.operand.mask);
   s = strmov(s, (unsigned char *)" = ");  
   s = sprintvalue(s, n->d.operand.value);
   *s = '\0';
   return s;
   }


static struct pt_node **na;  static int na_ix;
static int attr_order[LASTATTRIB+1];  static int a_nbr;

static int or_grp_cmp(const void *ap, const void *bp)
{
   struct pt_node *a, *b;
   int r;
   a = *(struct pt_node **)ap;
   b = *(struct pt_node **)bp;
   if (attr_order[a->d.operand.attrib] ==
         attr_order[b->d.operand.attrib]) {
      if (a->d.operand.mask_nbr == b->d.operand.mask_nbr) {
         if (a->d.operand.tag_bits == b->d.operand.tag_bits) {
            r = memcmp(a->d.operand.mask, b->d.operand.mask,
               sizeof(a->d.operand.mask));
            if (r == 0) {
               return memcmp(a->d.operand.value, b->d.operand.value,
                  sizeof(a->d.operand.value));
               }
            else return r;  /* Increasing mask width */
            }
         else if (a->d.operand.tag_bits < b->d.operand.tag_bits)
            return 1;  /* Decreasing tag bits */
         else return -1;
         }
      else if (a->d.operand.mask_nbr < b->d.operand.mask_nbr)
         return 1;  /* Decreasing mask number */
      else return -1;
      }
   else if (attr_order[a->d.operand.attrib] >
         attr_order[b->d.operand.attrib])
      return 1;  /* Increasing attribute order */
   else return -1;
   }

void find_or_nodes(struct pt_node *n)
{
   if (n->type == NT_OPERAND) {
      na[na_ix++] = n;
      if (attr_order[n->d.operand.attrib] == 0)
         attr_order[n->d.operand.attrib] = ++a_nbr;
      n->d.operand.mask_nbr = n->d.operand.tag_bits = 0;
      }
   else if (n->type == NT_BINOP) {
      find_or_nodes(n->left);
      find_or_nodes(n->right);
      }
   }

int is_subset(struct pt_node *a, struct pt_node *b)
{  /* Returns 1 if *b is containd within *a */
   int k, r;
   if (a->d.operand.attrib != b->d.operand.attrib) 
      return 0;  /* Different attributes */
   for (k = 0; k != sizeof(a->d.operand.attrib); ++k) {
      if ((b->d.operand.value[k] & a->d.operand.mask[k])
            != (a->d.operand.value[k] & a->d.operand.mask[k]))
         return 0;  /* Different values within *a width */
      }

   r = memcmp(b->d.operand.mask, a->d.operand.mask,
      sizeof(a->d.operand.mask));
   return r > 0;  /* *b has greater width than *a */
   }

void emit_node(struct pt_node *n, int dmy_pushes, char *succeed);

void optimise_or_group(int *g_sz)
{
   struct pt_node *a, *b;
   unsigned char avm[50], bvm[50];
   int grpsz, m, duplicates, j, r, again, k, cycle;
   unsigned int tag_bit, last_tag_bit;

   grpsz = *g_sz;
   if (grpsz == 1)
      check_mask(na[0]);
   else {
      qsort(na, grpsz,sizeof(struct pt_node *), or_grp_cmp);
      tag_bit = 
#if OR_OPTIMISE_TRACE
         0x20;
#else
         (unsigned int)(1 << (sizeof(unsigned int)*8 - 1));
#endif
      b = na[grpsz-1];  b->d.operand.mask_nbr = m = 1;
      b->d.operand.tag_bits = tag_bit;  /* Init: set tag for all operands */
      duplicates = 0;
      for (j = grpsz-2; j >= 0; --j) {
         a = na[j];
         r = memcmp(a->d.operand.mask, b->d.operand.mask,
               sizeof(a->d.operand.mask));
         if (r == 0) {
            if (memcmp(a->d.operand.value, na[j+1]->d.operand.value,
                  sizeof(a->d.operand.value)) == 0) {
               sprintvalmsk(avm, a);
               err_msg(ET_WARN,
                  "%s is duplicated in OR group", avm);
               a->d.operand.mask_nbr = 0;  /* Sort to top */
               ++duplicates;
               }
            else a->d.operand.mask_nbr = m;
            }
         else {
            b = a;  ++m;
            a->d.operand.mask_nbr = m;
            }
         a->d.operand.tag_bits = tag_bit;
         }
      if (duplicates != 0) {
         qsort(na, grpsz,sizeof(struct pt_node *), or_grp_cmp);
         grpsz -= duplicates;
         }
      }

   if (grpsz > 1) {
      for (cycle = 1; ; ++cycle) {
         last_tag_bit = tag_bit;
         tag_bit >>= 1;
#if OR_OPTIMISE_TRACE
         printf("=== Optimise OR: tag_bit=%X\n", tag_bit);
#endif
         again = 0;
         for (j = 0; j != grpsz-1; ++j) {
            a = na[j];
            for (k = j+1; k != grpsz; ++k) {
               b = na[k];
               if ((b->d.operand.tag_bits & last_tag_bit) == 0) break;
               if (is_subset(a, b)) {
                  sprintvalmsk(avm, a);  sprintvalmsk(bvm, b);
#if OR_OPTIMISE_TRACE
                  printf("--- %s (%lX.%lX) contains %s (%lX,%lX)\n",
                     avm, a->d.operand.mask_nbr, a->d.operand.tag_bits,
                     bvm, b->d.operand.mask_nbr, b->d.operand.tag_bits);
#else
                  if (cycle == 1)
                     err_msg(ET_WARN, "%s contains %s", avm,bvm);
#endif
                  b->d.operand.mask_nbr = a->d.operand.mask_nbr;
                  b->d.operand.tag_bits |= tag_bit;
                  again = 1;
                  }
               }
            }
         if (again == 0) break;
         if (tag_bit == 1) {
            err_msg(ET_ERR, "Attempt to optimise OR group failed");
            break;
            }
         qsort(na, grpsz,sizeof(struct pt_node *), or_grp_cmp);
         }
      }
   *g_sz = grpsz;
   }

void emit_or_group(struct pt_node *n, char *succeed)
{
   int grpsz, j;

   grpsz = n->grpsz;
   na = (struct pt_node **)malloc(sizeof(struct pt_node *)*grpsz);
   for (j = 0; j <= LASTATTRIB; ++j) attr_order[j] = 0;
   a_nbr = na_ix = 0;
   find_or_nodes(n);

   if (optimise_level >= 2)
      optimise_or_group(&grpsz);

   for (j = 0; j != grpsz; ++j)
      emit_node(na[j], 0, succeed);
   free(na);
   }

static int if_save;
static char if_target[IDENT_LN+1];
static int if_act;

void emit_node(struct pt_node *n, int dmy_pushes, char *succeed)
{
   char sbuf[IDENT_LN+1], fbuf[IDENT_LN+1];
   unsigned char cline[100], *cp, b[50];
   int act, final, j, mxn, mxl, mxr;

   if (n == NULL) return;
   if (n->type == NT_OPERAND) {
      cp = sprintoperand(cline, n);
      if (strcmp(succeed, if_target) == 0) act = if_act;
      else act = if_save ? RA_PUSHTO : RA_GOTO;
      emit_rule(inter[if_grp_ix], (char *)cline, act, succeed);
      }
   else if (n->type == NT_BINOP) {
      if (n->left == NULL || n->right == NULL)
         return;  /* Empty operands left by syntax errors */
      mxl = n->left->maxdepth;  mxr = n->right->maxdepth;
      mxn = n->maxdepth;
      if (n->d.binop.operator == SC_LOR 
            || n->d.binop.operator == SC_SAMEOR) {         
         if (n->grpsz != 0)
            emit_or_group(n, succeed);
         else {
            emit_node(n->left, mxn-mxl, succeed);
            emit_node(n->right, mxn-mxr, succeed);
            }
         }
      else if (n->d.binop.operator == SC_LAND) {
         sprintf(fbuf, "f%d", ++f_ln);
         sprintf(sbuf, "s%d", ++s_ln);
         if (if_save) {
            for (j = dmy_pushes; j != 0; --j)
               emit_rule(inter[if_grp_ix], NULL_TEST, RA_PUSHTO, "next");
            }
         emit_node(n->left, 0, sbuf);
         emit_rule(inter[if_grp_ix], NULL_TEST, RA_GOTO, fbuf);
         emit_label(sbuf);
         emit_node(n->right, 0, succeed);
         if (if_save) {
            for (j = mxl; j != 0; --j)
               emit_rule(inter[if_grp_ix], NULL_TEST, RA_POPTO, "next");
            }
         emit_label(fbuf);
         if (if_save) {
            for (j = dmy_pushes; j != 0; --j)
               emit_rule(inter[if_grp_ix], NULL_TEST, RA_POPTO, "next");
            }
	 emit_opt_level(0);  /* Mark break between expressions to be optimised */
         }
      }
   }

void emit_IF_level(int incr)
{
   char fn[FNAME_LN+1];

   if (prev_label) {  /* Undo label chain */
      for (;;) {
         lp->in_expr = in_expression;
         lp->goto_ix = 0;
#if OPT_LABEL_TRACE 
         printf("+++ emit_rule(IF_level): if_grp_ix=%d, lp->id=%s\n",
           if_grp_ix, lp->id);
#endif
         if (lp->prev == 0) break;
         lp =  &lbl_table[lp->prev];
	 }
      }
   prev_label = 0;

   if_grp_ix += incr;
   if (if_grp_ix < 0 || if_grp_ix > MXIFGRP) {
      err_msg(ET_ERR, "Compiler error, IF level under/overflow");
      exit(66);
      }
   if (inter[if_grp_ix] == NULL) {
      sprintf(fn, "%s.sri.%d", fn_prefix, if_grp_ix);
      if ((inter[if_grp_ix] = fopen(fn, "w")) == NULL)
         err_msg(ET_ERR, "Couldn't open intermediate file %s", fn);
      }
   if (asmint > 1) fprintf(inter[if_grp_ix],
      "c# -------- if_grp_ix = %d ---------\n", if_grp_ix);
   }

void emit_expression(struct pt_node *op,
   int which, char *succeed, char *next_if)
{
   if (op == NULL) return;
   switch(which) {
   case RW_EXIT:
      if_save = 0;
      if_act = RA_GOTO;
      strcpy(if_target, succeed);
      break;
   case RW_SAVE:
      if_save = 1;
      if_act = RA_PUSHTO;
      strcpy(if_target, succeed == NULL ? "?s?s?" : succeed);
      break;
   case RW_IGNORE:
      if_save = 0;
      if_act = RA_IGNORE;
      strcpy(if_target, "0");
      break;
   case RW_NOMATCH:
      if_save = 0;
      if_act = RA_NOMATCH;
      strcpy(if_target, "0");
      break;
   case RW_RETURN:
      if_save = 1;
      if_act = RA_RETURN;
      strcpy(if_target, succeed == NULL ? "?r?r?" : succeed);
      break;
      }
   in_expression = 1;  rule_kind = 'e';
   emit_node(op, 0, if_target);
   emit_rule(inter[if_grp_ix], NULL_TEST, RA_GOTO, next_if);
   in_expression = 0;   rule_kind = 'i';
   }

void emit_imperative(struct pt_node *op, int which, char *target)
{
   char body[100];
   static char zero_v[RULE_ADDR_LEN] = {0,0,0,0,0,0};

   switch(which) {
   case RW_EXIT:
      emit_rule(inter[if_grp_ix], NULL_TEST, RA_GOTO, target);
      break;
   case RW_COUNT:
      emit_rule(inter[if_grp_ix], NULL_TEST, RA_COUNT, "0");
      break;
   case RW_IGNORE:
      emit_rule(inter[if_grp_ix], NULL_TEST, RA_IGNORE, "0");
      break;
   case RW_NOMATCH:
      emit_rule(inter[if_grp_ix], NULL_TEST, RA_NOMATCH, "0");
      break;

   case RW_SAVE:
   case RW_SAVE_V:
      sprintoperand((unsigned char *)body, op);
      emit_rule(inter[if_grp_ix], body, 
/*         memcmp(op->d.operand.value, zero_v, RULE_ADDR_LEN) == 0  */
           which == RW_SAVE
               || DISTRIB_ATTRIB(op->d.operand.attrib)
            ? RA_PUSHPKTTO : RA_PUSHTO, 
         target == NULL ? "next" : target);
      break;
   case RW_STORE:
      sprintoperand((unsigned char *)body, op);
      emit_rule(inter[if_grp_ix], body, RA_PUSHTO,
         target == NULL ? "next" : target);
      break;

   case RW_RETURN:
      emit_rule(inter[if_grp_ix], NULL_TEST, RA_RETURN, 
         target == NULL ? "$$$" : target);
      break;
      }
   }

void emit_return_code(struct pt_node *op, int which, char *target)
{
   rule_kind = 'r';
   emit_imperative(op, which, target);
   rule_kind = 'i';
   }

void emit_subr_call(int call_stx)
{
   struct symbol *call_sp, *sp;
   int j;
   char b[50];
   struct lbl_info *tp;


   call_sp = &st[call_stx];
   for (j = 0; j != call_sp->d.sub.n_params; ++j) {
      sp = &st[call_stx+2 + j];

      if (!asmint) sprintf(b,
         "%d & 0 = &d", FTV1-1 + sp->d.arg.reg, sp->d.arg.attrib); 
      else sprintf(b, "%s & 0 = %s", 
         attribs[attr_ix[FTV1-1 + sp->d.arg.reg]].name,
         attribs[attr_ix[sp->d.arg.attrib]].name);

      emit_rule(inter[if_grp_ix], b, RA_ASSIGNACT, "next");
      }

   j = lbl_lookup(call_sp->name);  /* Make sure subroutine is in label table */
   tp = &lbl_table[lbl_index];
   if (!j) {  /* Wasn't in LT */
      tp->type = TOK_LABEL;  /* Its really a subroutine name! */
      }
   tp->stx = call_stx;
   emit_rule(inter[if_grp_ix], NULL_TEST, RA_GOSUB, call_sp->name);
   }


int next_inter_line(char *buf, char **bp)
{
   char fn[FNAME_LN+1];
   if (inter[if_grp_ix] == NULL)
      return 0;
   for (;;) {
      if (fgets(buf, INPUT_LN, inter[if_grp_ix]) == NULL) {  /* EOF */
         fclose(inter[if_grp_ix]);
         if (inter[++if_grp_ix] == NULL)
            return 0;  /* No more intermediate files */
         else {
            sprintf(fn, "%s.sri.%d", fn_prefix, if_grp_ix);
            if ((inter[if_grp_ix] = fopen(fn, "r")) == NULL) {
               err_msg(ET_ERR, "Couldn't open intermediate file %s", fn);
               return 0;
               }
            continue;
            }
         }
      if (buf[0] == 'c') {
         fprintf(code, "%s", &buf[1]);
         continue;
         }
#if PRINT_INTERMEDIATE
      else printf("icode: %s", buf);
#endif
      break;
      }
   *bp = &buf[1];
   return 1;
   }

void delete_intermediate_files(void)
{
   char fn[FNAME_LN+1];
   for (if_grp_ix = 0; inter[if_grp_ix] != NULL; ++if_grp_ix) {
      sprintf(fn, "%s.sri.%d", fn_prefix, if_grp_ix);
      if (unlink(fn) != 0) {
         err_msg(ET_ERR, "Couldn't delete intermediate file %s", fn);
         return;
         }
      }
   if (command != NULL) {
      sprintf(fn, "%s.cmd", fn_prefix);
      if (unlink(fn) != 0)
         err_msg(ET_ERR, "Couldn't delete NeMaC command file %s", fn);
      }
   }

static int lookahead;
static char labuf[INPUT_LN], *lbp;
static char la_copy[INPUT_LN], *lcp,
   *la_bodyp, *la_actionp, *la_targetp;
static int la_null_body, la_act;

int p2_lookahead(void)
{
   struct lbl_info *la_tp;
   unsigned long n;

   la_act = 0;
   if (!next_inter_line(labuf, &lbp))
      return 0;
   else {
      strcpy(la_copy, labuf);
      lcp = &la_copy[1];
      if (labuf[1] != ' ') {  /* It's a label */
         for (; *lcp != ':'; ++lcp) ;
         *lcp++ = '\0';
         }
      else {  /* Not a label */
         for (; *lcp == ' '; ++lcp) ;  /* Break next line into parts */
         la_bodyp = lcp;
         for (; *lcp != ':'; ++lcp) ;
         *lcp++ = '\0';
         for (; *lcp == ' '; ++lcp) ;
         la_actionp = lcp;
         for (; *lcp != ','; ++lcp) ;
         *lcp++ = '\0';
         for (; *lcp == ' '; ++lcp) ;
         la_targetp = lcp;
         for (; *lcp != ';'; ++lcp) ;
         *lcp++ = '\0';
         if (asmint) {
            la_null_body = strcmp(la_bodyp,ASM_NULL_TEST) == 0;
            lbl_lookup(la_actionp);
            la_tp = &lbl_table[lbl_index];
            la_act = la_tp->subtype;            
            }
         else {
            la_null_body = strcmp(la_bodyp,NULL_TEST) == 0;
            gnbr(&n, la_actionp);
            la_act = n;
            }
         if (lbl_lookup(la_targetp)) {
            la_tp = &lbl_table[lbl_index];
            if (la_tp->subtype != RA_NEXT) {
               switch (la_act) {
               case RA_GOTO:
               case RA_PUSHTO:
                  if (!la_tp->in_expr) ++la_act;
                  break;
               case RA_GOTOACT:
               case RA_PUSHTOACT:
                  if (la_tp->in_expr) --la_act;
                  break;
                  }
               }
            }         
         }
      }
   return lookahead = 1;
   }

int p2_nextline(void)
{
   if (lookahead) {
      strcpy(inbuf, labuf);  ibp = &inbuf[1];
      lookahead = 0;
      return 1;
      }
   else return next_inter_line(inbuf, &ibp);
   }

char *getdecimal(int *v, char *s)
{
   int n = 0;
   while (isdigit(*s)) {
      n = n*10 + *s-'0';
      ++s;
      }
   *v = n;
   return s;
   }

void read_node(struct pt_node *a, char *bodyp)
{
   char *bp, attrib[IDENT_LN+1], *ap;
   int v, k;

   for (bp = bodyp; *bp == ' '; ++bp) ;
   if (!isdigit(*bp)) {
      for (ap = attrib; *bp != ' '; ) *ap++ = *bp++;
      *ap = '\0';
      lookup(attrib);
      a->d.operand.attrib = id_table[id_index].subtype;
      }
   else {
      bp = getdecimal(&v, bp);
      a->d.operand.attrib = v;
      }
   bp += 3;  /* Skip over blank & blank */
   for (k = 0; ; ++k) {
      bp = getdecimal(&v, bp);
      a->d.operand.mask[k] = v;
      if (*bp != '.') break;
      ++bp;
      }
   bp += 3;  /* Skip over blank = blank */
   for (k = 0; ; ++k) {
      bp = getdecimal(&v, bp);
      a->d.operand.value[k] = v;
      if (*bp != '.') break;
      ++bp;
      }
   }

static int n_e_lines, enl_head, enl_tail;

void process_en_list(struct pt_node *en_list)
{
   int grpsz, j;
   struct pt_node *en;
   char body[200];

#if OPT_EXPR_TRACE
   printf("~~~ process_en_list(): n_e_lines=%d, enl_head=%d\n",
      n_e_lines, enl_head);
#endif
   grpsz = n_e_lines;
   na = (struct pt_node **)malloc(sizeof(struct pt_node *)*grpsz);
   for (j = 0; j <= LASTATTRIB; ++j) attr_order[j] = 0;
   a_nbr = na_ix = 0;
   en = en_list;
   for (na_ix = 0; na_ix != grpsz; ++na_ix) {
      na[na_ix] = en;
      if (attr_order[en->d.operand.attrib] == 0)
         attr_order[en->d.operand.attrib] = ++a_nbr;
      en = en->left;
      }

   optimise_or_group(&grpsz);

   for (j = 0; j != grpsz; ++j) {
      en = na[j];
      sprintoperand((unsigned char *)body, en);   
      if (!asmint) fprintf(code,
         "  %s: %d, %s;\n",
         body, en->d.operand.action, en->d.operand.target);
      else fprintf(code,
         "  %s: %s, %s;\n", 
         body, actions[en->d.operand.action], en->d.operand.target);
      free_node(en);
      }
   free(na);

   while (enl_head != 0) {
      fprintf(code, "%s:\n", lbl_table[enl_head].id);
      enl_head = lbl_table[enl_head].prev;
      }   
   n_e_lines = enl_head = enl_tail = 0;
   }

void emit_pass2(char *codefn)
{
   char fn[FNAME_LN+1], n_buf[IDENT_LN+1];
   char *bodyp, *actionp, *targetp;
   struct lbl_info *plp, *tp;
   struct symbol *sp;
   int null_body, act, target_in_expr, target_is_next,
     n_ret_actions, goto_ix, discard, opt_lev;
   unsigned long n;
   struct pt_node *en_head, *en_tail, *en;

   sprintf(fn, "%s.sri.0", fn_prefix);
   if ((inter[if_grp_ix = 0] = fopen(fn, "r")) == NULL) {
      err_msg(ET_ERR, "Couldn't open intermediate file %s", fn);
      return;
      }
   if (*codefn == '\0')
      sprintf(fn, "%s.rules", fn_prefix);
   else strcpy(fn, codefn);
   if ((code = fopen(fn, "w")) == NULL) {
      err_msg(ET_ERR, "Couldn't open output file %s", fn);
      return;
      }

   lookahead = n_ret_actions =
      n_e_lines = enl_head = enl_tail = 0;
   en_head = en_tail = NULL;
   for (;;) {
      if (!p2_nextline()) break;  /* EOF */

      if (inbuf[0] == 'o') {  /* Set optimise level */
         opt_lev = optimise_level;
         if (inbuf[1] == '*') {
            if (n_e_lines != 0)
               process_en_list(en_head);
            }
         else {
            getdecimal(&optimise_level, ibp);
            if (optimise_level != opt_lev && n_e_lines != 0)
               process_en_list(en_head);
            }
         continue;
         }

      if (*ibp != ' ') {  /* Label */
         for (; *ibp != ':'; ++ibp) ;
         *ibp = '\0';
         if (!lbl_lookup(&inbuf[1]))  /* Wasn't in LT */
            err_msg(ET_ERR,
               "Label %s missing from pass 2 input ???", &inbuf[1]);
         if (lbl_table[lbl_index].ngotos != 0) {
            *ibp = ':';
            if (n_e_lines != 0) {
               if (enl_head == 0) enl_head = lbl_index;
               else lbl_table[enl_tail].prev = lbl_index;
               lbl_table[enl_tail = lbl_index].prev = 0;
               }
            else fprintf(code, "%s", &inbuf[1]);
            }
         prev_label = lbl_index;
         }
      else {
         for (; *ibp == ' '; ++ibp) ;  /* Break code line into parts */
         bodyp = ibp;
         for (; *ibp != ':'; ++ibp) ;
         *ibp++ = '\0';
         for (; *ibp == ' '; ++ibp) ;
         actionp = ibp;
         for (; *ibp != ','; ++ibp) ;
         *ibp++ = '\0';
         for (; *ibp == ' '; ++ibp) ;
         targetp = ibp;
         for (; *ibp != ';'; ++ibp) ;
         *ibp++ = '\0';

         if (asmint) {
            null_body = strcmp(bodyp,ASM_NULL_TEST) == 0;
            lbl_lookup(actionp);
            tp = &lbl_table[lbl_index];
            act = tp->subtype;            
            }
         else {
            null_body = strcmp(bodyp,NULL_TEST) == 0;
            gnbr(&n, actionp);
            act = n;
            }

         if (!lbl_lookup(targetp))  /* Wasn't in LT */
            err_msg(ET_ERR, "Label %s missing from pass 2 input ???", targetp);
         tp = &lbl_table[lbl_index];

         p2_lookahead();
         if (tp->subtype == RA_NEXT) {
            target_is_next = 1;
            if (lookahead) {
               if (labuf[1] == ' ')
                  target_in_expr = labuf[0] == 'e';
               else {  /* Next line is a label */
                  if (!lbl_lookup(&la_copy[1])) {  /* Wasn't in LT */
                     err_msg(ET_ERR,
                        "Label %s missing from pass 2 input !!!", &la_copy[1]);
                     target_in_expr = 0;
                     }
                  else target_in_expr = lbl_table[lbl_index].in_expr;
                  }
	       }
            else target_in_expr = 0;
            }
         else {
            target_is_next = 0;
            target_in_expr = tp->in_expr;
            }

         switch (act) {
         case RA_IGNORE:
         case RA_NOMATCH:
         case RA_COUNT:
         case RA_COUNTPKT:
            break;

         case RA_RETURN:
            if (!isdigit(tp->id[0])) {
               lookup(tp->id);
               sp = &st[id_table[id_index].stx];
               sprintf(n_buf, "%u", sp->d.sub.n_returns+1);
               targetp = n_buf;
               }
            break;

         case RA_GOSUB:
         case RA_GOSUBACT:
            lbl_lookup(tp->id);
            sp = &st[lbl_table[lbl_index].stx];
            n_ret_actions = sp->d.sub.n_returns;
            if (act == RA_GOSUB && !target_in_expr) ++act;
            else if (act == RA_GOSUBACT && target_in_expr) --act;
            break;

         case RA_ASSIGN:
         case RA_GOTO:
         case RA_PUSHTO:
         case RA_PUSHPKTTO:
         case RA_POPTO:
            if (!target_in_expr) ++act;
            break;

         case RA_ASSIGNACT:
         case RA_GOTOACT:
         case RA_PUSHTOACT:
         case RA_PUSHPKTTOACT:
         case RA_POPTOACT:
            if (target_in_expr) --act;
            break;
            }         

         if (prev_label) {
            if (act == RA_GOTOACT && null_body && tp->subtype != RA_NEXT) {
               goto_ix = lbl_index;  /* LT index of target (?) */
               plp = &lbl_table[prev_label];
               if (plp->goto_ix == 0) {
                  plp->goto_ix = goto_ix;
#if OPT_LABEL_TRACE 
                  printf("+++ ==0 (%s, plp->0, goto=%d) %s: %s, %s\n", 
                     plp->id, goto_ix,  bodyp, actions[act], targetp);
#endif
                  }
               else if (plp->goto_ix != goto_ix) {
                  err_msg(ET_ERR,
                     "Compiler error, inconsistent label %s", plp->id);
#if OPT_LABEL_TRACE 
                  printf("+++ (%s, plp->goto=%d, goto=%d) %s: %s, %s\n", 
                     plp->id, plp->goto_ix, goto_ix,
                     bodyp, actions[act], targetp);
#endif
                  }
               }
	    }
         prev_label = 0;

#if RET_ACTION_TRACE
         if (n_ret_actions > 0) {
            printf("\n@@@@@ nra=%d,  %c  %s: %s, %s;\n",
               n_ret_actions,  inbuf[0], bodyp, actions[act], targetp);
            printf("      labuf:  %s", labuf);
            }
#endif
         if (inbuf[0] == 'r') {
            --n_ret_actions;
#if RET_ACTION_TRACE
            if (n_ret_actions != 0)
               printf("===== nra=%d\n", n_ret_actions);
#endif
            if (lookahead && labuf[0] != 'r' &&  /* 'Return;' action reached */
                  n_ret_actions >= 0) {  /* Missing ret actions */
#if RET_ACTION_TRACE
               printf("+++++ %d return actions\n", n_ret_actions+1);
#endif
               for (; n_ret_actions >= 0; --n_ret_actions) {
                  if (!asmint) fprintf(code,
                     "  %s: %d, %s;\n", bodyp, act, targetp);
                  else fprintf(code,
                     "  %s: %s, %s;\n", bodyp, actions[act], targetp);
                  }
               }
            }

         discard = 0;
         if (optimise_level >= 1) {
            if (act == RA_GOTO && null_body && tp->subtype != RA_NEXT) {
               if (tp->goto_ix != 0) {  /* Target is a null-body goto */
                     /* e  null & 0 = 0: goto, lbl-l;      */
                     /*    ...                             */
                     /* elbl-1:                            */
                     /* e  null & 0 = 0: goto, lbl-2;      */
                  --tp->ngotos;
                  while (tp->goto_ix != 0) {
                        /* Find end of chain */
                     tp = &lbl_table[tp->goto_ix];
                     }
                  ++tp->ngotos;
                  targetp = tp->id;  /* => Goto last label in chain */
                  }
               else if (lookahead && *lbp != ' ') {  /* Label following */
                     /* e  null & 0 = 0: goto, lbl;        */
                     /* elbl:                              */
                     /* e  ...                             */
                  for (; *lbp != ':'; ++lbp) ;
                  *lbp = '\0';
                  if (strcmp(&labuf[1], targetp) == 0 && tp->in_expr &&
                        inbuf[0] != 'r') {
                     --tp->ngotos;
                     *lbp = ':';
                     continue;  /* => Delete redundant goto, lbl */
                     }
                  *lbp = ':';
                  }
               }

            if (target_is_next && lookahead && *lbp == ' ') {
               if (la_null_body &&
                     (act == RA_PUSHTOACT || act == RA_PUSHPKTTOACT ||
                        act == RA_GOTOACT) &&
                     la_act == RA_GOTOACT ) {
                     /* i  operand: pushto, next;  # store  */
                     /* i  null & 0 = 0: goto, lbl;         */
                  targetp = la_targetp;
                  discard = 1;  /* => Delete redundant goto */

                  if (!lbl_lookup(targetp)) err_msg(ET_ERR,  /* Wasn't in LT */
                     "Opt label %s missing from pass 2 input ???", targetp);
                  tp = &lbl_table[lbl_index];
                  if (tp->goto_ix != 0) {  /* Target is null-body goto */
                     --tp->ngotos;
                     while (tp->goto_ix != 0) {
                           /* Find end of chain */
                        tp = &lbl_table[tp->goto_ix];
                        }
                     ++tp->ngotos;
                     targetp = tp->id;  /* => Goto last label in chain */
                     }
                  }
               }

            if (null_body &&
                  (act == RA_IGNORE || act == RA_NOMATCH || act == RA_RETURN
                  || act == RA_COUNT || act == RA_COUNTPKT) ) {
               if (lookahead && labuf[0] != 'r' && *lbp == ' ') {
                  if (la_act == RA_GOTO || la_act == RA_GOTOACT ||
                           la_act == RA_RETURN || la_act == RA_NOMATCH)
                        /* ?  null & 0 = 0: ignore/countpkt, n  */
                        /* ?  null & 0 = 0: goto/gotoact        */
                     discard = 1;  /* => Delete redundant goto */
                  else {
                     err_msg(ET_WARN,
                        "Program contains unreachable statement");
                     if (asmint > 1) {
                        printf("  %s: %s, %s;\n",
                           bodyp, actions[act], targetp);
                        printf("  %s: %s, %s;\n",
                           la_bodyp, actions[la_act], la_targetp);
                        }
                     }
                  }
               }
            }

         if (discard) lookahead = 0;
#if OPT_EXPR_TRACE
         if (n_e_lines > 0 && n_e_lines < 10)
            printf("~~~ n_e_lines=%d, inbuf=  %s: %s, %s;\n", 
               n_e_lines, bodyp, actions[act], targetp);
#endif

         if (optimise_level >= 3) {
            if (inbuf[0] == 'e' &&
                  memcmp(bodyp, "null", 4) != 0 && *bodyp != '0') {
               en = alloc_node();
               read_node(en, bodyp);
               en->d.operand.action = act;
               strcpy(en->d.operand.target, targetp);

               if (n_e_lines != 0 &&
                     en->d.operand.attrib != en_tail->d.operand.attrib)
                  process_en_list(en_head);

               if (n_e_lines == 0) en_head = en;
               else en_tail->left = en;
               en_tail = en;
               ++n_e_lines;
               }
            else {
               if (n_e_lines != 0)
                  process_en_list(en_head);

               if (!asmint) fprintf(code,
                  "  %s: %d, %s;\n", bodyp, act, targetp);
               else fprintf(code,
                  "  %s: %s, %s;\n", bodyp, actions[act], targetp);
               }
            }
         else {
            if (!asmint) fprintf(code,
               "  %s: %d, %s;\n", bodyp, act, targetp);
            else fprintf(code,
               "  %s: %s, %s;\n", bodyp, actions[act], targetp);
            }
         }
      }

   if (n_e_lines != 0)
      process_en_list(en_head);

   if (command != NULL) {  /* Copy NeMaC commands to tail of code file */
      sprintf(fn, "%s.cmd", fn_prefix);
      if ((command = fopen(fn, "r")) != NULL) {
         for (;;) {
            if (fgets(inbuf, INPUT_LN, command) == NULL) break;  /* EOF */
            fprintf(code, &inbuf[1]);
            }
         }
      fclose(command);
      }

   fclose(code);  /* Intermediate files closed by next_inter_line() */
   if (asmint < 2) delete_intermediate_files();
   }
