831 lines
14 KiB
C
831 lines
14 KiB
C
/* asmequ.c -- microsoft 80x86 assembler
|
|
**
|
|
** microsoft (r) macro assembler
|
|
** copyright (c) microsoft corp 1986. all rights reserved
|
|
**
|
|
** randy nevin
|
|
**
|
|
** 10/90 - Quick conversion to 32 bit by Jeff Spencer
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "asm86.h"
|
|
#include "asmfcn.h"
|
|
#include "asmctype.h"
|
|
#include "asmmsg.h"
|
|
|
|
/* EQU statement : There are 3 basic kinds of EQU:
|
|
|
|
1. To expression
|
|
2. To symbol( synonym )
|
|
3. All others are text macros
|
|
|
|
*/
|
|
|
|
VOID PASCAL CODESIZE assignconst ( USHORT );
|
|
|
|
char isGolbal; /* flag indicating if equ symbol was global */
|
|
|
|
/*** assignvalue - assign value to symbol
|
|
*
|
|
* assignvalue ();
|
|
*
|
|
* Entry
|
|
* Exit
|
|
* Returns
|
|
* Calls
|
|
*/
|
|
|
|
|
|
VOID PASCAL CODESIZE
|
|
assignvalue ()
|
|
{
|
|
struct eqar a;
|
|
register struct psop *pso;
|
|
register SYMBOL FARSYM *sym;
|
|
register DSCREC *dsc;
|
|
|
|
switchname ();
|
|
|
|
if (createequ(EXPR)) {
|
|
|
|
sym = symptr;
|
|
sym->attr |= M_BACKREF; /* Set we have DEFINED */
|
|
|
|
dsc = (equflag)? itemptr: expreval (&nilseg);
|
|
pso = &(dsc->dsckind.opnd);
|
|
|
|
if (noexp)
|
|
errorc (E_OPN);
|
|
|
|
/* If error, set undefined */
|
|
if (errorcode && errorcode != E_RES)
|
|
sym->attr &= ~(M_DEFINED | M_BACKREF);
|
|
|
|
if (equflag && equdef) {
|
|
if (sym->offset != pso->doffset ||
|
|
sym->symu.equ.equrec.expr.esign != pso->dsign ||
|
|
sym->symsegptr != pso->dsegment)
|
|
muldef ();
|
|
}
|
|
/* If = involves forward, don't set BACKREF */
|
|
if (M_FORTYPE & pso->dtype){
|
|
sym->attr &= ~M_BACKREF;
|
|
|
|
if (sym->attr & M_GLOBAL)
|
|
sym->attr &= ~M_GLOBAL;
|
|
}
|
|
if (pso->mode != 4 &&
|
|
!(pso->mode == 0 && pso->rm == 6) &&
|
|
!(pso->mode == 5 && pso->rm == 5) ||
|
|
pso->dflag == XTERNAL)
|
|
|
|
/* Not right kind of result */
|
|
errorc (E_IOT);
|
|
|
|
sym->symsegptr = pso->dsegment;
|
|
sym->symu.equ.equrec.expr.eassume = NULL;
|
|
if (pso->dtype == M_CODE)
|
|
sym->symu.equ.equrec.expr.eassume = pso->dcontext;
|
|
|
|
sym->length = 0;
|
|
sym->offset = pso->doffset;
|
|
/* Note: change sign */
|
|
sym->symu.equ.equrec.expr.esign = pso->dsign;
|
|
sym->symtype = pso->dsize;
|
|
|
|
if ((pso->dtype == M_RCONST || !pso->dsegment) &&
|
|
!(M_PTRSIZE & pso->dtype))
|
|
sym->symtype = 0;
|
|
|
|
if (fNeedList) {
|
|
|
|
listbuffer[1] = '=';
|
|
listindex = 3;
|
|
if (sym->symu.equ.equrec.expr.esign)
|
|
listbuffer[2] = '-';
|
|
|
|
offsetAscii (sym->offset);
|
|
copyascii ();
|
|
}
|
|
dfree ((char *)dsc );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** createequ - create entry for equ
|
|
*
|
|
* flag = createequ (typ, p)
|
|
*
|
|
* Entry typ = type of equ
|
|
* Exit
|
|
* Returns TRUE if equ created or found of right type
|
|
* FALSE if equ not created or found and wrong type
|
|
* Calls labelcreate, switchname
|
|
*/
|
|
|
|
|
|
UCHAR PASCAL CODESIZE
|
|
createequ (
|
|
UCHAR typ
|
|
){
|
|
|
|
equsel = typ;
|
|
switchname ();
|
|
labelcreate (0, EQU);
|
|
|
|
/* Make sure not set set fields if wrong type, flag to caller */
|
|
if (symptr->symkind != EQU || symptr->symu.equ.equtyp != typ) {
|
|
|
|
errorn (E_SDK);
|
|
return (FALSE);
|
|
}
|
|
else {
|
|
switchname ();
|
|
isGolbal = 0;
|
|
|
|
if (equsel == ALIAS){ /* lose public on pointer to alias */
|
|
|
|
isGolbal = symptr->attr & M_GLOBAL ? M_GLOBAL : 0;
|
|
symptr->attr &= ~M_GLOBAL;
|
|
}
|
|
|
|
if (typ != EXPR)
|
|
symptr->symsegptr = NULL;
|
|
|
|
return (TRUE);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** equtext - make remainder of line into text form of EQU
|
|
*
|
|
* equtext ();
|
|
*
|
|
* Entry
|
|
* Exit
|
|
* Returns
|
|
* Calls error, skipblanks
|
|
*/
|
|
|
|
|
|
VOID PASCAL CODESIZE
|
|
equtext (
|
|
USHORT cb
|
|
){
|
|
register UCHAR *pFirst, *pT, *pOld;
|
|
|
|
if (createequ (TEXTMACRO)) {
|
|
|
|
/* find end of line & then delete trailing blanks */
|
|
|
|
pFirst = lbufp;
|
|
|
|
if (cb == ((USHORT)-1)) {
|
|
for (pT = pFirst; *pT && *pT != ';'; pT++);
|
|
|
|
for (; pT > pFirst && ISBLANK (pT[-1]) ; pT--);
|
|
|
|
lbufp = pT;
|
|
cb = (USHORT)(pT - pFirst);
|
|
}
|
|
|
|
pOld = symptr->symu.equ.equrec.txtmacro.equtext;
|
|
|
|
pT = nalloc((USHORT)(cb+1), "equtext");
|
|
pT[cb] = NULL;
|
|
|
|
symptr->symu.equ.equrec.txtmacro.equtext =
|
|
(char *) memcpy(pT, pFirst, cb);
|
|
if (pOld)
|
|
free (pOld);
|
|
|
|
copystring (pT);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** equdefine - define EQU
|
|
*
|
|
* equdefine ();
|
|
*
|
|
* Entry
|
|
* Exit
|
|
* Returns
|
|
* Calls
|
|
*/
|
|
|
|
|
|
VOID PASCAL CODESIZE
|
|
equdefine ()
|
|
{
|
|
register SYMBOL FARSYM *pSY;
|
|
struct eqar a;
|
|
register char *p;
|
|
USHORT cb;
|
|
UCHAR opc = FALSE;
|
|
|
|
listbuffer[1] = '=';
|
|
switchname ();
|
|
a.dirscan = lbufp;
|
|
|
|
if (PEEKC () == '<') { /* look for <text macro> */
|
|
|
|
p = getTMstring();
|
|
a.dirscan = lbufp;
|
|
lbufp = p;
|
|
equtext ((USHORT)(a.dirscan - p - 1));
|
|
lbufp = a.dirscan;
|
|
return;
|
|
}
|
|
|
|
getatom ();
|
|
if ((*naim.pszName == '$') && (naim.pszName[1] == 0))
|
|
*naim.pszName = 0;
|
|
/*Need to check if 1st atom is an operator, otherwise
|
|
will make OFFSET an alias instead of text. */
|
|
if (fnoper ())
|
|
*naim.pszName = 0;
|
|
|
|
if (*naim.pszName && ISTERM (PEEKC ()) && !(opc = opcodesearch ())) {
|
|
|
|
/* Alias */
|
|
if (createequ (ALIAS)) {
|
|
|
|
pSY = symptr;
|
|
|
|
if (!symsrch ()) {
|
|
if (pass2)
|
|
/* Undefined */
|
|
errorn (E_SND);
|
|
/* Don't know symbol yet */
|
|
pSY->symu.equ.equrec.alias.equptr = NULL;
|
|
}
|
|
else {
|
|
/* Alias symbol is DEFINED */
|
|
|
|
pSY->attr = (unsigned char)(pSY->attr&~M_BACKREF | symptr->attr&M_BACKREF);
|
|
|
|
if (!pSY->symu.equ.equrec.alias.equptr)
|
|
pSY->symu.equ.equrec.alias.equptr = symptr;
|
|
|
|
if (pSY->symu.equ.equrec.alias.equptr != symptr) {
|
|
/* This is multiple definition */
|
|
symptr = pSY;
|
|
muldef ();
|
|
}
|
|
else {
|
|
/* See if good */
|
|
if (pSY = chasealias (pSY))
|
|
pSY->attr |= isGolbal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* Must be text form or expr */
|
|
#ifdef BCBOPT
|
|
goodlbufp = FALSE;
|
|
#endif
|
|
lbufp = a.dirscan;
|
|
xcreflag--;
|
|
emittext = FALSE;
|
|
|
|
if (opc) { /* quick patch to allow i.e. SYM equ MOV */
|
|
equtext ((USHORT)-1);
|
|
emittext = TRUE;
|
|
xcreflag++;
|
|
return;
|
|
}
|
|
|
|
a.dsc = expreval (&nilseg);
|
|
emittext = TRUE;
|
|
xcreflag++;
|
|
|
|
/* So don't see double ref */
|
|
/* force text if OFFSET or : */
|
|
if (a.dsc->dsckind.opnd.mode != 4 &&
|
|
!(a.dsc->dsckind.opnd.mode == 0 && a.dsc->dsckind.opnd.rm == 6) &&
|
|
!(a.dsc->dsckind.opnd.mode == 5 && a.dsc->dsckind.opnd.rm == 5) ||
|
|
|
|
(errorcode && errorcode != E_SND && errorcode != E_RES) ||
|
|
|
|
(M_EXPLOFFSET|M_EXPLCOLON|M_HIGH|M_LOW) & a.dsc->dsckind.opnd.dtype ||
|
|
|
|
a.dsc->dsckind.opnd.seg != NOSEG ||
|
|
a.dsc->dsckind.opnd.dflag == XTERNAL) {
|
|
|
|
/* Not good expression */
|
|
if (errorcode != E_LTL)
|
|
errorcode = 0;
|
|
dfree ((char *)a.dsc );
|
|
lbufp = a.dirscan;
|
|
equtext ((USHORT)-1);
|
|
}
|
|
else {
|
|
/* This is expression */
|
|
itemptr = a.dsc;
|
|
switchname ();
|
|
equflag = TRUE;
|
|
assignvalue ();
|
|
equflag = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** definesym - define symbol from command line
|
|
*
|
|
* definesym (p);
|
|
*
|
|
* Entry *p = symbol text
|
|
* Exit symbol define as EQU with value of 0
|
|
* Returns none
|
|
* Calls
|
|
*/
|
|
|
|
|
|
void PASCAL
|
|
definesym (
|
|
UCHAR *p
|
|
){
|
|
struct eqar a;
|
|
|
|
fCheckRes++;
|
|
fSkipList++;
|
|
|
|
#ifdef BCBOPT
|
|
goodlbufp = FALSE;
|
|
#endif
|
|
|
|
strcpy (lbufp = save, p);
|
|
getatom ();
|
|
if ((PEEKC() == 0 || PEEKC() == '=') && *naim.pszName) {
|
|
if (PEEKC() == '=')
|
|
SKIPC();
|
|
|
|
switchname ();
|
|
equtext ((USHORT)-1);
|
|
}
|
|
else
|
|
errorcode++;
|
|
|
|
fSkipList--;
|
|
fCheckRes--;
|
|
}
|
|
|
|
|
|
|
|
/*** defwordsize - define @WordSize using definesym()
|
|
*
|
|
* defwordsize ( );
|
|
*
|
|
* Entry
|
|
* Exit
|
|
* Returns
|
|
* Calls definesym()
|
|
*/
|
|
|
|
|
|
VOID PASCAL
|
|
defwordsize ()
|
|
{
|
|
static char wstext[] = "@WordSize=0D";
|
|
|
|
wstext[10] = wordsize + '0';
|
|
definesym(wstext);
|
|
symptr->attr |= M_NOCREF; /* don't cref @WordSize */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*** chasealias - return value of alias list
|
|
*
|
|
* symb = chasealias (equsym);
|
|
*
|
|
* Entry
|
|
* Exit
|
|
* Returns
|
|
* Calls
|
|
*/
|
|
|
|
|
|
SYMBOL FARSYM * PASCAL CODESIZE
|
|
chasealias (
|
|
SYMBOL FARSYM *equsym
|
|
){
|
|
register SYMBOL FARSYM *endalias;
|
|
|
|
endalias = equsym;
|
|
|
|
do {
|
|
/* Must check to see if EQU to self */
|
|
|
|
if (endalias->symu.equ.equrec.alias.equptr == equsym) {
|
|
|
|
endalias->symu.equ.equrec.alias.equptr = NULL;
|
|
errorc (E_CEA);
|
|
return (NULL);
|
|
}
|
|
|
|
endalias = endalias->symu.equ.equrec.alias.equptr;
|
|
|
|
if (!endalias) {
|
|
errorn (E_SND);
|
|
return(NULL); /* This is undefined */
|
|
}
|
|
|
|
} while (!(endalias->symkind != EQU ||
|
|
endalias->symu.equ.equtyp != ALIAS));
|
|
|
|
/* Now check final is ok - Only constant allowed */
|
|
|
|
if (endalias->symkind == EQU &&
|
|
endalias->symu.equ.equtyp != EXPR){
|
|
|
|
errorc (E_IOT);
|
|
return (NULL);
|
|
}
|
|
|
|
return (endalias);
|
|
}
|
|
|
|
|
|
|
|
/*** getTMstring - process a string or text macro
|
|
* used by substring, catstring, sizestring, & instring
|
|
*
|
|
* char * getTMstring ();
|
|
*
|
|
* Entry lbufp points to beginning of string or TM
|
|
* Exit
|
|
* Returns Pointer to string or equtext of TM
|
|
* Calls
|
|
*/
|
|
|
|
|
|
char * PASCAL CODESIZE
|
|
getTMstring ()
|
|
{
|
|
char cc;
|
|
register char * p;
|
|
static char tms [] = "text macro";
|
|
static char digitsT[33];
|
|
char * ret = NULL;
|
|
|
|
|
|
skipblanks ();
|
|
|
|
p = lbufp;
|
|
|
|
if ((cc = *p) == '<' ) {
|
|
|
|
ret = p + 1;
|
|
|
|
while (*(++p) && (*p != '>'))
|
|
;
|
|
|
|
if (!*p)
|
|
error(E_EXP,tms);
|
|
else
|
|
*(p++) = 0;
|
|
|
|
lbufp = p;
|
|
|
|
}
|
|
else if (test4TM()) {
|
|
ret = symptr->symu.equ.equrec.txtmacro.equtext;
|
|
|
|
}
|
|
else if (cc == '%') {
|
|
|
|
pTextEnd = (char *) -1;
|
|
lbufp = p+1;
|
|
*xxradixconvert (exprconst(), digitsT) = NULL;
|
|
return (digitsT);
|
|
}
|
|
else
|
|
error(E_EXP,tms );
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
|
|
/*** substring - process the subStr directive
|
|
*
|
|
* substring ();
|
|
*
|
|
* Syntax:
|
|
*
|
|
* <ident> subStr <subjectString> , <startIndex> {, <length> }
|
|
*
|
|
* Defines <ident> as a TEXTMACRO.
|
|
* <subjectString> must be a TEXTMACRO or a string: " ", < >, ' '
|
|
* <startIndex>: constant expression between 1 and strlen(subjectString)
|
|
* Optional <length>: constant expression between 0 and
|
|
* (strlen(subjectString) - startIndex + 1)
|
|
*
|
|
* Entry lbufp points to beginning of subjectString
|
|
* Exit
|
|
* Returns
|
|
* Calls getTMstring
|
|
*/
|
|
|
|
|
|
VOID PASCAL CODESIZE
|
|
substring ()
|
|
{
|
|
struct eqar a;
|
|
char *p;
|
|
register USHORT cb;
|
|
char cc;
|
|
register char *subjtext;
|
|
USHORT slength;
|
|
USHORT startindex = 0;
|
|
|
|
listbuffer[1] = '=';
|
|
switchname ();
|
|
|
|
/* First find string or text macro */
|
|
|
|
if (!(subjtext = getTMstring () ))
|
|
return;
|
|
|
|
cb = (USHORT) strlen(subjtext);
|
|
|
|
/* then check for start index */
|
|
|
|
if (skipblanks () == ',') {
|
|
SKIPC ();
|
|
startindex = (USHORT)(exprconst() - 1); /* get start index */
|
|
|
|
} else
|
|
error(E_EXP,"comma");
|
|
|
|
|
|
/* then check for length */
|
|
|
|
if (skipblanks () == ',') {
|
|
SKIPC ();
|
|
|
|
slength = (USHORT)exprconst(); /* get start index */
|
|
|
|
} else
|
|
slength = cb - startindex;
|
|
|
|
if (startindex > cb || slength > cb - startindex) {
|
|
errorc (E_VOR);
|
|
return;
|
|
}
|
|
|
|
p = lbufp;
|
|
|
|
lbufp = subjtext + startindex; /* set lbufp to start of substring */
|
|
equtext(slength); /* end of string index */
|
|
|
|
lbufp = p;
|
|
|
|
if (errorcode && symptr)
|
|
symptr->attr &= ~(M_DEFINED | M_BACKREF);
|
|
}
|
|
|
|
|
|
|
|
/*** catstring - process the catstr directive
|
|
*
|
|
* catstring ();
|
|
*
|
|
* Syntax:
|
|
*
|
|
* <ident> catStr <subjectString> {, <subjectString> } ...
|
|
*
|
|
* Defines <ident> as a TEXTMACRO.
|
|
* Each <subjectString> must be a TEXTMACRO or a string: " ", < >, ' '
|
|
*
|
|
* Entry
|
|
* Exit
|
|
* Returns
|
|
* Calls
|
|
*/
|
|
|
|
|
|
VOID PASCAL CODESIZE
|
|
catstring ()
|
|
{
|
|
struct eqar a;
|
|
register USHORT cb;
|
|
char *subjtext;
|
|
char resulttext[LBUFMAX];
|
|
USHORT cbresult = 0;
|
|
register char *p = resulttext;
|
|
|
|
listbuffer[1] = '=';
|
|
switchname ();
|
|
*p = '\0';
|
|
|
|
/* First find string or text macro */
|
|
|
|
do {
|
|
|
|
if (!(subjtext = getTMstring () ))
|
|
break;
|
|
|
|
cb = (USHORT) strlen (subjtext);
|
|
cbresult += cb;
|
|
|
|
if(cbresult > LBUFMAX) {
|
|
errorc(E_LTL);
|
|
break;
|
|
}
|
|
|
|
memcpy (p, subjtext, cb + 1); /* + 1 copies NULL */
|
|
p += cb;
|
|
|
|
} while (skipblanks() && NEXTC () == ',');
|
|
|
|
p = --lbufp;
|
|
lbufp = resulttext;
|
|
equtext(cbresult);
|
|
lbufp = p;
|
|
|
|
if (errorcode)
|
|
symptr->attr &= ~(M_DEFINED | M_BACKREF);
|
|
}
|
|
|
|
|
|
|
|
/*** assignconst - like assignvalue, only takes value as argument
|
|
*
|
|
* assignconst (cb);
|
|
*
|
|
* Entry USHORT cb == value to assign
|
|
* Exit
|
|
* Returns
|
|
* Calls
|
|
*/
|
|
|
|
|
|
VOID PASCAL CODESIZE
|
|
assignconst (
|
|
USHORT cb
|
|
){
|
|
register SYMBOL FARSYM *sym;
|
|
struct eqar a;
|
|
|
|
if (createequ(EXPR)) {
|
|
|
|
sym = symptr;
|
|
|
|
if (errorcode)
|
|
sym->attr &= ~(M_DEFINED | M_BACKREF);
|
|
else
|
|
sym->attr |= M_BACKREF; /* Set we have DEFINED */
|
|
|
|
sym->symsegptr = NULL;
|
|
sym->symu.equ.equrec.expr.eassume = NULL;
|
|
sym->length = 0;
|
|
sym->offset = cb;
|
|
|
|
sym->symu.equ.equrec.expr.esign = 0;
|
|
sym->symtype = 0;
|
|
|
|
if (fNeedList) {
|
|
|
|
listbuffer[1] = '=';
|
|
listindex = 3;
|
|
|
|
offsetAscii (sym->offset);
|
|
copyascii ();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*** sizestring - process the sizeStr directive
|
|
*
|
|
* sizestring ();
|
|
*
|
|
* Syntax:
|
|
*
|
|
* <ident> sizeStr <subjectString>
|
|
*
|
|
* Defines <ident> as a EXPR.
|
|
* The <subjectString> must be a TEXTMACRO or a string: " ", < >, ' '
|
|
*
|
|
* Entry
|
|
* Exit
|
|
* Returns
|
|
* Calls
|
|
*/
|
|
|
|
|
|
VOID PASCAL CODESIZE
|
|
sizestring ()
|
|
{
|
|
register USHORT cb = 0;
|
|
char *p;
|
|
|
|
switchname ();
|
|
|
|
/* First find string or text macro */
|
|
|
|
if (p = getTMstring () )
|
|
cb = (USHORT) strlen (p);
|
|
|
|
assignconst (cb);
|
|
}
|
|
|
|
|
|
|
|
/*** instring - process the instr directive
|
|
*
|
|
* instring ();
|
|
*
|
|
* Syntax:
|
|
*
|
|
* <ident> inStr { <startIndex> } , <subjectString> , <searchString>
|
|
*
|
|
* Defines <ident> as a TEXTMACRO.
|
|
* <startIndex>: constant expression between 1 and strlen(subjectString)
|
|
* <subjectString> must be a TEXTMACRO or a string: " ", < >, ' '
|
|
* <searchString> must be a TEXTMACRO or a string: " ", < >, ' '
|
|
*
|
|
* Entry lbufp points to beginning of subjectString
|
|
* Exit
|
|
* Returns
|
|
* Calls getTMstring
|
|
*/
|
|
|
|
//char * strstr();
|
|
|
|
|
|
VOID PASCAL CODESIZE
|
|
instring ()
|
|
{
|
|
register char *p;
|
|
register USHORT cb = 0;
|
|
register char cc;
|
|
char *subjtext;
|
|
char *searchtext;
|
|
USHORT startindex = 1;
|
|
|
|
switchname ();
|
|
|
|
/* First find start index */
|
|
|
|
p = lbufp;
|
|
|
|
if ((cc = *p) != '"' && cc != '\'' && cc != '<' && !test4TM ()) {
|
|
|
|
lbufp = p;
|
|
startindex = (USHORT)exprconst(); /* get start index */
|
|
|
|
if (lbufp != p)
|
|
if (skipblanks () == ',')
|
|
SKIPC ();
|
|
else
|
|
error(E_EXP,"comma");
|
|
|
|
} else
|
|
lbufp = p;
|
|
|
|
if (subjtext = getTMstring () ) {
|
|
|
|
cb = (USHORT) strlen(subjtext);
|
|
|
|
if (startindex < 1 || startindex > cb)
|
|
errorc (E_VOR);
|
|
|
|
if (skipblanks () == ',')
|
|
SKIPC ();
|
|
else
|
|
error(E_EXP,"comma");
|
|
|
|
|
|
/* then check for searchtext */
|
|
|
|
if (searchtext = getTMstring () ) {
|
|
|
|
p = subjtext + startindex - 1;
|
|
if (p = strstr (p, searchtext))
|
|
cb = (USHORT)(p - subjtext + 1);
|
|
else
|
|
cb = 0;
|
|
}
|
|
}
|
|
|
|
assignconst (cb);
|
|
}
|