/*
 * gencode.c  -  Code generator
 *
 * Copyright (C) 1997-2003 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  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
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: gencode.c,v 1.4 2003/01/25 23:29:44 gkminix Exp $
 */

#define NEED_BINARY 1
#include "mknbi.h"
#include "mgl.h"
#include "gencode.h"
#include "opcodes.h"



/*
 * Global variables
 */
addr_t dataptr = 0;			/* Current data pointer */
addr_t constptr = 0;			/* Current constant pointer */
addr_t codeptr = 0;			/* Current code pointer */
addr_t startadr = 0;			/* Start address */



/*
 * The following structure is used to hold debugging information
 */
struct debuginfo {
	addr_t addr;
	int    lineno;
};



/*
 * Local variables
 */
static __u8 codebuf[CODESIZE];			/* Buffer for code */
static __u8 constbuf[DATASIZE];		/* Buffer for constant data */
static addr_t relocbuf[RELOCSIZE];		/* Buffer for reloc entries */
static unsigned int relocnum = 0;		/* Current reloc entry */
static struct debuginfo debugbuf[DEBUGSIZE];	/* Buffer for debugging info */
static unsigned int debugnum = 0;		/* Number of debug entries */
static addr_t stackofs = 0;			/* Size of local stack area */




/*
 ************************************************************************
 *
 *               Basic code generation routines
 *
 ************************************************************************
 */

/*
 * Put one byte into the code segment
 */
void putcode(c)
unsigned int c;
{
  if (codeptr >= CODESIZE) {
	prnerr0("code segment too large");
	exit(EXIT_MGL_CODESIZE);
  }
  codebuf[codeptr++] = (__u8)(c & 0xff);
}



/*
 * Put one byte into the data segment
 */
void putconst(c)
unsigned int c;
{
  if (constptr >= CONSTSIZE) {
	prnerr0("constant data segment too large");
	exit(EXIT_MGL_CONSTSIZE);
  }
  constbuf[constptr++] = (__u8)(c & 0xff);
}



/*
 * Put one string into the data segment
 */
addr_t putstring(str)
char *str;
{
  char *cp;
  addr_t i, j, end;

  /* Let's see if we have the string in the constant buffer already */
  end = constptr - strlen(str) + 1;
  for (i = 0; i < end; i++) {
	for (cp = str, j = i; *cp && j < constptr; cp++, j++)
		if ((__u8)(*cp & 0xff) != constbuf[j])
			break;
	if (j < constptr && !*cp &&
	    (__u8)(*cp & 0xff) == constbuf[j])
		return(i);
  }

  /* No, it's not in yet, create a new string in the buffer */
  i = constptr;
  for (cp = str; *cp; cp++)
	putconst(*cp & 0xff);
  putconst(0);
  return(i);
}



/*
 * Put one integer value into the code segment
 */
void putint(val)
long val;
{
  putcode((unsigned int)(val & 0x00ff));
  putcode((unsigned int)((val >> 8) & 0x00ff));
}



/*
 * Set a new relocation entry at present code position
 */
void setreloc()
{
  if (relocnum >= RELOCSIZE) {
	prnerr0("relocation buffer overflow");
	exit(EXIT_MGL_RELOCOVRFLOW);
  }
  relocbuf[relocnum++] = codeptr;
}



/*
 * Put a jump opcode into the code segment
 */
void putjmp(dest, jmpcode)
addr_t dest;
unsigned int jmpcode;
{
  addr_t distance;

  distance = dest - codeptr - 2;
  if (jmpcode == JMP_UNCOND) {
	if (distance > -128 && distance < 128) {
		putcode(OP_JMP_SHORT);
		putcode((unsigned int)(distance & 0xff));
	} else {
		putcode(OP_JMP_NEAR);
		putint((dest - codeptr - 2) & 0xffff);
	}
  } else {
#ifdef PARANOID
	if (distance <= -128 || distance >= 128)
		interror(40, "conditional jump outside of range");
#endif
	putcode(OP_JMP_COND | jmpcode);
	putcode((unsigned int)(distance & 0xff));
  }
}



/*
 * Put any displacement according to mod and r/m fields into the code segment
 */
static void putdisp(modrm, disp, usereloc)
unsigned int modrm;
addr_t disp;
int usereloc;
{
  switch (modrm & OP_MOD_MASK) {
	case OP_MOD_DIRECT:
		/* Direct adressing */
		if ((modrm & OP_RM_MASK) == OP_RM_BP) {
			if (usereloc)
				setreloc();
			putint((long)disp);
		}
		break;
	case OP_MOD_8BIT:
		/* 8 bit displacement */
#ifdef PARANOID
		if (usereloc)
			interror(66, "cannot use 8 bit displacement with reloc");
#endif
		putcode((unsigned int)(disp & 0xff));
		break;
	case OP_MOD_16BIT:
		/* 16 bit displacement */
		if (usereloc)
			setreloc();
		putint((long)disp);
		break;
	case OP_MOD_REG:
		/* Register instruction, no displacement */
		break;
  }
}



/*
 * Put an opcode into the code segment which is either a reg/reg, reg/mem
 * or mem/reg operation.
 */
void putregop(op, srcreg, destreg, disp)
unsigned int op;
unsigned int srcreg;
unsigned int destreg;
addr_t disp;
{
  unsigned char c1 = op & ~((unsigned int)(OP_WORD_MASK | OP_DIR_MASK));
  unsigned char c2;
  int usereloc;

  /* Determine opcodes to use */
  if ((destreg & REG_8BIT_FLAG) == 0 || (srcreg & REG_8BIT_FLAG) == 0)
	c1 |= OP_WORD_MASK;
  if ((srcreg & REG_MODRM_FLAG) == REG_MODRM_FLAG) {
	/* operation is mem->reg */
	c1 |= OP_DIR_MASK;
	c2 = rmmid(destreg) | (srcreg & ~(unsigned int)REG_16BIT_MASK);
	usereloc = ((srcreg & REG_RELOC_FLAG) == REG_RELOC_FLAG);
  } else if ((destreg & REG_MODRM_FLAG) == REG_MODRM_FLAG) {
	/* operation is reg->mem */
	c2 = rmmid(srcreg) | (destreg & ~(unsigned int)REG_16BIT_MASK);
	usereloc = ((destreg & REG_RELOC_FLAG) == REG_RELOC_FLAG);
  } else {
	/* operation is reg->reg */
#ifdef PARANOID
	if ((destreg & REG_8BIT_FLAG) != (srcreg & REG_8BIT_FLAG))
		interror(81, "register sizes don't match");
#endif
	/* Optimize 'mov' away if possible */
	if (op == OP_MOV && srcreg == destreg)
		return;
	c1 |= OP_DIR_MASK;
	c2 = rmmid(destreg) | rmlow(srcreg) | OP_MOD_REG;
	usereloc = FALSE;
  }

  /* Move into or out of AX is special */
  if (op == OP_MOV &&
      ((srcreg == REG_AX && destreg == REG_16DISP) ||
       (destreg == REG_AX && srcreg == REG_16DISP))) {
	c1 &= OP_WORD_MASK | OP_DIR_MASK;	/* Preserve word and dir bits */
	c1 |= OP_MOV_AX;
	putcode(c1);
	if (usereloc)
		setreloc();
	putint((long)disp);
  } else {
	putcode(c1);
	putcode(c2);
	putdisp(c2, disp, usereloc);
  }
}



/*
 * Put an opcode with an immediate value into the code segment
 */
void putimmed(op, reg, val, disp)
unsigned int op;
unsigned int reg;
long val;
addr_t disp;
{
  unsigned char c1 = OP_IMMED;
  unsigned char c2 = op & OP_IMMED_MASK;

  /* Do some optimization */
  if ((op == OP_IMMED_ADD || op == OP_IMMED_SUB) && val == 0)
	/* Don't optimize for arithmetic with the carry flag! */
	return;

  /* Immediate move into a register is special */
  if (op == OP_MOV) {
	if (val == 0 && (reg & REG_MODRM_FLAG) != REG_MODRM_FLAG) {
		putregop(OP_XOR, reg, reg, 0);
	} else if ((reg & REG_MODRM_FLAG) == REG_MODRM_FLAG) {
		if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG)
			putcode(OP_MOV_MEMIM);
		else
			putcode(OP_MOV_MEMIM | OP_WORD_MASK);
		c2 = (reg & (OP_MOD_MASK | OP_RM_MASK));
		putcode(c2);
		putdisp(c2, disp, (reg & REG_RELOC_FLAG) == REG_RELOC_FLAG);
		if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG)
			putcode((unsigned int)(val & 0x00ff));
		else
			putint(val & 0xffff);
	} else if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG) {
		putcode(OP_MOV_BREGIM | rmlow(reg));
		putcode((unsigned int)(val & 0x00ff));
	} else {
		putcode(OP_MOV_WREGIM | rmlow(reg));
		putint(val & 0xffff);
	}
	return;
  }

  /* Compute first byte of opcode */
  if ((reg & REG_8BIT_FLAG) == 0) {
	c1 |= OP_WORD_MASK;
	if ((val & 0xff80) == 0 || (val & 0xff80) == 0xff80)
		c1 |= OP_SIGN_MASK;
  }
  putcode(c1);

  /* Compute second byte of opcode */
  if ((reg & REG_MODRM_FLAG) == REG_MODRM_FLAG)
	c2 |= reg & ~(unsigned int)OP_IMMED_MASK;
  else
	c2 |= OP_MOD_REG | rmlow(reg);
  putcode(c2);

  /* Output any displacement and the immediate value */
  putdisp(c2, disp, (reg & REG_RELOC_FLAG) == REG_RELOC_FLAG);
  if ((c1 & OP_WORD_MASK) == 0 || (c1 & OP_SIGN_MASK) == OP_SIGN_MASK)
	putcode((unsigned int)(val & 0x00ff));
  else
	putint(val);
}



/*
 * Determine the modrm value necessary to access data relative to a register
 */
unsigned int getmodrm(basereg, disp)
unsigned int basereg;
addr_t disp;
{
  unsigned int modrm;

  if ((disp & 0xff80) == 0 || (disp & 0xff80) == 0xff80)
	modrm = OP_MOD_8BIT | REG_MODRM_FLAG;
  else
	modrm = OP_MOD_16BIT | REG_MODRM_FLAG;
  switch (basereg) {
	case REG_BX:
		modrm |= OP_RM_BX;
		break;
	case REG_BP:
		modrm |= OP_RM_BP;
		break;
	case REG_SI:
		modrm |= OP_RM_IND | OP_RM_SI;
		break;
	case REG_DI:
		modrm |= OP_RM_IND | OP_RM_DI;
		break;
	default:
		modrm = REG_NONE;
		break;
  }
  return(modrm);
}



/*
 * Put an LEA or equivalent instruction into the output code segment
 */
void putlea(destreg, basereg, disp)
unsigned int destreg;
unsigned int basereg;
addr_t disp;
{
  unsigned int modrm;

  /* If we have a relocatable address, we must use an ADD instruction */
  if ((basereg & REG_RELOC_FLAG) == REG_RELOC_FLAG) {
	putregop(OP_MOV, (basereg & ~(unsigned int)REG_RELOC_FLAG), destreg, 0);
	putimmed(OP_IMMED_ADD, (destreg | REG_RELOC_FLAG), (long)disp, 0);
	return;
  }

  /* Otherwise we can use LEA */
  if (disp == 0) {
	putregop(OP_MOV, basereg, destreg, 0);	/* mov destreg,basereg */
  } else {
	modrm = getmodrm(basereg, disp);
	if (modrm != REG_NONE)
		putregop(OP_LEA, destreg, modrm, disp);
	else {
		putregop(OP_MOV, basereg, destreg, 0);
		putimmed(OP_IMMED_ADD, destreg, (long)disp, 0);
	}
  }
}



/*
 * Put a push register instruction into the output code segment
 */
void putstackreg(reg, push, disp)
unsigned int reg;
int push;
addr_t disp;
{
  unsigned int modrm;

  if ((reg & REG_MODRM_FLAG) == 0) {
	if ((reg & REG_8BIT_FLAG) == REG_8BIT_FLAG)
		reg &= REG_8BIT_MASK;
	if (push)
		putcode(OP_PUSH_REG | rmlow(reg));
	else
		putcode(OP_POP_REG | rmlow(reg));
  } else {
	modrm = (push ? OP_MEM_PUSH : 0) |
			(reg & ~((unsigned int)(OP_MOD_MASK | OP_RM_MASK)));
	if (push)
		putcode(OP_PUSH_MEM);
	else
		putcode(OP_POP_MEM);
	putdisp(modrm, disp, FALSE);
  }
}



/*
 * Put a call to the runtime module into the output code segment
 */
void putfunc(func, stack)
int func;
addr_t stack;
{
  if (func >= 0) {
	putimmed(OP_MOV, REG_AH, func, 0);
	putcode(OP_CALL);
	putint((long)(ENTRYOFS - codeptr - 2));
  }
  if (stack > 0)
	/* add sp,#stack */
	putimmed(OP_IMMED_ADD, REG_SP, (long)stack, 0);
}




/*
 ************************************************************************
 *
 *                  Code to support different processors
 *
 ************************************************************************
 */

/*
 * Put a BOUND instruction into the output code segment. This will set
 * a bounds check against the limits setup by the type given.
 */
void putbound(srcreg, tp)
unsigned int srcreg;
struct typesdef *tp;
{
  int ax_pushed = FALSE;

  /* No type given, cannot check */
  if (tp == NULL || !isscalar(tp))
	return;

  /* Check if we have the bounds in the constant segment already */
  if (tp->def.s.boundaddr < 0) {
	tp->def.s.boundaddr = constptr;
	putconst((unsigned char)(tp->def.s.min & 0x00ff));
	putconst((unsigned char)((tp->def.s.min >> 8) & 0x00ff));
	putconst((unsigned char)(tp->def.s.max & 0x00ff));
	putconst((unsigned char)((tp->def.s.max >> 8) & 0x00ff));
  }

  /* If we have a 186+ processor, we can now just use the BOUND instruction */
  if ((srcreg & REG_8BIT_FLAG) == REG_8BIT_FLAG) {
	if (srcreg == REG_AH) {
		putpush(REG_AX);
		ax_pushed = TRUE;
	}
	putregop(OP_MOV, srcreg, REG_AL, 0);
	putcode(OP_CBW);
	srcreg = REG_AX;
  }
  if (is186) {
	putcode(OP_BOUND);
	putcode(OP_MOD_DIRECT | (srcreg & REG_16BIT_MASK) | OP_RM_BP);
	putint(tp->def.s.boundaddr & 0xffff);	/* const seg --> no reloc */
  } else {
	putimmed(OP_IMMED_CMP, srcreg, tp->def.s.min, 0);
	putjmp(codeptr + 6, JMP_JC);
	putimmed(OP_IMMED_CMP, srcreg, tp->def.s.max, 0);
	putjmp(codeptr + 4, JMP_JBE);
	putcode(OP_INT);
	putcode(5);
  }
  if (ax_pushed)
	putpop(REG_AX);
}



/*
 * Put an enter instruction into the output code segment
 */
static void putenter(stack, level)
addr_t stack;
int level;
{
  int i;

  stackofs = stack & 0xffff;
  if (is186) {
	level = (level > 1 ? level - 1 : 0);
	putcode(OP_ENTER);
	putint((long)(stack & 0xffff));
	putcode((unsigned int)(level & 0xff));
  } else {
	putpush(REG_BP);				/* push bp */
	if (level > 1) {
		putregop(OP_MOV, REG_SP, REG_AX, 0);	/* mov ax,sp */
		for (i = 0; i < (level - 2); i++) {
			/* sub bp,2 */
			putimmed(OP_IMMED_SUB, REG_BP, 2, 0);
			/* push [bp] */
			putstackreg(REG_BP_8DISP, TRUE, 0);
		}
		putpush(REG_AX);			/* push ax */
		putregop(OP_MOV, REG_AX, REG_BP, 0);	/* mov bp,ax */
	} else
		putregop(OP_MOV, REG_SP, REG_BP, 0);	/* mov bp,sp */
	if (stackofs > 0)
		/* sub sp, stackofs */
		putimmed(OP_IMMED_SUB, REG_SP, (long)stackofs, 0);
  }
  putpush(REG_DI);
  putpush(REG_SI);
}



/*
 * Put a leave instruction into the output code segment
 */
static void putleave()
{
  putpop(REG_SI);
  putpop(REG_DI);
  if (is186)
	putcode(OP_LEAVE);
  else {
	if (stackofs > 0)
		/*
		 * Don't reset stackofs here - CODE_PROC_END might
		 * be called more than once in a procedure!
		 */
		putregop(OP_MOV, REG_BP, REG_SP, 0);	/* mov sp,bp */
	putcode(OP_POP_REG | rmlow(REG_BP));		/* pop bp */
  }
}



/*
 * Put a set-bit instruction into the output code segment
 */
void putsetbit(reg, cond)
unsigned int reg;
unsigned int cond;
{
  if (!is386) {
	putimmed(OP_MOV, reg, 1, 0);
	putjmp(codeptr + 4, cond);
	putregop(OP_XOR, reg, reg, 0);
  } else {
	putcode(OP_386);
	putcode(OP_SET_COND | cond);
	putcode(OP_MOD_REG | rmlow(reg));
  }
}




/*
 ************************************************************************
 *
 *                   Conditional and nested commands
 *
 ************************************************************************
 */

/*
 * Structure used to keep track of nested commands
 */
#define MAX_JUMP	32		/* Maximum number of jumps */

typedef enum {
	NEST_IF,			/* Type for if command */
	NEST_WHILE,			/* Type for while command */
	NEST_REPEAT,			/* Type for repeat command */
	NEST_SELECT			/* Type for select command */
} nesttype;

struct nest {
	nesttype     type;		/* Type of nesting statement */
	int          selecterr;		/* Error in select, skip rest */
	int          jmpnum;		/* Number of addresses to jumps */
	addr_t       jmptab[MAX_JUMP];	/* Pointers to jump instructions */
	addr_t       jmpdest[MAX_JUMP];	/* Addresses where jumps are going to */
	addr_t       loopaddr;		/* Return address for loops */
	int          breaknum;		/* Number of break statements */
	int          breaktabsize;	/* Size of break address table */
	addr_t      *breaktab;		/* Pointers to jumps for break */
	struct nest *next;
};

static struct nest *nestlist = NULL;	/* List of nested commands */



/*
 * Put the condition handling code into the output buffer
 */
static void putcond(ep)
struct expr *ep;
{
  struct meminfo di;
  unsigned char jmpcode;

#ifdef PARANOID
  if (exprtype(ep) != EXPR_BOOL)
	interror(59, "invalid expression in condition");
#endif

  /*
   * We simply leave comparison to the boolean expression routine, but
   * don't let it return a value. This doesn't work if we have a boolean
   * leaf node. In that case we have to load the value into AL.
   */
  di.t = NULL;
  if (!isleaf(ep)) {
	di.memtype = MEM_NOADDR | MEM_NOSIZE;
	putboolexpr(ep, &di);
  } else {
	di.memtype = MEM_REGISTER | MEM_NOSIZE;
	di.addr.r = REG_AL;
	putboolexpr(ep, &di);				/* mov al,val */
	putregop(OP_OR, REG_AL, REG_AL, 0);		/* or al,al */
  }

  /* Determine type of jump required */
  switch (ep->opcode) {
	case CMD_EQ:	jmpcode = JMP_JZ;  break;
	case CMD_GT:	jmpcode = JMP_JG;  break;
	case CMD_GE:	jmpcode = JMP_JGE; break;
	case CMD_LT:	jmpcode = JMP_JL;  break;
	case CMD_LE:	jmpcode = JMP_JLE; break;
	case CMD_NE:	jmpcode = JMP_JNZ; break;
	default:	jmpcode = JMP_JNZ; break;	/* for AND, OR, XOR */
  }
  putjmp(codeptr + 5, jmpcode);			/* jcond $+3 */
  nestlist->jmptab[nestlist->jmpnum++] = codeptr;
  codeptr += 3;					/* provide space for jmp near */
}



/*
 * Create a new nest structure
 */
static void newnest(type)
nesttype type;
{
  struct nest *np;

  np = (struct nest *)nbmalloc(sizeof(struct nest));
  np->next = nestlist;
  np->type = type;
  np->loopaddr = codeptr;
  nestlist = np;
}



/*
 * Put out the code for the 'else' branch of an 'if' statement
 */
static void putelse()
{
#ifdef PARANOID
  if (nestlist == NULL || nestlist->type != NEST_IF || nestlist->jmpnum != 1)
	interror(62, "missing if statement for else");
#endif

  nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr + 3;/* where if jumps to */
  nestlist->jmptab[nestlist->jmpnum++] = codeptr;	/* mark new jump */
  codeptr += 3;					/* provide space for jmp near */
}



/*
 * Put an item branch of a select command into the output code segment
 */
static void putitem(ep)
struct expr *ep;
{
  int itemchar;

#ifdef PARANOID
  if (nestlist == NULL || nestlist->type != NEST_SELECT)
	interror(63, "no select statement for item");
  if (ep == NULL || exprtype(ep) != EXPR_NUM || !isconst(ep) ||
      ep->spec.cval.val.i > 9)
	interror(64, "invalid item number");
#endif

  /* Check that we don't have too many items in select statement */
  if (nestlist->jmpnum >= MAX_JUMP) {
	error("too many items in select statement");
	nestlist->selecterr++;
  }
  if (nestlist->selecterr > 0)
	return;

  /* Let the last item terminate and point to this new one */
  if (nestlist->jmpnum > 0) {
	putjmp(nestlist->loopaddr, JMP_UNCOND);
	nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr;
  }

  /* Now put out a new compare instruction */
  if (ep->spec.cval.val.i < 0)
	itemchar = 0xff;
  else
	itemchar = (ep->spec.cval.val.i & 0x0f) + '0';
  putimmed(OP_IMMED_CMP, REG_AL, itemchar, 0);		/* cmp ax,itemchar */
  putjmp(codeptr + 5, JMP_JZ);				/* jz $+3 */
  nestlist->jmptab[nestlist->jmpnum++] = codeptr;
  codeptr += 3;					/* provide space for jmp near */
}



/*
 * Break command to jump to end of innermost loop command
 */
static void putbreak()
{
  struct nest *np;
  addr_t *newbuf;
  int newsize;

  /* Search innermost loop command */
  for (np = nestlist; np != NULL; np = np->next)
	if (np->type == NEST_SELECT ||
	    np->type == NEST_WHILE ||
	    np->type == NEST_REPEAT)
		break;
  if (np == NULL) {
	error("invalid break command");
	return;
  }

  /* Lets see, if our break pointer buffer is large enough */
  if (np->breaktab == NULL || np->breaknum >= np->breaktabsize) {
	newsize = np->breaktabsize + 32;
	newbuf = (addr_t *)nbmalloc(sizeof(addr_t) * newsize);
	if (np->breaktab != NULL) {
		memcpy(newbuf, np->breaktab, sizeof(addr_t) * np->breaknum);
		free(np->breaktab);
	}
	np->breaktabsize = newsize;
	np->breaktab = newbuf;
  }

  /* Add current code pointer into the break table, and put out new jump */
  np->breaktab[np->breaknum++] = codeptr;
  codeptr += 3;					/* provide space for jmp near */
}



/*
 * End nested command
 */
static void putendnest(ep)
struct expr *ep;
{
  struct nest *temp;
  addr_t tmpadr;
  int i;

#ifdef PARANOID
  if (nestlist == NULL)
	interror(65, "invalid endnest, nestlist empty");
#endif

  /* Terminate current command */
  switch (nestlist->type) {
	case NEST_IF:
		nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr;
		break;
	case NEST_WHILE:
		putjmp(nestlist->loopaddr, JMP_UNCOND);
		nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr;
		break;
	case NEST_REPEAT:
		putcond(ep);
		nestlist->jmpdest[nestlist->jmpnum - 1] = nestlist->loopaddr;
		break;
	case NEST_SELECT:
		nestlist->jmpdest[nestlist->jmpnum - 1] = codeptr;
		putjmp(nestlist->loopaddr, JMP_UNCOND);
		break;
  }

  /* Resolve jump list */
  tmpadr = codeptr;
  for (i = 0; i < nestlist->jmpnum; i++) {
	codeptr = nestlist->jmptab[i];
	putjmp(nestlist->jmpdest[i], JMP_UNCOND);
  }

  /* Resolve break list */
  if (nestlist->breaktab != NULL) {
	for (i = 0; i < nestlist->breaknum; i++) {
		codeptr = nestlist->breaktab[i];
		putjmp(tmpadr, JMP_UNCOND);
	}
	free(nestlist->breaktab);
  }
  codeptr = tmpadr;

  /* Remove current nested command from list */
  temp = nestlist;
  nestlist = nestlist->next;
  free(temp);
}




/*
 ************************************************************************
 *
 *                Determine the address of a variable
 *
 ************************************************************************
 */

/*
 * Return a pointer to the inuse flag corresponding to a register
 */
static int *getinuse(reg)
unsigned int reg;
{
  switch (reg) {
	case REG_BX:
		return(&bx_inuse);
	case REG_CX:
		return(&cx_inuse);
	case REG_DX:
		return(&dx_inuse);
	case REG_SI:
		return(&si_inuse);
	case REG_DI:
		return(&di_inuse);
	default:
		return(NULL);
  }
}



/*
 * Set the meminfo record for a terminal symbol (i.e. a symbol which
 * appears last in a record reference).
 */
static void setfirstinfo(sp, dp, basereg, sizereg)
struct sym *sp;
struct meminfo *dp;
unsigned int basereg;
unsigned int sizereg;
{
  unsigned int basedisp = REG_BP_16DISP;

  if (isfuncsym(sp)) {
#ifdef PARANOID
	if (sp->def.f.ret == NULL || sp != curproc)
		interror(72, "invalid function assignment");
#endif
	/*
	 * Assigning a return value to a function means putting the value
	 * into some space provided by the caller. If the return value is
	 * non-scalar, there is a pointer to the return value space on the
	 * stack. With scalar return types, the space for the return value
	 * is directly located on the stack and accessible through BP.
	 */
	if (isnonscalar(sp->def.f.ret)) {
		putregop(OP_MOV, REG_BP_16DISP, basereg, sp->def.f.retaddr);
		dp->memtype = MEM_RELATIVE;
		dp->addr.i = 0;
		dp->addr.r = basereg;
		if (sp->def.f.ret->type == EXPR_STRING && sizereg != REG_NONE) {
			putregop(OP_MOV, REG_BP_16DISP, sizereg,
							sp->def.f.retaddr + 2);
			dp->memtype |= MEM_SIZEREG;
			dp->size.i = 0;
			dp->size.r = sizereg;
		} else {
			dp->memtype |= MEM_IMMEDIATE;
			dp->size.i = sp->def.f.ret->size;
		}
	} else if (isscalar(sp->def.f.ret)) {
		dp->memtype = MEM_RELATIVE;
		dp->addr.i = sp->def.f.retaddr;
		dp->addr.r = REG_BP;
	}
#ifdef PARANOID
	else
		interror(50, "invalid function return type");
#endif
  } else if (isvarsym(sp)) {
#ifdef PARANOID
	if (sp->level > curlevel)
		interror(73, "invalid symbol level");
#endif
	/*
	 * Handle a variable as the destination. We have to consider the
	 * following cases:
	 *
	 *  1.)  Variable is scalar and can be directly accessed because:
	 *          - it's defined within the current context
	 *          - it's passed to a function by value
	 *  2.)  Variable is scalar and has been passed by reference. In this
	 *       case we only have a pointer on the stack.
	 *  3.)  Variable is non-scalar and can be directly accessed because it
	 *       has been defined within the current context.
	 *  4.)  Variable is non-scalar and has been passed by value. In this
	 *       case we also only have a pointer to the variable value on the
	 *       stack. However, the variable value has to be copied into a
	 *       temporary memory first (usually located on the stack as well),
	 *       so that the original variable remains untouched even when the
	 *       called function modifies the variable.
	 * 5.)   Variable is non-scalar and has been passed by reference. We
	 *       also have a pointer on the stack like in case 4.) but without
	 *       a temporary copy.
	 *
	 * Therefore, cases 1.) and 3.) can be handled together, like cases
	 * 2.), 4.) and 5.) are also handled the same.
	 */
	if ((isscalar(sp->def.v.t) && sp->def.v.attr != ATTR_REF) ||
	    sp->addr < 0) {
		/*
		 * If we have a variable, which is defined within the current
		 * context, or a variable which has NOT been passed by re-
		 * ference (non-scalars are always passed with pointers, not
		 * by reference, which is not the same but very similar), we
		 * can directly access the variable without having to calculate
		 * the address at runtime.
		 */
		if (sp->level == curlevel) {
			dp->memtype = MEM_RELATIVE;
			dp->addr.i = sp->addr;
			dp->addr.r = REG_BP;
		} else if (sp->level <= 0) {
			/* This catches static variables and record elements */
			dp->memtype = MEM_ABSOLUTE;
			dp->addr.i = sp->addr;
		} else {
			putregop(OP_MOV, REG_BP_16DISP, basereg,
						-((sp->level - 1) * 2));
			dp->memtype = MEM_RELATIVE;
			dp->addr.i = sp->addr;
			dp->addr.r = basereg;
		}
	} else {
		/*
		 * It's somewhat more complicated with non-scalars: they
		 * can either be declared within the current context, or
		 * passed as an argument. In the latter case, we only have
		 * a pointer to the variable space on the stack. The first
		 * case is handled above (with sp->addr < 0).
		 * Also with all variables passed by reference we have a
		 * pointer on the stack instead of the actual value, so we
		 * have to load the pointer in order to be able to load
		 * or save a value.
		 */
		if (sp->level == curlevel) {
			putregop(OP_MOV, REG_BP_16DISP, basereg, sp->addr);
			dp->memtype = MEM_RELATIVE;
			dp->addr.i = 0;
			dp->addr.r = basereg;
		} else if (sp->level <= 0) {
			/* This catches record elements, should never happen */
			dp->memtype = MEM_ABSOLUTE;
			dp->addr.i = sp->addr;
		} else {
			switch (basereg) {
				case REG_BX:
					basedisp = REG_BX_16DISP;
					break;
				case REG_BP:
					basedisp = REG_BP_16DISP;
					break;
				case REG_SI:
					basedisp = REG_SI_16DISP;
					break;
				case REG_DI:
					basedisp = REG_DI_16DISP;
					break;
#ifdef PARANOID
				default:
					interror(110, "invalid base register");
#endif
			}
			putregop(OP_MOV, REG_BP_16DISP, basereg,
						-((sp->level - 1) * 2));
			putregop(OP_MOV, basedisp, basereg, sp->addr);
			dp->memtype = MEM_RELATIVE;
			dp->addr.i = 0;
			dp->addr.r = basereg;
		}
	}

	/*
	 * Determine the size of a non-scalar.  If it's not a strings, a static
	 * variable, not an argument or an argument passed by value, we know
	 * the size from the variable type. Only for strings passed by refe-
	 * rence the destination size has been pushed onto the stack together
	 * with the address.
	 */
	if (isnonscalar(sp->def.v.t)) {
		if (sp->def.v.t->type == EXPR_STRING &&
		    sp->def.v.attr == ATTR_REF &&
		    sp->addr >= 0 && sp->level > 0 &&
		    sizereg != REG_NONE) {
			if (sp->level == curlevel) {
				putregop(OP_MOV, REG_BP_16DISP, sizereg,
								sp->addr + 2);
			} else {
				if (si_inuse || basereg == REG_SI)
					putpush(REG_SI);
				putregop(OP_MOV, REG_BP_16DISP, REG_SI,
							-((sp->level - 1) * 2));
				putregop(OP_MOV, REG_SI_16DISP, sizereg, sp->addr);
				if (si_inuse || basereg == REG_SI)
					putpop(REG_SI);
			}
			dp->memtype |= MEM_SIZEREG;
			dp->size.r = sizereg;
		} else {
			dp->memtype |= MEM_IMMEDIATE;
			dp->size.i = sp->def.v.t->size;
		}
	}
  }
}



/*
 * Set meminfo record for an array. The record should already contain
 * the data for the base element (i.e. the array itself).
 */
static void setarrayinfo(vp, dp, basereg)
struct varinfo *vp;
struct meminfo *dp;
unsigned int basereg;
{
  struct typesdef *elemtype = vp->type->def.a.basetype;
  struct typesdef *indextype = vp->type->def.a.indextype;
  struct meminfo di;
  int *addrinuse, *sizeinuse;
  long indexval;

#ifdef PARANOID
  if (!isscalar(indextype))
	interror(105, "invalid index type");
#endif

  /* If the index is constant, we can compute it at compile time */
  if (isconst(vp->index)) {
	indexval = getord(vp->index);
	if (indexval < indextype->def.s.min ||
	    indexval > indextype->def.s.max ||
	    (indexval - indextype->def.s.min) >= vp->type->def.a.elementnum) {
		error("array subscript out of range");
		return;
	}
	indexval   -= indextype->def.s.min;
	dp->addr.i += indexval * elemtype->size;
	return;
  }

  /*
   * Otherwise we have to compute it at runtime. For this we have to mark
   * the base and size registers as used.
   */
  addrinuse = NULL;
  if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
	addrinuse = getinuse(dp->addr.r);
  if (addrinuse != NULL)
	(*addrinuse)++;

  sizeinuse = NULL;
  if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
	sizeinuse = getinuse(dp->size.r);
  if (sizeinuse != NULL)
	(*sizeinuse)++;

  /*
   * We can now setup a new meminfo for the subexpression and let put
   * out the code to compute the index.
   */
  di.memtype = MEM_REGISTER | MEM_NOSIZE;
  di.t = NULL;
  switch (indextype->type) {
	case EXPR_ENUM:
	case EXPR_NUM:
		di.addr.r = REG_AX;
		putintexpr(vp->index, &di);
		break;
	case EXPR_BOOL:
		di.addr.r = REG_AL;
		putboolexpr(vp->index, &di);
		putcode(OP_CBW);
		break;
	case EXPR_CHAR:
		di.addr.r = REG_AL;
		putcharexpr(vp->index, &di);
		putcode(OP_CBW);
		break;
	default:
		/* Can't happen, already checked above */
		break;
  }

  /* Check that the index is within bounds */
  putbound(REG_AX, indextype);

  /* If the min of the index is not zero, we have to subtract the min */
  if (indextype->def.s.min != 0)
	putimmed(OP_SUB, REG_AX, indextype->def.s.min, 0);

  /* The index is now in AX, so that we can compute the offset */
  if (elemtype->size == 2) {
	putcode(OP_SHIFT_1);
	putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX));
  } else if (elemtype->size == 4) {
	putcode(OP_SHIFT_1);
	putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX));
	putcode(OP_SHIFT_1);
	putcode(OP_MOD_REG | OP_SHIFT_SHL | rmlow(REG_AX));
  } else if (elemtype->size > 1) {
	if (dx_inuse)
		putpush(REG_DX);
	putimmed(OP_MOV, REG_DX, elemtype->size, 0);
	putcode(OP_NONIM | OP_WORD_MASK);
	putcode(OP_NONIM_MUL | OP_MOD_REG | rmlow(REG_DX));
	if (dx_inuse)
		putpop(REG_DX);
  }

  /* We have the offset in AX now, so we can finally set the meminfo */
  if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
	putimmed(OP_MOV, basereg, dp->addr.i, 0);
	putregop(OP_ADD, REG_AX, basereg, 0);
	dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
	dp->addr.i = 0;
	dp->addr.r = basereg;
  } else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
	if (dp->addr.r == REG_BP) {
		putlea(basereg, dp->addr.r, dp->addr.i);
		putregop(OP_ADD, REG_AX, basereg, 0);
		dp->addr.r = basereg;
	} else {
		putlea(dp->addr.r, dp->addr.r, dp->addr.i);
		putregop(OP_ADD, REG_AX, dp->addr.r, 0);
	}
	dp->addr.i = 0;
  }

  /* Restore the inuse indicators */
  if (addrinuse)
	(*addrinuse)--;
  if (sizeinuse)
	(*sizeinuse)--;
}



/*
 * Set meminfo record for a record variable. The meminfo should already contain
 * the data for the base element (i.e. the record itself). This is very similar
 * but not the same as 'setfirstinfo'.
 */
static void setrecordinfo(vp, dp, basereg)
struct varinfo *vp;
struct meminfo *dp;
unsigned int basereg;
{
  struct sym *sp = vp->symbol;

#ifdef PARANOID
  if (sp == NULL || (!isvarsym(sp) && !isfuncsym(sp)))
	interror(107, "invalid type for record element");
  if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE && dp->addr.r == REG_BP)
	interror(108, "BP register not allowed for record elements");
#endif

  /* Handle function return value. This always has to be a terminal. */
  if (isfuncsym(sp)) {
#ifdef PARANOID
	if (sp->def.f.ret == NULL ||
	    sp->def.f.ret->type != EXPR_RECORD || sp != curproc)
		interror(106, "invalid function assignment");
#endif
	if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
		putregop(OP_MOV, REG_BP_16DISP, basereg, sp->def.f.retaddr);
		dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
		dp->addr.r = basereg;
	} else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
		putregop(OP_ADD, REG_BP_16DISP, dp->addr.r, sp->def.f.retaddr);
	return;
  }

  /* Handle variable record elements */
#ifdef PARANOID
  if (sp->def.v.t->type != EXPR_RECORD)
	interror(109, "invalid record");
#endif
  if (sp->level == curlevel) {
	if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
		dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
		dp->addr.r  = REG_BP;
	} else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
		putregop(OP_ADD, REG_BP, dp->addr.r, 0);
	}
  } else if (sp->level > 0) {
	if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
		putregop(OP_MOV, REG_BP_16DISP, basereg,
						-((sp->level - 1) * 2));
		dp->memtype = (dp->memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
		dp->addr.r  = basereg;
	} else {
		putregop(OP_ADD, REG_BP_16DISP, dp->addr.r,
						-((sp->level - 1) * 2));
	}
  }
  dp->addr.i += sp->addr;
}



/*
 * Set a meminfo record according to the variable pointed to in the
 * expression.
 */
void setmeminfo(ep, dp, basereg, sizereg)
struct expr *ep;
struct meminfo *dp;
unsigned int basereg;
unsigned int sizereg;
{
  struct varinfo *vp, *prev;

#ifdef PARANOID
  if (!isvariable(ep))
	interror(57, "expression is not a variable");
#endif

  /*
   * Find out which register we can use in case we need to calculate an
   * address.
   */
  if (basereg == REG_NONE) {
	if (!di_inuse)
		basereg = REG_DI;
	else if (!si_inuse)
		basereg = REG_SI;
	else if (!bx_inuse)
		basereg = REG_BX;
	else if (!cx_inuse)
		basereg = REG_CX;
	else if (!dx_inuse)
		basereg = REG_DX;
	else
		basereg = REG_AX;
  }

  /*
   * Handle the first element in the varinfo record (which is the
   * last element in a record reference).
   */
#ifdef PARANOID
  if (ep->spec.var.symbol == NULL ||
      (!isvarsym(ep->spec.var.symbol) && !isfuncsym(ep->spec.var.symbol)))
	interror(103, "missing symbol in variable specification");
#endif
  setfirstinfo(ep->spec.var.symbol, dp, basereg, sizereg);
  if (ep->spec.var.index != NULL)
	setarrayinfo(&(ep->spec.var), dp, basereg);

  /*
   * Now scan through the variable info list and handle all record references.
   * At this point, if we have a record element, the meminfo record contains
   * the offset to the element from the start of the record.
   */
  prev = &(ep->spec.var);
  for (vp = ep->spec.var.next; vp != NULL; vp = vp->next) {
#ifdef PARANOID
	if (vp->symbol == NULL || !isvarsym(vp->symbol))
		interror(104, "missing symbol in variable specification");
#endif
	prev = vp;
	setrecordinfo(vp, dp, basereg);
	if (vp->index != NULL)
		setarrayinfo(vp, dp, basereg);
  }

  /*
   * Finally we have to find out if the variable is in the static area, so
   * that we have to set the relocation flag. Also save the variable type
   * into the meminfo record for later use by the bound checker.
   */
  if (prev->symbol->level <= 0)
	dp->memtype |= MEM_RELOC_FLAG;
  dp->t = ep->type;
}





/*
 ************************************************************************
 *
 *                      Interface to parser
 *
 ************************************************************************
 */

/*
 * Save debug info for current statement
 */
static void setdebug()
{
  if (debug && (debugnum == 0 || debugbuf[debugnum - 1].lineno != lineno)) {
	debugbuf[debugnum].addr = codeptr;
	debugbuf[debugnum].lineno = lineno;
	if (++debugnum >= DEBUGSIZE) {
		prnerr0("debugging info buffer overflow");
		exit(EXIT_MGL_DEBUGOVRFLOW);
	}
  }
}



/*
 * Compile a timeout value and put it into DX.
 */
static void puttimeout(ep)
struct expr *ep;
{
  struct meminfo di;

  if (ep != NULL) {
	di.memtype = MEM_REGISTER;
	di.addr.r = REG_DX;
	di.t = NULL;
	putintexpr(ep, &di);
  } else
	putregop(OP_XOR, REG_DX, REG_DX, 0);
}



/*
 * With an assignment to a complex variable we have a problem: the
 * expression might involve the variable itself, like:  a := b + a.
 * In this case we cannot allow the expression calculation routines
 * to put the result directly into the destination, but have to
 * use a temporary area as the initial destination. After doing the
 * expression the data has to be copied out of the temp area into
 * the final variable.
 */
static void handlecomplex(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
  struct meminfo di;
  unsigned int tempreg = REG_DI;
  unsigned int modrm;
  int *addrinuse = NULL;
  int *sizeinuse = NULL;
  int needcopy;
  addr_t size;

  /*
   * We don't need to copy if the expression is a leaf node, or it's
   * a function of which we know that it doesn't use any complex
   * variables:
   *	1.) it has to be an internal function
   *	2.) it has to have no string arguments
   * Presently, there is only one such function: getbootp.
   */
  needcopy = (!isleaf(ep) && ep->opcode != CMD_GETBOOTP);

  di = *dp;
  if (needcopy) {
	/*
	 * If the expression is a leaf node, there is no need to use a
	 * temporary buffer. Otherwise we have to provide temp space on
	 * the stack. We don't have to care much about used registers,
	 * because this routine is only called from 'docmd'.
	 */
	addrinuse = NULL;
	if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
		addrinuse = getinuse(dp->addr.r);
	if (addrinuse != NULL)
		/* Prevent base pointer to get used by expression */
		(*addrinuse)++;

	sizeinuse = NULL;
	if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
		sizeinuse = getinuse(dp->size.r);
	if (sizeinuse != NULL)
		/* Prevent size register to get used by expression */
		(*sizeinuse)++;

	if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
		tempreg = (dp->addr.r == REG_DI ? REG_SI :
		           dp->addr.r == REG_BX ? REG_DI : REG_BX);
	else
		tempreg = REG_DI;

	if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
		putregop(OP_SUB, dp->size.r, REG_SP, 0);
	else {
		if (exprtype(ep) == EXPR_STRING)
			size = (dp->size.i + 2) & 0xfffe;
		else
			size = (dp->size.i + 1) & 0xfffe;
		putimmed(OP_SUB, REG_SP, size, 0);
	}
	putregop(OP_MOV, REG_SP, tempreg, 0);
	di.memtype = (di.memtype & ~(MEM_ADR_MASK | MEM_RELOC_FLAG)) |
								MEM_RELATIVE;
	di.addr.i = 0;
	di.addr.r = tempreg;
  }

  /* Process the expression */
  switch (exprtype(ep)) {
	case EXPR_STRING:
		putstrexpr(ep, &di, FALSE);
		break;
	case EXPR_ARRAY:
	case EXPR_RECORD:
		putcomplexexpr(ep, &di);
		break;
	default:
		break;
  }

  /*
   * If we used a temporary data area, copy the result into the variable.
   * Here we can safely use any register we want, because this routine
   * returns back to the parser.
   */
  if (needcopy) {
	if (addrinuse != NULL)
		(*addrinuse)--;
	if (sizeinuse != NULL)
		(*sizeinuse)--;

	if (tempreg == REG_DI) {
		putregop(OP_MOV, REG_DI, REG_AX, 0);
		tempreg = REG_AX;
	}

	/* Set destination address into DI */
	if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
		modrm = 0;
		if ((dp->memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
			modrm = REG_RELOC_FLAG;
		putlea(REG_DI, dp->addr.r | modrm, dp->addr.i);
	} else if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
		putcode(OP_MOV_WREGIM | rmlow(REG_DI));
		if ((dp->memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
			setreloc();
		putint(dp->addr.i);
	}

	/*
	 * Set source register to our temporary area, the expression is
	 * not allowed to touch the base register in 'di', e.g. our tempreg.
	 */
	if (tempreg == REG_SI) {
		putregop(OP_MOV, REG_SI, REG_AX, 0);	/* Save it for later */
		tempreg = REG_AX;
	} else
		putregop(OP_MOV, tempreg, REG_SI, 0);

	/* Set copy size */
	if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
		putregop(OP_MOV, dp->size.r, REG_CX, 0);
	else {
		if (exprtype(ep) == EXPR_STRING)
			size = (dp->size.i + 2) & 0xfffe;
		else
			size = (dp->size.i + 1) & 0xfffe;
		putimmed(OP_MOV, REG_CX, size, 0);
	}

	/* Now do the copy */
	putcode(OP_REP);
	putcode(OP_MOVSB);

	/* Restore the stack */
	putregop(OP_MOV, REG_SI, REG_SP, 0);
  }
}



/*
 * Handle high level commands and break them down into smaller pieces.
 * This is basically one big switch statement.
 */
void docmd(cmd, sp, ep1, ep2)
codes cmd;
struct sym *sp;
struct expr *ep1;
struct expr *ep2;
{
  struct meminfo di;

  setdebug();
  switch (cmd) {
	case CODE_PROC_START:
		/* Start a new procedure or function */
		putenter(sp->addr, sp->level);
		break;
	case CODE_PROC_END:
		/* Quit current procedure and return to previous procedure */
		if (isfuncsym(curproc) && curproc->def.f.ret != NULL) {
			if (isscalar(curproc->def.f.ret) &&
			    curproc->def.f.ret->size < 2)
				putregop(OP_MOV, REG_BP_16DISP | REG_8BIT_FLAG,
						REG_AL, curproc->def.f.retaddr);
			else
				putregop(OP_MOV, REG_BP_16DISP,
						REG_AX, curproc->def.f.retaddr);
		}
		putleave();					
		putcode(OP_RET);
		break;
	case CODE_RESTART:
		/* Restart is simply a jump to the beginning of current menu */
#ifdef PARANOID
		if (curproc == NULL || !isfuncsym(curproc))
			interror(60, "invalid curproc");
#endif
		putjmp(curproc->def.f.restartaddr, JMP_UNCOND);
		break;
	case CODE_CALL_PROC:
		/* Call a procedure */
#ifdef PARANOID
		if (!isproc(ep1))
			interror(71, "invalid procedure call");
#endif
		di.memtype = MEM_NOADDR | MEM_NOSIZE;
		di.t = NULL;
		putproc(ep1, &di);
		break;
	case CODE_ASSIGN:
		setmeminfo(ep1, &di, REG_DI, REG_CX);
		switch (exprtype(ep2)) {
			case EXPR_NUM:
			case EXPR_ENUM:
				/* Integer assignment */
				putintexpr(ep2, &di);
				break;
			case EXPR_CHAR:
				/* Character assignment */
				putcharexpr(ep2, &di);
				break;
			case EXPR_BOOL:
				/* Boolean assignment */
				putboolexpr(ep2, &di);
				break;
			case EXPR_STRING:
			case EXPR_ARRAY:
			case EXPR_RECORD:
				/* Non-scalar assignment */
				handlecomplex(ep2, &di);
				break;
#ifdef PARANOID
			default:
				interror(69, "invalid symbol type in assignment");
				break;
#endif
		}
		break;
	case CODE_INT_PRINT:
		/* Print integer number */
		di.memtype = MEM_REGISTER;
		di.addr.r = REG_CX;
		di.t = NULL;
		putintexpr(ep1, &di);
		putfunc(FUNC_iprint, 0);
		break;
	case CODE_STR_PRINT:
		/* Print string */
		di.memtype = MEM_REGISTER;
		di.addr.r = REG_SI;
		di.t = NULL;
		putstrexpr(ep1, &di, FALSE);
		putfunc(FUNC_sprint, 0);
		break;
	case CODE_CHAR_PRINT:
		/* Print character */
		di.memtype = MEM_REGISTER;
		di.addr.r = REG_CL;
		di.t = NULL;
		putcharexpr(ep1, &di);
		putfunc(FUNC_cprint, 0);
		break;
	case CODE_INT_GET:
		/* Read an integer number */
		puttimeout(ep2);
		setmeminfo(ep1, &di, REG_NONE, REG_NONE);
		putfunc(FUNC_iget, 0);
		putsaveintreg(REG_AX, &di);
		break;
	case CODE_CHAR_GET:
		/* Read a character */
		puttimeout(ep2);
		setmeminfo(ep1, &di, REG_NONE, REG_NONE);
		putfunc(FUNC_cget, 0);
		putsaveintreg(REG_AL, &di);
		break;
	case CODE_STR_GET:
		/* Read a string */
		puttimeout(ep2);
		setmeminfo(ep1, &di, REG_NONE, REG_CX);
		if ((di.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
			putcode(OP_MOV_WREGIM | rmlow(REG_DI));
			if ((di.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
				setreloc();
			putint((long)(di.addr.i & 0xffff));
#ifdef PARANOID
		} else if ((di.memtype & MEM_ADR_MASK) != MEM_RELATIVE) {
			interror(48, "invalid meminfo in get string");
#endif
		} else {
			putlea(REG_DI, di.addr.r, di.addr.i);
		}
		if ((di.memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE)
			putimmed(OP_MOV, REG_CX, (long)di.size.i, 0);
		else
			putregop(OP_MOV, di.size.r, REG_CX, 0);
		putfunc(FUNC_sget, 0);
		break;
	case CODE_PUSH_IPADDR:
		/* Push an IP address onto the stack */
		if (sp == NULL) {
			putregop(OP_XOR, REG_CX, REG_CX, 0);	/* xor cx,cx */
			putpush(REG_CX);			/* push cx */
			putpush(REG_CX);			/* push cx */
		} else {
			putcode(OP_MOV_WREGIM | rmlow(REG_CX));	/* mov cx,val */
			putint((*(__u16 *)&(sp->name[2])) & 0xffff);
			putpush(REG_CX);			/* push cx */
			putcode(OP_MOV_WREGIM | rmlow(REG_CX));	/* mov cx,val */
			putint((*(__u16 *)&(sp->name[0])) & 0xffff);
			putpush(REG_CX);			/* push cx */
		}
		break;
	case CODE_LOAD:
		/* Load new file. Two IP addresses are on the stack already */
		di.memtype = MEM_REGISTER;
		di.addr.r = REG_AX;
		di.t = NULL;
		putstrexpr(ep1, &di, FALSE);
		putpush(REG_AX);
		putfunc(FUNC_load, 10);
		break;
	case CODE_IF:
		/* Start IF command */
		newnest(NEST_IF);
		putcond(ep1);
		break;
	case CODE_ELSE:
		/* Start ELSE command */
		putelse();
		break;
	case CODE_WHILE:
		/* Start WHILE command */
		newnest(NEST_WHILE);
		putcond(ep1);
		break;
	case CODE_REPEAT:
		/* Start REPEAT command */
		newnest(NEST_REPEAT);
		break;
	case CODE_SELECT:
		/* Start select command */
		newnest(NEST_SELECT);
		puttimeout(ep1);
		putfunc(FUNC_getsel, 0);
		break;
	case CODE_ITEM:
		/* Start new ITEM selection */
		putitem(ep1);
		break;
	case CODE_BREAK:
		/* Terminate current nested command */
		putbreak();
		break;
	case CODE_ENDNEST:
		/* Terminate current nesting command */
		putendnest(ep1);
		break;
  }
}




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

/*
 * Main routine of the code generator. It parses the input file and
 * assembles the output binary.
 */
int gencode(infile)
char *infile;
{
  char *fname;
  long codesize;
  long relocval;
  long debugptr;
  int outfile;
  unsigned int j;

  /* Clear code and data buffers and write runtime module into code segment */
  memset(codebuf, OP_NOP, sizeof(codebuf));
  memset(constbuf, 0, sizeof(constbuf));
  memcpy(codebuf, runtime_data, runtime_data_size);

  /* Setup global variables */
  codeptr = runtime_data_size;
  dataptr = (MAX_STR_LEN + 2) & 0xfffe;
  constptr = 0;

  /* Parse the input file */
  yylexinit(infile);
  yyparse();
  yylexterm();
  if (errors > 0) {
	prnerr1("%d error(s) encountered, aborting", errors);
	exit(EXIT_MGL_COMPERRS);
  }
  if (warnings > 0)
	prnerr1("%d warning(s)", warnings);

  /*
   * Write the debugging info into the constant segment
   */
  debugptr = 0;
  if (debug) {
	debugptr = constptr;
	for (j = 0; j < debugnum; j++) {
		putconst((unsigned int)(debugbuf[j].addr & 0x00ff));
		putconst((unsigned int)((debugbuf[j].addr >> 8) & 0x00ff));
		putconst((unsigned int)(debugbuf[j].lineno & 0x00ff));
		putconst((unsigned int)((debugbuf[j].lineno >> 8) & 0x00ff));
	}
	putconst(0);	/* end marker */
	putconst(0);
  }

  /*
   * Scan through the relocation table and add the value at the relocation
   * to the size of the constant segment
   */
  for (j = 0; j < relocnum; j++) {
	relocval = codebuf[relocbuf[j]] & 0x00ff;
	relocval += ((codebuf[relocbuf[j]+1]) << 8) & 0xff00;
	relocval += constptr;
	codebuf[relocbuf[j]] = (relocval & 0x00ff);
	codebuf[relocbuf[j]+1] = ((relocval >> 8) & 0x00ff);
  }

  /* Write the start address into the code module */
  if (startadr == 0) {
	prnerr0("no start screen defined");
	exit(EXIT_MGL_NOSTART);
  }
  assign(*((__u16 *)&(codebuf[STARTOFS])), htot(startadr & 0xffff));

  /* Align the data segment to a segment boundary and write code size */
  codesize = (codeptr + 0x000f) & 0xfff0;
  assign(*((__u16 *)&(codebuf[CODESIZEOFS])), htot(codesize & 0xffff));
  assign(*((__u16 *)&(codebuf[CONSTSIZEOFS])), htot(constptr & 0xffff));
  assign(*((__u16 *)&(codebuf[DATASIZEOFS])), htot(dataptr & 0xffff));
  assign(*((__u16 *)&(codebuf[DEBUGOFS])), htot(debugptr & 0xffff));

  /* Open temporary output file */
  if ((fname = tempnam(NULL, "mgl")) == NULL) {
	prnerr0("unable to generate name of temporary file");
	exit(EXIT_TEMPNAME);
  }
  if ((outfile = open(fname, O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
								0644)) < 0) {
	prnerr1("unable to open temporary file %s", fname);
	exit(EXIT_OPEN);
  }
  if (verbose < 2)
	unlink(fname);
  free(fname);

  /* Write all buffers into output file */
  (void)nbwrite(codebuf, (unsigned int)codesize, outfile);
  (void)nbwrite(constbuf, (unsigned int)constptr, outfile);
  if (lseek(outfile, 0L, 0) < 0) {
	prnerr0("unable to seek in temporary file");
	exit(EXIT_SEEK);
  }
  return(outfile);
}



syntax highlighted by Code2HTML, v. 0.9.1