/*
* genexpr.c - Code generator for expressions
*
* 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: genexpr.c,v 1.4 2003/01/25 23:29:44 gkminix Exp $
*/
#include "mknbi.h"
#include "mgl.h"
#include "gencode.h"
#include "opcodes.h"
/*
* Define which registers are presently used
*/
int si_inuse = 0; /* SI register is in use */
int di_inuse = 0; /* DI register is in use */
int bx_inuse = 0; /* BX register is in use */
int cx_inuse = 0; /* CX register is in use */
int dx_inuse = 0; /* DX register is in use */
/*
************************************************************************
*
* Handle pushing registers
*
************************************************************************
*/
/*
* Register identifiers for the push/pop routines
*/
#define PUSH_BX 0x0001
#define PUSH_CX 0x0002
#define PUSH_DX 0x0004
#define PUSH_SI 0x0008
#define PUSH_DI 0x0010
/*
* In order to be able to reset the inuse flags, we have to save the
* old values in a linked list.
*/
struct pushinfo {
int old_bx_inuse;
int old_cx_inuse;
int old_dx_inuse;
int old_si_inuse;
int old_di_inuse;
int *inuseaddr;
int *inusesize;
unsigned int regspushed;
struct pushinfo *next;
};
static struct pushinfo *pushstack = NULL; /* The linked list */
/*
* Push registers onto the stack
*/
static void pushregs(regs, dp)
unsigned int regs;
struct meminfo *dp;
{
struct pushinfo *pp;
int *inuseaddr, *inusesize;
/*
* When calling a routine which recursively calls other routines AND
* the result is going into a register-relative location, we have to
* mark that register as used.
*/
inuseaddr = NULL;
if (dp != NULL && (dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
#ifdef PARANOID
if ((dp->addr.r & REG_8BIT_FLAG) == REG_8BIT_FLAG)
interror(89, "cannot use 8-bit-register for relative base");
#endif
if (dp->addr.r == REG_BX)
inuseaddr = &bx_inuse;
else if (dp->addr.r == REG_CX)
inuseaddr = &cx_inuse;
else if (dp->addr.r == REG_DX)
inuseaddr = &dx_inuse;
else if (dp->addr.r == REG_SI)
inuseaddr = &si_inuse;
else if (dp->addr.r == REG_DI)
inuseaddr = &di_inuse;
if (inuseaddr != NULL)
(*inuseaddr)++;
}
inusesize = NULL;
if (dp != NULL && (dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
#ifdef PARANOID
if ((dp->size.r & REG_8BIT_FLAG) == REG_8BIT_FLAG)
interror(90, "cannot use 8-bit-register for size value");
#endif
if (dp->size.r == REG_BX)
inusesize = &bx_inuse;
else if (dp->size.r == REG_CX)
inusesize = &cx_inuse;
else if (dp->size.r == REG_DX)
inusesize = &dx_inuse;
else if (dp->size.r == REG_SI)
inusesize = &si_inuse;
else if (dp->size.r == REG_DI)
inusesize = &di_inuse;
if (inusesize != NULL)
(*inusesize)++;
}
/* Get some memory for new push info */
pp = (struct pushinfo *)nbmalloc(sizeof(struct pushinfo));
if ((regs & PUSH_BX) == PUSH_BX && bx_inuse) {
putpush(REG_BX);
pp->regspushed |= PUSH_BX;
pp->old_bx_inuse = bx_inuse;
bx_inuse = 0;
}
if ((regs & PUSH_CX) == PUSH_CX && cx_inuse) {
putpush(REG_CX);
pp->regspushed |= PUSH_CX;
pp->old_cx_inuse = cx_inuse;
cx_inuse = 0;
}
if ((regs & PUSH_DX) == PUSH_DX && dx_inuse) {
putpush(REG_DX);
pp->regspushed |= PUSH_DX;
pp->old_dx_inuse = dx_inuse;
dx_inuse = 0;
}
if ((regs & PUSH_SI) == PUSH_SI && si_inuse) {
putpush(REG_SI);
pp->regspushed |= PUSH_SI;
pp->old_si_inuse = si_inuse;
si_inuse = 0;
}
if ((regs & PUSH_DI) == PUSH_DI && di_inuse) {
putpush(REG_DI);
pp->regspushed |= PUSH_DI;
pp->old_di_inuse = di_inuse;
di_inuse = 0;
}
pp->inuseaddr = inuseaddr;
pp->inusesize = inusesize;
pp->next = pushstack;
pushstack = pp;
}
/*
* Pop registers from the stack. The order has to be reverse than in the
* push routine!
*/
static void popregs()
{
struct pushinfo *pp;
#ifdef PARANOID
if (pushstack == NULL)
interror(79, "invalid push stack");
#endif
pp = pushstack;
if ((pp->regspushed & PUSH_DI) == PUSH_DI) {
putpop(REG_DI);
di_inuse = pp->old_di_inuse;
}
if ((pp->regspushed & PUSH_SI) == PUSH_SI) {
putpop(REG_SI);
si_inuse = pp->old_si_inuse;
}
if ((pp->regspushed & PUSH_DX) == PUSH_DX) {
putpop(REG_DX);
dx_inuse = pp->old_dx_inuse;
}
if ((pp->regspushed & PUSH_CX) == PUSH_CX) {
putpop(REG_CX);
cx_inuse = pp->old_cx_inuse;
}
if ((pp->regspushed & PUSH_BX) == PUSH_BX) {
putpop(REG_BX);
bx_inuse = pp->old_bx_inuse;
}
if (pp->inuseaddr != NULL)
(*(pp->inuseaddr))--;
if (pp->inusesize != NULL)
(*(pp->inusesize))--;
pushstack = pp->next;
free(pp);
}
/*
************************************************************************
*
* Routines for handling scalar variables
*
************************************************************************
*/
/*
* Save an integer value in a register into the memory location or
* register given by the destination info record.
*/
void putsaveintreg(srcreg, dp)
unsigned int srcreg;
struct meminfo *dp;
{
struct typesdef *tp;
unsigned int src8bit, dest8bit;
/* Just for safety */
if (dp == NULL || (dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
return;
/*
* If we have a scalar subclass, and the subclass limits are other than the
* standard limits (which are cared for by the expression handling routines)
* we have to add some code to check for overflows.
*/
tp = NULL;
if (dp->t != NULL)
switch (dp->t->type) {
case EXPR_BOOL:
tp = &bool_type;
break;
case EXPR_NUM:
tp = &int_type;
break;
case EXPR_CHAR:
tp = &char_type;
break;
case EXPR_ENUM:
/*
* This is special: the max value of 'none_type' is
* zero and therefore always different from the max
* found in the target enum type.
*/
tp = &none_type;
break;
default:
break;
}
if (tp != NULL && dp->t != NULL && isscalar(dp->t) &&
(dp->t->def.s.min != tp->def.s.min || dp->t->def.s.max != tp->def.s.max))
putbound(srcreg, dp->t);
/* Check for 8 bit transfers */
src8bit = (srcreg & REG_8BIT_FLAG);
if (dp->t == NULL)
dest8bit = src8bit;
else {
dest8bit = (dp->t->size > 1 ? 0 : REG_8BIT_FLAG);
if (!src8bit && dest8bit) {
/* Use only lower 8 bits of source register */
srcreg = (srcreg & REG_8BIT_MASK) | REG_LOW_MASK | REG_8BIT_FLAG;
} else if (src8bit && !dest8bit) {
/* Convert 8-bit value into 16 bits */
putregop(OP_MOV, srcreg, REG_AL, 0);
putcode(OP_CBW);
srcreg = REG_AX;
}
}
/* Save register */
switch (dp->memtype & MEM_ADR_MASK) {
case MEM_REGISTER:
putregop(OP_MOV, srcreg, dp->addr.r, 0);
break;
case MEM_ABSOLUTE:
putregop(OP_MOV, srcreg, REG_16DISP | REG_RELOC_FLAG | dest8bit,
dp->addr.i);
break;
case MEM_RELATIVE:
if (dp->addr.r == REG_BP) {
if ((dp->addr.i & 0xff80) == 0 ||
(dp->addr.i & 0xff80) == 0xff80)
putregop(OP_MOV, srcreg,
REG_BP_8DISP | dest8bit, dp->addr.i);
else
putregop(OP_MOV, srcreg,
REG_BP_16DISP | dest8bit, dp->addr.i);
} else if (dp->addr.r == REG_BX) {
if (dp->addr.i == 0)
putregop(OP_MOV, srcreg,
REG_BX_0DISP | dest8bit, 0);
else if ((dp->addr.i & 0xff80) == 0 ||
(dp->addr.i & 0xff80) == 0xff80)
putregop(OP_MOV, srcreg,
REG_BX_8DISP | dest8bit, dp->addr.i);
else
putregop(OP_MOV, srcreg,
REG_BX_16DISP | dest8bit, dp->addr.i);
} else if (regcmp(srcreg, REG_BX)) {
putpush(REG_BP);
putlea(REG_BP, dp->addr.r, dp->addr.i);
putregop(OP_MOV, srcreg, REG_BP_8DISP | dest8bit, 0);
putpop(REG_BP);
} else {
if (bx_inuse)
putpush(REG_BX);
putlea(REG_BX, dp->addr.r, dp->addr.i);
putregop(OP_MOV, srcreg, REG_BX_0DISP | dest8bit, 0);
if (bx_inuse)
putpop(REG_BX);
}
break;
case MEM_NOADDR:
/* Expression doesn't return a value */
break;
}
}
/*
* Save a constant integer value into the memory location or register
* given by the destination info record.
*/
static void putsaveintconst(val, size, dp)
long val;
int size;
struct meminfo *dp;
{
unsigned int modrm = (size > 1 ? 0 : REG_8BIT_FLAG);
/* Just for safety */
if (dp == NULL)
return;
/* Expression doesn't return a value */
if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
return;
/*
* If we have a scalar subclass, we have to check for a correct constant
* value.
*/
if (dp->t != NULL && (val < dp->t->def.s.min || val > dp->t->def.s.max)) {
warning("scalar out of bounds");
val = (val < dp->t->def.s.min ? dp->t->def.s.min : dp->t->def.s.max);
}
/* Now write the code for moving the constant value into the destination */
switch (dp->memtype & MEM_ADR_MASK) {
case MEM_REGISTER:
putimmed(OP_MOV, dp->addr.r, val, 0);
break;
case MEM_ABSOLUTE:
modrm |= REG_16DISP | REG_RELOC_FLAG;
putimmed(OP_MOV, modrm, val, dp->addr.i);
break;
case MEM_RELATIVE:
if (dp->addr.r != REG_BP && dp->addr.r != REG_BX) {
pushregs(PUSH_BX, NULL);
putlea(REG_BX, dp->addr.r, dp->addr.i);
putimmed(OP_MOV, REG_BX_0DISP | modrm, val, 0);
popregs();
} else {
modrm |= REG_MODRM_FLAG;
modrm |= (dp->addr.r == REG_BP ? OP_RM_BP : OP_RM_BX);
modrm |= (((dp->addr.i & 0xff80) == 0 ||
(dp->addr.i & 0xff80) == 0xff80) ?
OP_MOD_8BIT : OP_MOD_16BIT);
putimmed(OP_MOV, modrm, val, dp->addr.i);
}
break;
}
}
/*
* Handle numerical leaf nodes
*/
static void putleafint(ep, dp, size)
struct expr *ep;
struct meminfo *dp;
int size;
{
struct meminfo si;
unsigned int modrm = REG_NONE;
unsigned int tempreg;
/* Check if the expression returns a value */
if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
return;
/* Handle symbols */
if (isvariable(ep)) {
/* Handle a variable */
setmeminfo(ep, &si, REG_NONE, REG_NONE);
if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER)
tempreg = dp->addr.r;
else
tempreg = (size > 1 ? REG_AX : REG_AL);
if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE)
modrm = getmodrm(si.addr.r, si.addr.i);
else if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE)
modrm = REG_16DISP;
if (modrm != REG_NONE) {
if (size < 2)
modrm |= REG_8BIT_FLAG;
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
modrm |= REG_RELOC_FLAG;
putregop(OP_MOV, modrm, tempreg, si.addr.i);
} else {
modrm = (size > 1 ? 0 : REG_8BIT_FLAG);
if (tempreg != REG_BX) {
pushregs(PUSH_BX, NULL);
putlea(REG_BX, si.addr.r, si.addr.i);
putregop(OP_MOV, REG_BX_0DISP | modrm, tempreg, 0);
popregs();
} else {
pushregs(PUSH_SI, NULL);
putlea(REG_SI, si.addr.r, si.addr.i);
putregop(OP_MOV, REG_SI_0DISP | modrm, tempreg, 0);
popregs();
}
}
putsaveintreg(tempreg, dp);
} else if (isconst(ep)) {
/* Handle a constant value */
switch (exprtype(ep)) {
case EXPR_NUM:
putsaveintconst(ep->spec.cval.val.i, size, dp);
break;
case EXPR_CHAR:
putsaveintconst(ep->spec.cval.val.c, size, dp);
break;
case EXPR_BOOL:
putsaveintconst(ep->spec.cval.val.b, size, dp);
break;
case EXPR_ENUM:
putsaveintconst(ep->spec.cval.val.e, size, dp);
break;
#ifdef PARANOID
default:
interror(92, "invalid type in numerical leaf node");
break;
}
#endif
}
#ifdef PARANOID
else
interror(54, "expected leaf node in numerical expression");
#endif
}
/*
************************************************************************
*
* Character expression handling
*
************************************************************************
*/
/*
* Generate a unary character operation. ep points to left subtree.
*/
static void putunarychar(op, ep, dp)
int op;
struct expr *ep;
struct meminfo *dp;
{
struct meminfo di;
/* chr command is special */
if (op == CMD_CHR) {
if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
di = *dp;
di.addr.r &= REG_8BIT_MASK;
di.t = NULL;
putintexpr(ep, &di);
} else {
di.memtype = MEM_REGISTER;
di.addr.r = REG_AX;
di.t = NULL;
putintexpr(ep, &di);
di.addr.r = REG_AL;
putsaveintreg(di.addr.r, dp);
}
return;
}
/* Determine destination for subexpression code */
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di.memtype = MEM_REGISTER;
di.addr.r = REG_AL;
di.t = NULL;
} else {
di = *dp;
di.t = NULL;
}
/* Put character into register and do operation */
putcharexpr(ep, &di);
switch (op) {
case CMD_PRED:
/* OP_DEC_MEM is also for 8 bit registers */
putcode(OP_DEC_MEM);
putcode(OP_MOD_REG | OP_MEM_DEC | rmlow(di.addr.r));
putcode(OP_INTO);
break;
case CMD_SUCC:
/* OP_INC_MEM is also for 8 bit registers */
putcode(OP_INC_MEM);
putcode(OP_MOD_REG | OP_MEM_INC | rmlow(di.addr.r));
putcode(OP_INTO);
break;
#ifdef PARANOID
default:
interror(29, "invalid unary operation for character expression");
#endif
}
/* Finally save the result into the destination */
putsaveintreg(di.addr.r, dp);
}
/*
* Output a character operation opcode into code segment
* This will do an 'op dest,src'. The only supported operation
* is a comparison.
*/
static void putcharop(op, src, dest)
int op;
struct meminfo *src;
struct meminfo *dest;
{
if (op == CMD_CMP)
putregop(OP_CMP, src->addr.r, dest->addr.r, 0);
#ifdef PARANOID
else
interror(95, "invalid character operation");
#endif
}
/*
* Convert a character expression.
*/
void putcharexpr(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
struct meminfo di1, di2;
unsigned int tempreg;
int *inuseptr;
/* Just as a safety measure */
if (ep == NULL)
return;
/* Expression is a function */
if (isfunc(ep)) {
putproc(ep, dp);
return;
}
#ifdef PARANOID
if (isproc(ep))
interror(44, "procedure not allowed in expression");
#endif
/* Expression is a leaf node */
if (isleaf(ep)) {
putleafint(ep, dp, 1);
return;
}
/* Handle unary expression */
if (ep->exprnum != 2) {
#ifdef PARANOID
if (ep->exprnum != 1)
interror(28, "invalid unary expression");
#endif
putunarychar(ep->opcode, ep->left, dp);
return;
}
/* Try to find out which register we can use as temporary storage */
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di1.memtype = MEM_REGISTER;
di1.addr.r = REG_AL;
di1.t = NULL;
tempreg = REG_NONE;
if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
tempreg = dp->addr.r;
} else {
di1 = *dp;
di1.t = NULL;
tempreg = dp->addr.r;
}
inuseptr = NULL;
di2.t = NULL;
di2.memtype = MEM_REGISTER;
if (!bx_inuse && !regcmp(tempreg, REG_BX)) {
inuseptr = &bx_inuse;
di2.addr.r = REG_BL;
} else if (!cx_inuse && !regcmp(tempreg, REG_CX)) {
inuseptr = &cx_inuse;
di2.addr.r = REG_CL;
} else if (!dx_inuse && !regcmp(tempreg, REG_DX)) {
inuseptr = &dx_inuse;
di2.addr.r = REG_DL;
}
/* Handle binary expression */
if (inuseptr != NULL) {
putcharexpr(ep->right, &di2); /* mov di2,expr */
(*inuseptr)++;
putcharexpr(ep->left, &di1); /* mov di1,expr */
(*inuseptr)--;
putcharop(ep->opcode, &di2, &di1); /* op di1,di2 */
} else if (!regcmp(tempreg, REG_BX)) {
/* No temporary register available and tempreg is not BX, use stack */
putpush(REG_BX); /* push bx */
di2.addr.r = REG_BL;
putcharexpr(ep->right, &di2); /* mov bl,expr */
putcharexpr(ep->left, &di1); /* mov di1,expr */
putcharop(ep->opcode, &di2, &di1); /* op di1,bl */
putpop(REG_BX); /* pop bx */
} else {
/* No temporary register available and destreg is BX, use stack */
putpush(REG_CX); /* push cx */
di2.addr.r = REG_CL;
putcharexpr(ep->right, &di2); /* mov cl,expr */
putcharexpr(ep->left, &di1); /* mov di1,expr */
putcharop(ep->opcode, &di2, &di1); /* op di1,cl */
putpop(REG_CX); /* pop cx */
}
putsaveintreg(di1.addr.r, dp);
}
/*
************************************************************************
*
* Boolean expression handling
*
************************************************************************
*/
/*
* Comparison
*/
static void putcmp(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
struct meminfo di;
unsigned int jmpop;
int oldopcode;
#ifdef PARANOID
if (ep->exprnum != 2 || exprtype(ep->left) != exprtype(ep->right))
interror(96, "invalid comparison");
#endif
/* Determine jump code */
switch (ep->opcode) {
default:
case CMD_EQ:
jmpop = JMP_JZ;
break;
case CMD_GT:
jmpop = JMP_JG;
break;
case CMD_GE:
jmpop = JMP_JGE;
break;
case CMD_LT:
jmpop = JMP_JL;
break;
case CMD_LE:
jmpop = JMP_JLE;
break;
case CMD_NE:
jmpop = JMP_JNZ;
break;
}
/*
* Comparisons use a special opcode CMD_CMP, which will not be returned
* by the parser. Additionally we set the no return value address, so
* that no code will be produced which might interfere with the flags.
*/
oldopcode = ep->opcode;
ep->opcode = CMD_CMP;
di.memtype = MEM_NOADDR | MEM_NOSIZE;
di.t = NULL;
switch (exprtype(ep->left)) {
case EXPR_STRING:
putstrexpr(ep, &di, FALSE);
break;
case EXPR_NUM:
case EXPR_ENUM:
putintexpr(ep, &di);
break;
case EXPR_BOOL:
putboolexpr(ep, &di);
break;
case EXPR_CHAR:
putcharexpr(ep, &di);
break;
#ifdef PARANOID
default:
interror(61, "invalid type in comparison");
break;
#endif
}
ep->opcode = oldopcode;
/* Now check the resulting flags */
if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER)
putsetbit(dp->addr.r, jmpop);
else if ((dp->memtype & MEM_ADR_MASK) != MEM_NOADDR) {
putsetbit(REG_AL, jmpop);
putsaveintreg(REG_AL, dp);
}
}
/*
* Generate a unary boolean operation. ep points to left subtree.
*/
static void putunarybool(op, ep, dp)
int op;
struct expr *ep;
struct meminfo *dp;
{
struct meminfo di;
/* Determine destination for subexpression code */
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di.memtype = MEM_REGISTER;
di.addr.r = REG_AL;
di.t = NULL;
} else {
di = *dp;
di.t = NULL;
}
putcharexpr(ep, &di);
switch (op) {
case CMD_NOT:
putcode(OP_NONIM);
putcode(OP_NONIM_NOT | OP_MOD_REG | rmlow(di.addr.r));
putimmed(OP_IMMED_AND, di.addr.r, 0x01, 0);
break;
case CMD_PRED:
/* This should check for overrun */
putcode(OP_DEC_MEM);
putcode(OP_MOD_REG | OP_MEM_DEC | rmlow(di.addr.r));
putimmed(OP_IMMED_AND, di.addr.r, 0x01, 0);
break;
case CMD_SUCC:
/* This should check for overrun */
putcode(OP_INC_MEM);
putcode(OP_MOD_REG | OP_MEM_INC | rmlow(di.addr.r));
putimmed(OP_IMMED_AND, di.addr.r, 0x01, 0);
break;
#ifdef PARANOID
default:
interror(23, "invalid unary operation for character expression");
#endif
}
/* Finally save the result into the destination */
putsaveintreg(di.addr.r, dp);
}
/*
* Output a boolean operation opcode into code segment
* This will do an 'op dest,src'
*/
static void putboolop(op, src, dest)
int op;
struct meminfo *src;
struct meminfo *dest;
{
unsigned int srcreg, destreg;
srcreg = src->addr.r;
destreg = dest->addr.r;
switch (op) {
case CMD_OR:
/* or dest,src */
putregop(OP_OR, srcreg, destreg, 0);
break;
case CMD_XOR:
/* xor dest,src */
putregop(OP_XOR, srcreg, destreg, 0);
break;
case CMD_AND:
/* and dest,src */
putregop(OP_AND, srcreg, destreg, 0);
break;
case CMD_CMP:
/* cmp dest,src */
putregop(OP_CMP, srcreg, destreg, 0);
break;
#ifdef PARANOID
default:
interror(94, "invalid boolean operation");
break;
#endif
}
}
/*
* Convert a boolean expression.
*/
void putboolexpr(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
struct meminfo di1, di2;
unsigned int tempreg;
int *inuseptr;
/* Just as a safety measure */
if (ep == NULL)
return;
/* Expression is a function */
if (isfunc(ep)) {
putproc(ep, dp);
return;
}
#ifdef PARANOID
if (isproc(ep))
interror(41, "procedure not allowed in expression");
#endif
/* Expression is a leaf node */
if (isleaf(ep)) {
putleafint(ep, dp, 1);
return;
}
/* Handle unary expression */
if (ep->exprnum != 2) {
#ifdef PARANOID
if (ep->exprnum != 1 || exprtype(ep->left) != EXPR_BOOL)
interror(93, "invalid unary expression");
#endif
putunarybool(ep->opcode, ep->left, dp);
return;
}
/* Comparisons are special because they involve different expression types */
if (iscmdcond(ep)) {
putcmp(ep, dp);
return;
}
/*
* In contrast to numerical expressions we are not handling any special
* cases here, because boolean operations also affect flags even when
* not returning any value.
*/
/* Try to find out which register we can use as temporary storage */
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di1.memtype = MEM_REGISTER;
di1.addr.r = REG_AL;
di1.t = NULL;
tempreg = REG_NONE;
if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
tempreg = dp->addr.r;
} else {
di1 = *dp;
di1.t = NULL;
tempreg = dp->addr.r;
}
inuseptr = NULL;
di2.t = NULL;
di2.memtype = MEM_REGISTER;
if (!bx_inuse && !regcmp(tempreg, REG_BX)) {
inuseptr = &bx_inuse;
di2.addr.r = REG_BL;
} else if (!cx_inuse && !regcmp(tempreg, REG_CX)) {
inuseptr = &cx_inuse;
di2.addr.r = REG_CL;
} else if (!dx_inuse && !regcmp(tempreg, REG_DX)) {
inuseptr = &dx_inuse;
di2.addr.r = REG_DL;
}
/* Handle binary expression */
if (inuseptr != NULL) {
putboolexpr(ep->right, &di2); /* mov di2,expr */
(*inuseptr)++;
putboolexpr(ep->left, &di1); /* mov di1,expr */
(*inuseptr)--;
putboolop(ep->opcode, &di2, &di1); /* op di1,di2 */
} else if (!regcmp(tempreg, REG_BX)) {
/* No temporary register available and tempreg is not BX, use stack */
putpush(REG_BX); /* push bx */
di2.addr.r = REG_BL;
putboolexpr(ep->right, &di2); /* mov bl,expr */
putboolexpr(ep->left, &di1); /* mov di1,expr */
putboolop(ep->opcode, &di2, &di1); /* op di1,bl */
putpop(REG_BX); /* pop bx */
} else {
/* No temporary register available and destreg is BX, use stack */
putpush(REG_CX); /* push cx */
di2.addr.r = REG_CL;
putboolexpr(ep->right, &di2); /* mov cl,expr */
putboolexpr(ep->left, &di1); /* mov di1,expr */
putboolop(ep->opcode, &di2, &di1); /* op di1,cl */
putpop(REG_CX); /* pop cx */
}
putsaveintreg(di1.addr.r, dp);
}
/*
************************************************************************
*
* Numerical expression handling
*
************************************************************************
*/
/*
* Generate a unary integer operation. ep points to left subtree.
*/
static void putunaryint(op, ep, dp)
int op;
struct expr *ep;
struct meminfo *dp;
{
struct meminfo di;
/* Determine destination for subexpression code */
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di.memtype = MEM_REGISTER;
di.addr.r = REG_AX;
di.t = NULL;
} else {
di = *dp;
di.t = NULL;
}
/* For CMD_ORD we have to deal with different types in the subtree */
if (op == CMD_ORD) {
switch (exprtype(ep)) {
case EXPR_NUM:
case EXPR_ENUM:
/* Use full 16 bit register */
putintexpr(ep, &di);
break;
case EXPR_CHAR:
di.addr.r = REG_AL;
putcharexpr(ep, &di);
putcode(OP_CBW);
di.addr.r = REG_AX;
break;
case EXPR_BOOL:
di.addr.r = REG_AL;
putboolexpr(ep, &di);
putcode(OP_CBW);
di.addr.r = REG_AX;
break;
#ifdef PARANOID
default:
interror(99, "invalid subexpression type for ord command");
break;
#endif
}
} else {
#ifdef PARANOID
if (exprtype(ep) != EXPR_NUM &&
(exprtype(ep) != EXPR_ENUM ||
(op != CMD_PRED && op != CMD_SUCC)))
interror(51, "invalid subexpression type for unary command");
#endif
putintexpr(ep, &di);
switch (op) {
case '-':
putcode(OP_NONIM | OP_WORD_MASK);
putcode(OP_NONIM_NEG | OP_MOD_REG | rmlow(di.addr.r));
break;
case CMD_NOT:
putcode(OP_NONIM | OP_WORD_MASK);
putcode(OP_NONIM_NOT | OP_MOD_REG | rmlow(di.addr.r));
break;
case CMD_ODD:
putimmed(OP_IMMED_AND, di.addr.r, 0x0001, 0);
break;
case CMD_PRED:
putcode(OP_DEC_REG16 | rmlow(di.addr.r));
putcode(OP_INTO);
break;
case CMD_SUCC:
putcode(OP_INC_REG16 | rmlow(di.addr.r));
putcode(OP_INTO);
break;
case CMD_ABS:
putregop(OP_OR, di.addr.r, di.addr.r, 0);
putjmp(codeptr + 4, JMP_JNS);
putcode(OP_NONIM | OP_WORD_MASK);
putcode(OP_NONIM_NEG | OP_MOD_REG | rmlow(di.addr.r));
break;
case CMD_SQR:
if (dx_inuse && !regcmp(di.addr.r, REG_DX))
putpush(REG_DX);
putregop(OP_MOV, di.addr.r, REG_AX, 0);
putcode(OP_NONIM | OP_WORD_MASK);
putcode(OP_NONIM_IMUL | OP_MOD_REG | rmlow(di.addr.r));
putregop(OP_MOV, REG_AX, di.addr.r, 0);
if (dx_inuse && !regcmp(di.addr.r, REG_DX))
putpop(REG_DX);
putcode(OP_INTO);
break;
#ifdef PARANOID
default:
interror(52, "invalid unary operation for numerical expression");
#endif
}
}
/* Finally save the result into the destination */
putsaveintreg(di.addr.r, dp);
}
/*
* Output an integer operation opcode into code segment
* This will do an 'op dest,src'
*/
static void putintop(op, src, dest)
int op;
struct meminfo *src;
struct meminfo *dest;
{
unsigned int srcreg, destreg;
srcreg = src->addr.r;
destreg = dest->addr.r;
switch (op) {
case '+':
/* add dest,src */
putregop(OP_ADD, srcreg, destreg, 0);
putcode(OP_INTO);
break;
case '-':
/* sub dest,src */
putregop(OP_SUB, srcreg, destreg, 0);
putcode(OP_INTO);
break;
case '*':
/* imul src */
if (dx_inuse && !regcmp(destreg, REG_DX))
putpush(REG_DX); /* push dx */
putregop(OP_MOV, destreg, REG_AX, 0); /* mov ax,dest */
putcode(OP_NONIM | OP_WORD_MASK);
putcode(OP_NONIM_IMUL | OP_MOD_REG | rmlow(srcreg));
putregop(OP_MOV, REG_AX, destreg, 0); /* mov dest,ax */
if (dx_inuse && !regcmp(destreg, REG_DX))
putpop(REG_DX); /* pop dx */
putcode(OP_INTO);
break;
case '/':
/* idiv src */
if (regcmp(srcreg, REG_DX)) {
if (cx_inuse && !regcmp(destreg, REG_CX))
putpush(REG_CX); /* push cx */
putregop(OP_MOV, srcreg, REG_CX, 0); /* mov cx,src */
}
if (dx_inuse && !regcmp(destreg, REG_DX))
putpush(REG_DX); /* push dx */
putregop(OP_MOV, destreg, REG_AX, 0); /* mov ax,dest */
putcode(OP_CWD); /* cwd */
putcode(OP_NONIM | OP_WORD_MASK);
if (regcmp(srcreg, REG_DX))
putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(REG_CX));
else
putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(srcreg));
putregop(OP_MOV, REG_AX, destreg, 0); /* mov dest,ax */
if (dx_inuse && !regcmp(destreg, REG_DX))
putpop(REG_DX); /* pop dx */
if (regcmp(srcreg, REG_DX) &&
cx_inuse && !regcmp(destreg, REG_CX))
putpop(REG_CX); /* pop cx */
break;
case '%':
/* imod src */
if (regcmp(srcreg, REG_DX)) {
if (cx_inuse && !regcmp(destreg, REG_CX))
putpush(REG_CX); /* push cx */
putregop(OP_MOV, srcreg, REG_CX, 0); /* mov cx,src */
}
if (dx_inuse && !regcmp(destreg, REG_DX))
putpush(REG_DX); /* push dx */
putregop(OP_MOV, destreg, REG_AX, 0); /* mov ax,dest */
putcode(OP_CWD); /* cwd */
putcode(OP_NONIM | OP_WORD_MASK);
if (regcmp(srcreg, REG_DX))
putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(REG_CX));
else
putcode(OP_NONIM_IDIV | OP_MOD_REG | rmlow(srcreg));
putregop(OP_MOV, REG_DX, destreg, 0); /* mov dest,dx */
if (dx_inuse && !regcmp(destreg, REG_DX))
putpop(REG_DX); /* pop dx */
if (regcmp(srcreg, REG_DX) &&
cx_inuse && !regcmp(destreg, REG_CX))
putpop(REG_CX); /* pop cx */
break;
case CMD_OR:
/* or dest,src */
putregop(OP_OR, srcreg, destreg, 0);
break;
case CMD_XOR:
/* xor dest,src */
putregop(OP_XOR, srcreg, destreg, 0);
break;
case CMD_AND:
/* and dest,src */
putregop(OP_AND, srcreg, destreg, 0);
break;
case CMD_CMP:
/* cmp dest,src */
putregop(OP_CMP, srcreg, destreg, 0);
break;
#ifdef PARANOID
default:
interror(88, "invalid numerical operation");
break;
#endif
}
}
/*
* Convert a numeric expression. This also handles enumeration expressions.
*/
void putintexpr(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
struct meminfo di1, di2;
struct expr *tmpexpr;
unsigned int tempreg;
int *inuseptr;
/* Just as a safety measure */
if (ep == NULL)
return;
/* Expression is a function */
if (isfunc(ep)) {
putproc(ep, dp);
return;
}
#ifdef PARANOID
if (isproc(ep))
interror(42, "procedure not allowed in expression");
#endif
/* Expression is a leaf node */
if (isleaf(ep)) {
putleafint(ep, dp, 2);
return;
}
/* Handle unary expression */
if (ep->exprnum != 2) {
#ifdef PARANOID
if (ep->exprnum != 1)
interror(56, "invalid unary expression");
#endif
putunaryint(ep->opcode, ep->left, dp);
return;
}
/*
* Handle some special expression cases, where the result can be
* forecast:
* 1.) 0 / num --> 0
* 3.) num * 0 --> 0
* 2.) num + 0 --> num
* 4.) num + 1 --> inc(num)
* 5.) num - 1 --> dec(num)
* 6.) num * 1 --> num
* 7.) num / 1 --> num
* 10.) num & 0 --> 0
* 11.) num | 0 --> num
* 12.) num / 0 --> error
* 13.) num % 0 --> error
* 14.) num % 2 --> and(num,1)
* 15.) num % 1 --> 0
*/
if (exprtype(ep) == EXPR_NUM && (isconst(ep->left) || isconst(ep->right))) {
#ifdef PARANOID
if (isconst(ep->left) && isconst(ep->right))
interror(67, "expression tree not collapsed properly");
#endif
/* Case (1): 0 / num --> 0 */
if (ep->opcode == '/' && isconst(ep->left) &&
ep->left->spec.cval.val.i == 0) {
putsaveintconst(0, 2, dp);
return;
}
/* Case (2): num * 0 --> 0 */
if (ep->opcode == '*' &&
((isconst(ep->left) && ep->left->spec.cval.val.i == 0) ||
(isconst(ep->right) && ep->right->spec.cval.val.i == 0))) {
putsaveintconst(0, 2, dp);
return;
}
/* Case (3): num + 0 --> num */
if (ep->opcode == '+' &&
((isconst(ep->left) && ep->left->spec.cval.val.i == 0) ||
(isconst(ep->right) && ep->right->spec.cval.val.i == 0))) {
if (!isconst(ep->left))
putintexpr(ep->left, dp); /* mov dest,expr */
else
putintexpr(ep->right, dp); /* mov dest,expr */
return;
}
/* Case (4): num + 1 --> inc(num) */
if (ep->opcode == '+' &&
((isconst(ep->left) && ep->left->spec.cval.val.i == 1) ||
(isconst(ep->right) && ep->right->spec.cval.val.i == 1))) {
tmpexpr = (isconst(ep->left) ? ep->right : ep->left);
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di1.memtype = MEM_REGISTER;
di1.addr.r = REG_AX;
di1.t = NULL;
putintexpr(tmpexpr, &di1);
putcode(OP_INC_REG16 | rmlow(REG_AX));
putsaveintreg(REG_AX, dp);
} else {
putintexpr(tmpexpr, dp);
putcode(OP_INC_REG16 | rmlow(dp->addr.r));
}
return;
}
/* Case (5): num - 1 --> dec(num) */
if (ep->opcode == '-' &&
(isconst(ep->right) && ep->right->spec.cval.val.i == 1)) {
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di1.memtype = MEM_REGISTER;
di1.addr.r = REG_AX;
di1.t = NULL;
putintexpr(ep->left, &di1);
putcode(OP_DEC_REG16 | rmlow(REG_AX));
putsaveintreg(REG_AX, dp);
} else {
putintexpr(ep->left, dp);
putcode(OP_DEC_REG16 | rmlow(dp->addr.r));
}
return;
}
/* Case (6): num * 1 --> num */
if (ep->opcode == '*' &&
((isconst(ep->left) && ep->left->spec.cval.val.i == 1) ||
(isconst(ep->right) && ep->right->spec.cval.val.i == 1))) {
if (!isconst(ep->left))
putintexpr(ep->left, dp); /* mov dest,expr */
else
putintexpr(ep->right, dp); /* mov dest,expr */
return;
}
/* Case (7): num / 1 --> num */
if (ep->opcode == '/' &&
(isconst(ep->right) && ep->right->spec.cval.val.i == 1)) {
putintexpr(ep->left, dp); /* mov dest,expr */
return;
}
/* Case (10): num & 0 --> 0 */
if (ep->opcode == CMD_AND &&
((isconst(ep->left) && ep->left->spec.cval.val.i == 0) ||
(isconst(ep->right) && ep->right->spec.cval.val.i == 0))) {
putsaveintconst(0, 2, dp);
return;
}
/* Case (11): num | 0 --> num */
if (ep->opcode == CMD_OR &&
((isconst(ep->left) && ep->left->spec.cval.val.i == 0) ||
(isconst(ep->right) && ep->right->spec.cval.val.i == 0))) {
if (!isconst(ep->left))
putintexpr(ep->left, dp); /* mov dest,expr */
else
putintexpr(ep->right, dp); /* mov dest,expr */
return;
}
/* Case (12) and case (13): num /|% 0 --> error */
if ((ep->opcode == '/' || ep->opcode == '%') &&
(isconst(ep->right) && ep->right->spec.cval.val.i == 0)) {
error("division by zero");
return;
}
/* Case (14): num % 2 --> and(num,1) */
if (ep->opcode == '/' &&
(isconst(ep->right) && ep->right->spec.cval.val.i == 2)) {
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di1.memtype = MEM_REGISTER;
di1.addr.r = REG_AX;
di1.t = NULL;
putintexpr(ep->left, &di1);
putimmed(OP_IMMED_AND, REG_AX, 0x0001, 0);
putsaveintreg(REG_AX, dp);
} else {
putintexpr(ep->left, dp);
putcode(OP_SHIFT_1 | OP_WORD_MASK);
putimmed(OP_IMMED_AND, dp->addr.r, 0x0001, 0);
}
return;
}
/* Case (15): num % 1 --> 0 */
if (ep->opcode == '%' &&
(isconst(ep->right) && ep->right->spec.cval.val.i == 1)) {
putsaveintconst(0, 2, dp);
return;
}
}
/*
* Try to find out which register we can use as temporary storage. First
* use the general purpose registers, then the string pointer registers,
* and finally DX. DX has to come last because it is used with multipli-
* cation and division and with every such operation has to be saved on
* the stack if it's used.
*/
if ((dp->memtype & MEM_ADR_MASK) != MEM_REGISTER) {
di1.memtype = MEM_REGISTER;
di1.addr.r = REG_AX;
di1.t = NULL;
tempreg = REG_NONE;
if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
tempreg = dp->addr.r;
} else {
di1 = *dp;
di1.t = NULL;
tempreg = dp->addr.r;
}
inuseptr = NULL;
di2.t = NULL;
di2.memtype = MEM_REGISTER;
if (!bx_inuse && tempreg != REG_BX) {
inuseptr = &bx_inuse;
di2.addr.r = REG_BX;
} else if (!cx_inuse && tempreg != REG_CX) {
inuseptr = &cx_inuse;
di2.addr.r = REG_CX;
} else if (!si_inuse && tempreg != REG_SI) {
inuseptr = &si_inuse;
di2.addr.r = REG_SI;
} else if (!di_inuse && tempreg != REG_DI) {
inuseptr = &di_inuse;
di2.addr.r = REG_DI;
} else if (!dx_inuse && tempreg != REG_DX) {
inuseptr = &dx_inuse;
di2.addr.r = REG_DX;
}
/* Handle binary expression */
if (inuseptr != NULL) {
putintexpr(ep->right, &di2); /* mov di2,expr */
(*inuseptr)++;
putintexpr(ep->left, &di1); /* mov di1,expr */
(*inuseptr)--;
putintop(ep->opcode, &di2, &di1); /* op di1,di2 */
} else if (tempreg != REG_BX) {
/* No temporary register available and tempreg is not BX, use stack */
putpush(REG_BX); /* push bx */
di2.addr.r = REG_BX;
putintexpr(ep->right, &di2); /* mov bx,expr */
putintexpr(ep->left, &di1); /* mov di1,expr */
putintop(ep->opcode, &di2, &di1); /* op di1,bx */
putpop(REG_BX); /* pop bx */
} else {
/* No temporary register available and destreg is BX, use stack */
putpush(REG_CX); /* push cx */
di2.addr.r = REG_CX;
putintexpr(ep->right, &di2); /* mov cx,expr */
putintexpr(ep->left, &di1); /* mov di1,expr */
putintop(ep->opcode, &di2, &di1); /* op di1,cx */
putpop(REG_CX); /* pop cx */
}
putsaveintreg(di1.addr.r, dp);
}
/*
************************************************************************
*
* String expression handling
*
************************************************************************
*/
/*
* Handle leaf string nodes. Determining where the string is coming from
* is a bit easier than with scalar data types because if it has been
* passed to a function, we always have a pointer to the variable on the
* stack, regardless if it's passed by value or reference (this distinction
* has to be made by the caller by providing a temporary variable space if
* the variable has to be passed by value).
* If the 'rec' flag has been set, we were called from a recursively called
* 'putstrexpr' routine, and have to return the end of the destination string
* in AX. In that case we will never be called with MEM_REGISTER.
*/
static void putleafstr(ep, dp, rec)
struct expr *ep;
struct meminfo *dp;
int rec;
{
struct meminfo si;
unsigned int modrm;
/* Expression doesn't return a value */
if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
return;
/* Just return the adress of the string in the given register */
if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
#ifdef PARANOID
if (rec)
interror(55, "invalid call to 'putleafstr'");
#endif
if (isvariable(ep)) {
setmeminfo(ep, &si, dp->addr.r, REG_NONE);
} else if (isconst(ep)) {
#ifdef PARANOID
if (ep->spec.cval.val.s == NULL)
interror(68, "no string defined in string leaf node");
#endif
si.memtype = MEM_ABSOLUTE;
si.size.i = strlen(ep->spec.cval.val.s);
si.addr.i = putstring(ep->spec.cval.val.s);
}
if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
/* String is constant or static */
putcode(OP_MOV_WREGIM | rmlow(dp->addr.r));
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
setreloc();
putint(si.addr.i);
} else if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
#ifdef PARANOID
if (dp->addr.r == REG_BP)
interror(117, "invalid destination register for string");
#endif
modrm = 0;
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
modrm = REG_RELOC_FLAG;
putlea(dp->addr.r, si.addr.r | modrm, si.addr.i);
}
return;
}
/*
* We have to copy the string into the destination. First determine the
* address of the source and put it into SI.
*/
pushregs(PUSH_SI | PUSH_DI | PUSH_CX, dp);
if (isvariable(ep)) {
setmeminfo(ep, &si, REG_SI, REG_NONE);
} else if (isconst(ep)) {
#ifdef PARANOID
if (ep->spec.cval.val.s == NULL)
interror(74, "no string defined in string leaf node");
#endif
si.memtype = MEM_ABSOLUTE | MEM_IMMEDIATE;
si.size.i = strlen(ep->spec.cval.val.s);
si.addr.i = putstring(ep->spec.cval.val.s);
}
if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
/* String is constant or static */
putcode(OP_MOV_WREGIM | rmlow(REG_SI));
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
setreloc();
putint(si.addr.i);
} else if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
modrm = 0;
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
modrm = REG_RELOC_FLAG;
putlea(REG_SI, si.addr.r | modrm, si.addr.i);
}
/* Determine the address of the destination and put it into DI */
if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
putcode(OP_MOV_WREGIM | rmlow(REG_DI)); /* mov di,#dest */
if ((dp->memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
setreloc();
putint(dp->addr.i);
} else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
putlea(REG_DI, dp->addr.r, dp->addr.i); /* lea di,dest */
#ifdef PARANOID
else
interror(82, "invalid destination type");
#endif
/* Determine the size of the destination buffer and put it into CX */
if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) {
putimmed(OP_MOV, REG_CX, dp->size.i, 0); /* mov cx,size */
} else if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
putregop(OP_MOV, dp->size.r, REG_CX, 0); /* mov cx,sizereg */
#ifdef PARANOID
else
interror(76, "invalid size type");
#endif
/* Now actually generate the copy code */
putcode(OP_JCXZ); /* jcxz $+9 */
putcode(8);
putcode(OP_LODSB); /* lodsb */
putregop(OP_OR, REG_AL, REG_AL, 0); /* or al,al */
putjmp(codeptr + 5, JMP_JZ); /* jz $+5 */
putcode(OP_STOSB); /* stosb */
putcode(OP_LOOP); /* loop $-6 */
putcode((unsigned int)(-8 & 0xff));
putimmed(OP_MOV, REG_DI_0DISP | REG_8BIT_FLAG, 0, 0); /* mov [di],0 */
if (rec)
putregop(OP_MOV, REG_DI, REG_AX, 0); /* mov ax,di */
popregs();
}
/*
* Put string comparison into code segment
*/
static void putstrcmp(ep)
struct expr *ep;
{
struct meminfo di;
unsigned int tmpreg;
addr_t stack;
addr_t tempstrings[2];
int i;
#ifdef PARANOID
if (ep->exprnum != 2 ||
exprtype(ep->left) != EXPR_STRING ||
exprtype(ep->right) != EXPR_STRING)
interror(91, "invalid string comparison");
#endif
/* We need the SI and DI registers */
pushregs(PUSH_SI | PUSH_DI, NULL);
/* Decrement the stack pointer to get enough space for argument strings */
stack = 0;
for (i = 0; i < 2; i++) {
#ifdef PARANOID
if (isproc(ep->exprlist[i]))
interror(53, "procedure not allowed in string comparison");
#endif
tempstrings[i] = -1;
if (!isconst(ep->exprlist[i]) && !isvariable(ep->exprlist[i])) {
/* String expression is not a constant or variable */
tempstrings[i] = stack;
stack += MAX_STR_LEN + 1;
}
}
if (stack > 0) {
pushregs(PUSH_DX, NULL); /* push dx */
putregop(OP_MOV, REG_SP, REG_DX, 0); /* mov dx,sp */
putimmed(OP_IMMED_SUB, REG_SP, stack, 0); /* sub sp,ofs */
dx_inuse++;
}
/* Now scan through all expressions and push the values onto the stack */
for (i = 0; i < 2; i++) {
tmpreg = (i == 0 ? REG_SI : REG_DI);
if (tempstrings[i] < 0) {
/* String is a variable or is constant */
di.memtype = MEM_REGISTER;
di.addr.r = tmpreg;
di.t = NULL;
putleafstr(ep->exprlist[i], &di, FALSE);
} else {
/* String is a subexpression, so copy it into temp space */
putregop(OP_MOV, REG_SP, tmpreg, 0);
putlea(tmpreg, tmpreg, tempstrings[i]);
di.memtype = MEM_RELATIVE | MEM_IMMEDIATE;
di.addr.i = 0;
di.addr.r = tmpreg;
di.size.i = MAX_STR_LEN;
di.t = NULL;
putstrexpr(ep->exprlist[i], &di, FALSE);
}
if (i == 0)
si_inuse++;
else
di_inuse++;
}
/* Actually call the string comparison function, and cleanup stack */
putcode(OP_LODSB); /* lodsb */
putcode(OP_SCASB); /* scasb */
putjmp(codeptr + 6, JMP_JNZ); /* jne $+6 */
putregop(OP_OR, REG_AL, REG_AL, 0); /* or al,al */
putjmp(codeptr - 6, JMP_JNZ); /* jnz $-6 */
if (stack > 0) {
dx_inuse--;
putregop(OP_MOV, REG_DX, REG_SP, 0); /* mov sp,dx */
popregs(); /* pop dx */
}
si_inuse--;
di_inuse--;
popregs(); /* pop regs */
}
/*
* Convert a string expression. When the 'rec' parameter is TRUE, we
* are called recursively, and have to return a pointer to the end of
* the result string in AX. This does not apply for a string comparison,
* since the result is always boolean.
*/
void putstrexpr(ep, dp, rec)
struct expr *ep;
struct meminfo *dp;
int rec;
{
struct meminfo di, tmpdi;
addr_t tmpcode, jmpadr = 0;
/* Just a safety measure */
if (ep == NULL)
return;
/* Handle string comparison */
if (ep->opcode == CMD_CMP) {
putstrcmp(ep);
return;
}
/*
* Expression is a function. A function always returns the destination
* pointer in AX. If _we_ have to return the result's end adress, we
* have to search for the end marker.
*/
if (isfunc(ep)) {
putproc(ep, dp);
if (rec) {
pushregs(PUSH_DI | PUSH_CX, dp);
putregop(OP_MOV, REG_AX, REG_DI, 0); /* mov di,ax */
putregop(OP_XOR, REG_AL, REG_AL, 0); /* xor al,al */
if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) {
putimmed(OP_MOV, REG_CX, dp->size.i, 0);
} else if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
putregop(OP_MOV, dp->size.r, REG_CX, 0);
}
putcode(OP_REPNE);
putcode(OP_SCASB); /* repne scasb */
putcode(OP_DEC_REG16 | rmlow(REG_DI)); /* dec di */
putregop(OP_MOV, REG_DI, REG_AX, 0); /* mov ax,di */
popregs();
}
return;
}
#ifdef PARANOID
if (isproc(ep))
interror(45, "procedure not allowed in expression");
#endif
/* Expression is a leaf node */
if (isleaf(ep)) {
putleafstr(ep, dp, rec);
return;
}
/* There are no unary string operations */
#ifdef PARANOID
if (ep->exprnum != 2)
interror(78, "no unary operation for strings");
else if (ep->opcode != '+' && ep->opcode != '*')
interror(80, "invalid binary operation for strings");
#endif
/* Expression doesn't return a value */
if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
return;
/*
* If the target string is not long enough, there is no need to continue.
* We only have to check for a constant length here. Variable length will
* be handled by the string copy routines.
*/
#ifdef PARANOID
if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE && dp->size.i <= 0)
interror(87, "invalid string length");
#endif
/*
* If the result has to go into a register, we use our "register"
* string buffer. Otherwise we can directly copy all strings into
* the destination.
*/
di = *dp;
di.t = NULL;
if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
di.memtype = MEM_ABSOLUTE | MEM_IMMEDIATE;
di.addr.i = 0;
di.size.i = MAX_STR_LEN;
}
/*
* Put the address of the destination string space into DI. All further
* string expression handling will then be done relative to DI.
*/
pushregs(PUSH_DI, dp); /* push di and rel reg */
if ((di.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
putcode(OP_MOV_WREGIM | rmlow(REG_DI)); /* mov di,#dest */
setreloc();
putint(di.addr.i);
} else if ((di.memtype & MEM_ADR_MASK) == MEM_RELATIVE)
putlea(REG_DI, di.addr.r, di.addr.i); /* lea di,dest */
#ifdef PARANOID
else
interror(83, "invalid destination type");
#endif
di.memtype = (di.memtype & ~MEM_ADR_MASK) | MEM_RELATIVE;
di.addr.i = 0;
di.addr.r = REG_DI;
/*
* A multiplication is special: it means to copy the character in the
* left subexpression into the destination string by the number given
* by the right subexpression.
*/
if (exprtype(ep->left) == EXPR_CHAR &&
exprtype(ep->right) == EXPR_NUM &&
ep->opcode == '*') {
/* First get the number of characters to fill in */
tmpdi.memtype = MEM_REGISTER | MEM_NOSIZE;
tmpdi.addr.r = REG_AX;
tmpdi.t = NULL;
putintexpr(ep->right, &tmpdi);
/* Compare the destination size with the number of characters to copy */
if ((di.memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE)
putimmed(OP_CMP, REG_AX, di.size.i, 0); /* cmp ax,size */
else if ((di.memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
putregop(OP_CMP, di.size.r, REG_AX, 0);
#ifdef PARANOID
else
interror(116, "invalid size type");
#endif
/* Use the smaller of the two values */
jmpadr = codeptr;
codeptr += 2;
if ((di.memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) {
putimmed(OP_MOV, REG_AX, di.size.i, 0); /* mov ax,size */
} else if ((di.memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
putregop(OP_MOV, di.size.r, REG_AX, 0);
}
tmpcode = codeptr;
codeptr = jmpadr;
putjmp(tmpcode, JMP_JC);
codeptr = tmpcode;
/* Move the copy size into CX */
pushregs(PUSH_CX, dp);
putregop(OP_MOV, REG_AX, REG_CX, 0);
/* Get the character to copy into AL */
cx_inuse++;
tmpdi.memtype = MEM_REGISTER | MEM_NOSIZE;
tmpdi.addr.r = REG_AL;
tmpdi.t = NULL;
putcharexpr(ep->left, &tmpdi);
cx_inuse--;
/* Now actually do the fill */
putcode(OP_REP);
putcode(OP_STOSB);
putimmed(OP_MOV, REG_DI_0DISP | REG_8BIT_FLAG, 0, 0);
/* Return a pointer to the last character in AX and restore stack */
if (rec)
putregop(OP_MOV, REG_DI, REG_AX, 0);
goto endstrexpr;
}
/*
* Handle left expression. We call this routine recursively, so that it
* will return a pointer to the end of the destination string in AX. This
* does not apply if the left subexpression is a character.
*/
if (exprtype(ep->left) == EXPR_STRING) {
/* String subexpression */
putstrexpr(ep->left, &di, TRUE);
} else if (exprtype(ep->left) == EXPR_CHAR) {
/* Character subexpression */
if ((di.memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
putregop(OP_OR, di.size.r, di.size.r, 0); /* or size,size */
putjmp(codeptr + 5, JMP_JNZ); /* jnz $+3 */
jmpadr = codeptr;
codeptr += 3; /* provide space for jmp near */
}
putcharexpr(ep->left, &di);
}
#ifdef PARANOID
else
interror(85, "invalid subexpression for string expression");
#endif
/*
* Put the size of the destination string space into CX. All further
* string expression handling will then be done using CX.
*/
pushregs(PUSH_CX, dp); /* push cx and rel reg */
if ((di.memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE)
putimmed(OP_MOV, REG_CX, di.size.i, 0); /* mov cx,size */
else if ((di.memtype & MEM_SIZE_MASK) == MEM_SIZEREG)
putregop(OP_MOV, di.size.r, REG_CX, 0);
#ifdef PARANOID
else
interror(86, "invalid size type");
#endif
di.memtype = (di.memtype & ~MEM_SIZE_MASK) | MEM_SIZEREG;
di.size.r = REG_CX;
/*
* Now we have to find out the end of the current string so that we
* can append the right side. Also, the size of the remaining string
* buffer has to be put into CX.
*/
if (exprtype(ep->left) == EXPR_STRING) {
/* String subexpression */
putregop(OP_XCHG, REG_AX, REG_DI, 0); /* xchg di,ax */
putregop(OP_SUB, REG_DI, REG_AX, 0); /* sub ax,di */
putregop(OP_ADD, REG_AX, REG_CX, 0); /* add cx,ax */
} else {
/* Character subexpression */
putcode(OP_INC_REG16 | rmlow(REG_DI)); /* inc di */
putcode(OP_DEC_REG16 | rmlow(REG_CX)); /* dec cx */
}
/*
* Handle right expression. All relevant values are now in DI and CX.
* If the it's a character expression, we also have to check if there
* is enough space for the additional character.
*/
if (exprtype(ep->right) == EXPR_STRING) {
putstrexpr(ep->right, &di, rec);
} else if (exprtype(ep->right) == EXPR_CHAR) {
di.memtype = MEM_REGISTER;
di.addr.r = REG_AL;
di.t = NULL;
di_inuse++;
cx_inuse++;
putcharexpr(ep->right, &di);
di_inuse--;
cx_inuse--;
putcode(OP_JCXZ); /* jcxz skip */
putcode(5);
putcode(OP_STOSB); /* stosb */
putimmed(OP_MOV, REG_DI_0DISP | REG_8BIT_FLAG,
0, 0); /* mov [di],0 */
if (rec)
putregop(OP_MOV, REG_DI, REG_AX, 0); /* mov ax,di */
}
/* Resolve the jump, which checked for string size previously */
if (jmpadr > 0) {
tmpcode = codeptr;
codeptr = jmpadr;
putjmp(tmpcode, JMP_UNCOND);
codeptr = tmpcode;
}
/*
* If we were using the string "register", we have to set the processor
* register accordingly.
*/
endstrexpr:
if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
putcode(OP_MOV_WREGIM | rmlow(dp->addr.r));
setreloc();
putint(0);
}
/* At the end restore all registers we might have set */
popregs(); /* pop cx */
popregs(); /* pop di */
}
/*
************************************************************************
*
* Non-scalar expression handling
*
************************************************************************
*/
/*
* Handle leaf nodes. This is the only operation allowed with non-scalars.
*/
static void putleafcomplex(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
struct meminfo si;
unsigned int modrm;
/* Expression doesn't return a value */
if ((dp->memtype & MEM_ADR_MASK) == MEM_NOADDR)
return;
/* Just return the adress of the string in the given register */
#ifdef PARANOID
if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER || !isvariable(ep))
interror(111, "invalid call to 'putleafcomplex'");
#endif
/*
* We have to copy the string/record into the destination. First
* determine the address of the source and put it into SI.
*/
pushregs(PUSH_SI | PUSH_DI | PUSH_CX, dp);
setmeminfo(ep, &si, REG_SI, REG_NONE);
if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
/* Variable is static */
putcode(OP_MOV_WREGIM | rmlow(REG_SI));
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
setreloc();
putint(si.addr.i);
} else if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
modrm = 0;
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
modrm = REG_RELOC_FLAG;
putlea(REG_SI, si.addr.r | modrm, si.addr.i);
}
/* Determine the address of the destination and put it into DI */
if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
putcode(OP_MOV_WREGIM | rmlow(REG_DI)); /* mov di,#dest */
if ((dp->memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
setreloc();
putint(dp->addr.i);
} else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
putlea(REG_DI, dp->addr.r, dp->addr.i); /* lea di,dest */
#ifdef PARANOID
else
interror(112, "invalid destination type");
#endif
/* Determine the size of the destination buffer and put it into CX */
if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE)
putimmed(OP_MOV, REG_CX, dp->size.i, 0); /* mov cx,size */
#ifdef PARANOID
else
interror(113, "invalid size type");
#endif
/* Now actually generate the copy code */
putcode(OP_REP); /* rep */
putcode(OP_MOVSB); /* movsb */
popregs();
}
/*
* Handle complex expressions with record or arrays. The only such
* operations are an assignment and function call.
*/
void putcomplexexpr(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
/* Just a safety measure */
if (ep == NULL)
return;
/* Expression is a function */
if (isfunc(ep)) {
putproc(ep, dp);
return;
}
#ifdef PARANOID
if (isproc(ep))
interror(114, "procedure not allowed in expression");
if (!isleaf(ep))
interror(115, "invalid operation in complex expression");
#endif
/* Expression is a leaf node */
putleafcomplex(ep, dp);
}
/*
************************************************************************
*
* Call a function or procedure
*
************************************************************************
*/
/*
* Determine the size of a non-scalar expression
*/
static addr_t getsize(ep)
struct expr *ep;
{
addr_t argsize = 0;
if (isvariable(ep))
/* Expression is a variable only */
argsize = ep->spec.var.type->size;
else if (isconst(ep) && exprtype(ep) == EXPR_STRING)
/* Expression is a constant string */
argsize = strlen(ep->spec.cval.val.s);
else if (isfunc(ep) && ep->spec.func->def.f.ret != NULL)
/* Expression is a function */
argsize = ep->spec.func->def.f.ret->size;
else if (exprtype(ep) == EXPR_STRING)
/* Expression is a string operation --> we can't forcast it's size */
argsize = MAX_STR_LEN;
#ifdef PARANOID
else
/* There are no other possible operations on non-scalars! */
interror(43, "cannot determine size of non-scalar");
#endif
return(argsize);
}
/*
* Push the address of a variable onto the stack
*/
static void pushvaraddr(ep)
struct expr *ep;
{
struct meminfo si;
unsigned int modrm;
setmeminfo(ep, &si, REG_NONE, REG_NONE);
if ((si.memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
/* String is constant or static */
putcode(OP_MOV_WREGIM | rmlow(REG_AX));
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
setreloc();
putint(si.addr.i);
putpush(REG_AX);
} else if ((si.memtype & MEM_ADR_MASK) == MEM_RELATIVE) {
modrm = 0;
if ((si.memtype & MEM_RELOC_FLAG) == MEM_RELOC_FLAG)
modrm = REG_RELOC_FLAG;
if (si.addr.r == REG_BP) {
putlea(REG_AX, si.addr.r | modrm, si.addr.i);
putpush(REG_AX);
} else {
putlea(si.addr.r, si.addr.r | modrm, si.addr.i);
putpush(si.addr.r);
}
}
}
/*
* Put a function or procedure call into the code segment.
*/
void putproc(ep, dp)
struct expr *ep;
struct meminfo *dp;
{
struct sym *sp = NULL;
struct expr *curexpr;
struct meminfo di;
addr_t tempstrings[MAX_EXPRS];
addr_t argsize, stack, stackofs;
int i;
/* Do some preliminary checks */
if (isfunc(ep) || isproc(ep))
sp = ep->spec.func;
#ifdef PARANOID
if (sp == NULL || !isfuncsym(sp))
interror(70, "invalid function call");
if (ep->exprnum != sp->def.f.argnum)
interror(30, "number of subexpressions doesn't match function prototype");
#endif
/*
* If we have used BX, CX or DX, they have to be saved because the
* called procedure doesn't. We also have to save the base register
* if the destination is relative.
*/
pushregs(PUSH_BX | PUSH_CX | PUSH_DX, dp);
/*
* Decrement the stack pointer to get enough space for non-scalar arguments.
* This is necessary for all non-scalars which are passed by value.
*/
stack = 0;
for (i = 0; i < ep->exprnum; i++) {
#ifdef PARANOID
if (ep->exprlist[i] == NULL)
interror(58, "NULL expression in argument list");
#endif
tempstrings[i] = -1;
if (sp->def.f.attribs[i] == ATTR_NONE ||
(sp->def.f.attribs[i] == ATTR_CONST &&
!isvariable(ep->exprlist[i]) && !isconst(ep->exprlist[i]))) {
/* Argument has to be passed by value and is not constant */
if (isnonscalar(ep->exprlist[i]->type)) {
tempstrings[i] = stack;
stack += (getsize(ep->exprlist[i]) + 2) & 0xfffe;
}
}
#ifdef PARANOID
else if (!isvariable(ep->exprlist[i]) &&
sp->def.f.attribs[i] == ATTR_REF)
interror(31, "passing a non-variable by reference");
#endif
}
if (stack > 0)
putimmed(OP_IMMED_SUB, REG_SP, stack, 0); /* sub sp,ofs */
/*
* If the function returns a non-scalar, push the destination address and
* size for the destination.
*/
stackofs = 0;
if (isfunc(ep) && isnonscalar(ep->type)) {
if ((dp->memtype & MEM_ADR_MASK) == MEM_REGISTER) {
if (exprtype(ep) == EXPR_STRING) {
putimmed(OP_MOV, REG_AX, MAX_STR_LEN, 0);
putpush(REG_AX);
}
putcode(OP_MOV_WREGIM | rmlow(REG_AX));
setreloc();
putint(0); /* Use non-scalar "register", the temp area */
putpush(REG_AX);
} else {
if ((dp->memtype & MEM_SIZE_MASK) == MEM_IMMEDIATE) {
putimmed(OP_MOV, REG_AX, dp->size.i, 0);/* mov ax,size */
putpush(REG_AX); /* push ax */
} else if ((dp->memtype & MEM_SIZE_MASK) == MEM_SIZEREG) {
putpush(dp->size.r);
}
if ((dp->memtype & MEM_ADR_MASK) == MEM_ABSOLUTE) {
putcode(OP_MOV_WREGIM | rmlow(REG_AX)); /* mov ax,addr */
setreloc();
putint(dp->addr.i);
} else if ((dp->memtype & MEM_ADR_MASK) == MEM_RELATIVE)
putlea(REG_AX, dp->addr.r, dp->addr.i); /* lea ax,dest */
#ifdef PARANOID
else
interror(77, "invalid destination type");
#endif
putpush(REG_AX); /* push ax */
}
stackofs += 4;
}
/* Now scan through all expressions and push the values onto the stack */
for (i = 0; i < ep->exprnum; i++) {
argsize = 0;
curexpr = ep->exprlist[i];
if (sp->def.f.attribs[i] == ATTR_REF) {
/*
* The argument has to be passed by reference, so determine
* and push it's address onto the stack. It has already been
* checked above that the argument is indeed a variable, so
* don't have to repeat that check here.
* Strings passed by reference get the size _and_ address
* pushed.
*/
#ifdef PARANOID
if (!isvariable(curexpr))
interror(75, "passing by reference something not a variable");
#endif
if (exprtype(ep->exprlist[i]) == EXPR_STRING) {
putimmed(OP_MOV, REG_AX, /* mov ax,size */
ep->exprlist[i]->type->size, 0);
putpush(REG_AX); /* push ax */
argsize += 2;
}
pushvaraddr(curexpr); /* push addr */
argsize += 2;
} else switch (exprtype(ep->exprlist[i])) {
case EXPR_NUM:
case EXPR_ENUM:
di.memtype = MEM_REGISTER;
di.addr.r = REG_AX;
di.t = NULL;
putintexpr(curexpr, &di);
putpush(REG_AX);
argsize = 2;
break;
case EXPR_STRING:
if (sp->def.f.attribs[i] == ATTR_CONST &&
(isvariable(curexpr) || isconst(curexpr))) {
/* For constant string we just pass the addr*/
if (isvariable(curexpr))
pushvaraddr(curexpr);
else {
di.memtype = MEM_REGISTER | MEM_NOSIZE;
di.addr.r = REG_AX;
di.t = NULL;
putstrexpr(curexpr, &di, FALSE);
putpush(REG_AX);
}
} else {
#ifdef PARANOID
if (tempstrings[i] < 0)
interror(32, "passing string by value without buffer");
#endif
putregop(OP_MOV, REG_SP, REG_BX, 0);
putlea(REG_BX, REG_BX, stackofs + tempstrings[i]);
putpush(REG_BX);
di.memtype = MEM_RELATIVE | MEM_IMMEDIATE;
di.addr.i = 0;
di.addr.r = REG_BX;
di.size.i = getsize(curexpr) + 1;
di.t = NULL;
putstrexpr(curexpr, &di, FALSE);
}
argsize = 2;
break;
case EXPR_BOOL:
di.memtype = MEM_REGISTER;
di.addr.r = REG_AL;
di.t = NULL;
putboolexpr(curexpr, &di);
putpush(REG_AX);
argsize = 2;
break;
case EXPR_CHAR:
di.memtype = MEM_REGISTER;
di.addr.r = REG_AL;
di.t = NULL;
putcharexpr(curexpr, &di);
putpush(REG_AX);
argsize = 2;
break;
case EXPR_NONE:
argsize = 0;
break;
}
stackofs += argsize;
}
/* Determine if we have to call an internal or a user function/menu */
if (ep->opcode >= CMD_FIRSTINT) {
putfunc(ep->opcode, stack + stackofs);
} else {
putcode(OP_CALL);
putint((long)(sp->addr - codeptr - 2));
if ((stack + stackofs) > 0)
/* add sp,#stack */
putimmed(OP_IMMED_ADD, REG_SP, (long)(stack + stackofs), 0);
}
/* Restore the registers which we saved previously */
popregs();
/* Save the result into the destination */
if ((dp->memtype & MEM_ADR_MASK) != MEM_NOADDR)
switch (exprtype(ep)) {
case EXPR_NUM:
case EXPR_ENUM:
putsaveintreg(REG_AX, dp);
break;
case EXPR_BOOL:
case EXPR_CHAR:
putsaveintreg(REG_AL, dp);
break;
default:
/*
* Non-scalars are already copied into destination
* buffer by the called function
*/
break;
}
}
syntax highlighted by Code2HTML, v. 0.9.1