/* errmsg.h */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* */
/* Cross Assembler */
/* */
/* Error message definition & associated checking */
/* */
/*****************************************************************************/
#include <string.h>
#include <stdarg.h>
#include "strutil.h"
#include "datatypes.h"
#include "asmdef.h"
#include "asmpars.h"
#include "strutil.h"
#include "asmsub.h"
#include "nlmessages.h"
#include "cpulist.h"
#include "as.rsc"
#include "errmsg.h"
static tErrorNum GetDefaultCPUErrorNum
(tErrorNum ThisNum
)
{
return ThisNum
? ThisNum
: ErrNum_InstructionNotSupported
;
}
/*!------------------------------------------------------------------------
* \fn report_error_range(LargeInt value, LargeInt ref, char comp_op, tErrorNum error_num, const tStrComp *p_comp)
* \brief core routine to emit range error message
* \param value offending value
* \param ref reference value was checked against
* \param comp_op comparison op that failed
* \param error_num error message to emit
* \param p_comp source argument (may be NULL)
* \return constant False
* ------------------------------------------------------------------------ */
static Boolean report_error_range
(LargeInt value
, LargeInt ref
, char comp_op
, tErrorNum error_num
, const tStrComp
*p_comp
)
{
char s
[100];
as_snprintf
(s
, sizeof(s
), "%llld %c %llld", value
, comp_op
, ref
);
if (p_comp
)
WrXErrorPos
(error_num
, s
, &p_comp
->Pos
);
else
WrXError
(error_num
, s
);
return False
;
}
/*!------------------------------------------------------------------------
* \fn ChkRangePos(LargeInt Value, LargeInt Min, LargeInt Max, const tStrComp *p_comp)
* \brief check whether integer is in range and issue error if not
* \param Value value to check
* \param Min minimum of range
* \param Max maximum of range
* \param p_comp corresponding source argument (may be NULL)
* \return TRUE if in-range and no error
* ------------------------------------------------------------------------ */
Boolean ChkRangePos
(LargeInt Value
, LargeInt Min
, LargeInt Max
, const tStrComp
*p_comp
)
{
if (Value
< Min
)
return report_error_range
(Value
, Min
, '<', ErrNum_UnderRange
, p_comp
);
else if (Value
> Max
)
return report_error_range
(Value
, Max
, '>', ErrNum_OverRange
, p_comp
);
else
return True
;
}
/*!------------------------------------------------------------------------
* \fn ChkRangeWarnPos(LargeInt Value, LargeInt Min, LargeInt Max, const tStrComp *p_comp)
* \brief check whether integer is in range and issue error if not
* \param Value value to check
* \param Min minimum of range
* \param Max maximum of range
* \param p_comp corresponding source argument (may be NULL)
* \return TRUE if in-range and no error
* ------------------------------------------------------------------------ */
Boolean ChkRangeWarnPos
(LargeInt Value
, LargeInt Min
, LargeInt Max
, const tStrComp
*p_comp
)
{
if (Value
< Min
)
return report_error_range
(Value
, Min
, '<', ErrNum_WUnderRange
, p_comp
);
else if (Value
> Max
)
return report_error_range
(Value
, Max
, '>', ErrNum_WOverRange
, p_comp
);
else
return True
;
}
/*!------------------------------------------------------------------------
* \fn ChkArgCntExtPos(int ThisCnt, int MinCnt, int MaxCnt, const struct sLineComp *pComp)
* \brief check whether argument count is within given range and issue error if not
* \param ThisCnt count to check
* \param MinCnt minimum allowed count
* \param MaxCnt maximum allowed count
* \param pComp position in source line (optional)
* \return TRUE if in-range and no error
* ------------------------------------------------------------------------ */
Boolean ChkArgCntExtPos
(int ThisCnt
, int MinCnt
, int MaxCnt
, const struct sLineComp
*pComp
)
{
if ((ThisCnt
< MinCnt
) || (ThisCnt
> MaxCnt
))
{
char Str
[100];
if (MinCnt
!= MaxCnt
)
as_snprintf
(Str
, sizeof(Str
), getmessage
(Num_ErrMsgArgCntFromTo
), MinCnt
, MaxCnt
, ThisCnt
);
else switch (MinCnt
)
{
case 0:
as_snprintf
(Str
, sizeof(Str
), getmessage
(Num_ErrMsgArgCntZero
), ThisCnt
);
break;
case 1:
as_snprintf
(Str
, sizeof(Str
), getmessage
(Num_ErrMsgArgCntOne
), ThisCnt
);
break;
default:
as_snprintf
(Str
, sizeof(Str
), getmessage
(Num_ErrMsgArgCntMulti
), MinCnt
, ThisCnt
);
}
if (pComp
)
WrXErrorPos
(ErrNum_WrongArgCnt
, Str
, pComp
);
else
WrXError
(ErrNum_WrongArgCnt
, Str
);
return False
;
}
else
return True
;
}
/*!------------------------------------------------------------------------
* \fn ChkArgCntExtEitherOr(int ThisCnt, int EitherCnt, int OrCnt)
* \brief check whether argument count is according to given values and issue error if not
* \param ThisCnt count to check
* \param EitherCnt allowed count
* \param OrCnt other allowed count
* \return TRUE if count OK and no error
* ------------------------------------------------------------------------ */
Boolean ChkArgCntExtEitherOr
(int ThisCnt
, int EitherCnt
, int OrCnt
)
{
if ((ThisCnt
!= EitherCnt
) && (ThisCnt
!= OrCnt
))
{
char Str
[100];
as_snprintf
(Str
, sizeof(Str
), getmessage
(Num_ErrMsgArgCntEitherOr
), EitherCnt
, OrCnt
, ThisCnt
);
WrXError
(ErrNum_WrongArgCnt
, Str
);
return False
;
}
else
return True
;
}
/*!------------------------------------------------------------------------
* \fn ChkMinCPUExt(CPUVar MinCPU, tErrorNum ErrorNum)
* \brief check whether currently selected CPU is at least given one and issue error if not
* \param MinCPU minimum required CPU
* \param ErrorNum error to issue if not OK (0 = default message)
* \return TRUE if currently selected CPU is OK and no error
* ------------------------------------------------------------------------ */
extern Boolean ChkMinCPUExt
(CPUVar MinCPU
, tErrorNum ErrorNum
)
{
if (MomCPU
< MinCPU
)
{
const tCPUDef
*pCPUDef
;
ErrorNum
= GetDefaultCPUErrorNum
(ErrorNum
);
pCPUDef
= LookupCPUDefByVar
(MinCPU
);
if (pCPUDef
)
{
char Str
[100];
as_snprintf
(Str
, sizeof(Str
), getmessage
(Num_ErrMsgMinCPUSupported
), pCPUDef
->Name
);
WrXError
(ErrorNum
, Str
);
}
else
WrError
(ErrorNum
);
return False
;
}
return True
;
}
/*!------------------------------------------------------------------------
* \fn AChkMinCPUExtPos(CPUVar MinCPU, tErrorNum ErrorNum, const struct sStrComp *pComp)
* \brief check for minimum CPU of this addressing mode
* \param MinCPU min. CPU required
* \param ErrorNum error message to print
* \param pComp argument to complain about
* \return True if CPU is OK
* ------------------------------------------------------------------------ */
Boolean AChkMinCPUExtPos
(CPUVar MinCPU
, tErrorNum ErrorNum
, const struct sStrComp
*pComp
)
{
if (MomCPU
< MinCPU
)
{
WrStrErrorPos
(ErrorNum
, pComp
);
return False
;
}
return True
;
}
/*!------------------------------------------------------------------------
* \fn ChkMaxCPUExt(CPUVar MaxCPU, tErrorNum ErrorNum)
* \brief check whether currently selected CPU is at most given one and issue error if not
* \param MaxCPU maximum required CPU
* \param ErrorNum error to issue if not OK (0 = default message)
* \return TRUE if currently selected CPU is OK and no error
* ------------------------------------------------------------------------ */
extern Boolean ChkMaxCPUExt
(CPUVar MaxCPU
, tErrorNum ErrorNum
)
{
if (MomCPU
> MaxCPU
)
{
const tCPUDef
*pCPUDef
;
ErrorNum
= GetDefaultCPUErrorNum
(ErrorNum
);
pCPUDef
= LookupCPUDefByVar
(MaxCPU
);
if (pCPUDef
)
{
char Str
[100];
as_snprintf
(Str
, sizeof(Str
), getmessage
(Num_ErrMsgMaxCPUSupported
), pCPUDef
->Name
);
WrXError
(ErrorNum
, Str
);
}
else
WrError
(ErrorNum
);
return False
;
}
return True
;
}
/*!------------------------------------------------------------------------
* \fn ChkRangeCPUExt(CPUVar MinCPU, CPUVar MaxCPU, tErrorNum ErrorNum)
* \brief check whether currently selected CPU is within given range
* \param MinCPU minimum required CPU
* \param MaxCPU maximum required CPU
* \param ErrorNum error to issue if not OK (0 = default message)
* \return TRUE if currently selected CPU is OK and no error
* ------------------------------------------------------------------------ */
extern Boolean ChkRangeCPUExt
(CPUVar MinCPU
, CPUVar MaxCPU
, tErrorNum ErrorNum
)
{
if ((MomCPU
< MinCPU
) || (MomCPU
> MaxCPU
))
{
const tCPUDef
*pCPUDefMin
, *pCPUDefMax
;
ErrorNum
= GetDefaultCPUErrorNum
(ErrorNum
);
pCPUDefMin
= LookupCPUDefByVar
(MinCPU
);
pCPUDefMax
= LookupCPUDefByVar
(MaxCPU
);
if (pCPUDefMin
&& pCPUDefMax
)
{
char Str
[100];
as_snprintf
(Str
, sizeof(Str
), getmessage
(Num_ErrMsgRangeCPUSupported
), pCPUDefMin
->Name
, pCPUDefMax
->Name
);
WrXError
(ErrorNum
, Str
);
}
else
WrError
(ErrorNum
);
return False
;
}
return True
;
}
/*!------------------------------------------------------------------------
* \fn ChkMinCPUExt(CPUVar MatchCPU, tErrorNum ErrorNum)
* \brief check whether currently selected CPU is given one and issue error if not
* \param MatchCPU required CPU
* \param ErrorNum error to issue if not OK (0 = default message)
* \return TRUE if currently selected CPU is OK and no error
* ------------------------------------------------------------------------ */
extern Boolean ChkExactCPUExt
(CPUVar MatchCPU
, tErrorNum ErrorNum
)
{
if (MomCPU
!= MatchCPU
)
{
const tCPUDef
*pCPUDef
;
ErrorNum
= GetDefaultCPUErrorNum
(ErrorNum
);
pCPUDef
= LookupCPUDefByVar
(MatchCPU
);
if (pCPUDef
)
{
char Str
[100];
as_snprintf
(Str
, sizeof(Str
), "%s%s%s", getmessage
(Num_ErrMsgOnlyCPUSupported1
), pCPUDef
->Name
, getmessage
(Num_ErrMsgOnlyCPUSupported2
));
WrXError
(ErrorNum
, Str
);
}
else
WrError
(ErrorNum
);
return False
;
}
return True
;
}
/*!------------------------------------------------------------------------
* \fn ChkExcludeCPUExt(CPUVar MatchCPU, tErrorNum ErrorNum)
* \brief check whether currently selected CPU is NOT given one and issue error if not
* \param MatchCPU disallowed CPU
* \param ErrorNum error to issue if not OK (0 = default message)
* \return TRUE if currently selected CPU is OK and no error
* ------------------------------------------------------------------------ */
typedef struct
{
const tCPUDef
*pExcludeCPUDef
;
const tCPUDef
*pLastCPUDef
;
String Str
;
Boolean First
;
Word ExcludeMask
;
CPUVar ExcludeCPUFirst
;
} tExcludeContext
;
static void IterateExclude
(const tCPUDef
*pThisCPUDef
, void *pUser
)
{
tExcludeContext
*pContext
= (tExcludeContext
*)pUser
;
/* ignore other families or aliases */
if (pThisCPUDef
)
{
if ((pThisCPUDef
->SwitchProc
!= pContext
->pExcludeCPUDef
->SwitchProc
)
|| ((1 << (pThisCPUDef
->Number
- pContext
->ExcludeCPUFirst
)) & pContext
->ExcludeMask
)
|| (pThisCPUDef
->Number
!= pThisCPUDef
->Orig
))
return;
}
if (pContext
->pLastCPUDef
)
{
if (!pContext
->First
)
strmaxcat
(pContext
->Str
, pThisCPUDef
? ", " : getmessage
(Num_ErrMsgOnlyCPUSupportedOr
), sizeof(pContext
->Str
));
strmaxcat
(pContext
->Str
, pContext
->pLastCPUDef
->Name
, sizeof(pContext
->Str
));
pContext
->First
= False
;
}
pContext
->pLastCPUDef
= pThisCPUDef
;
}
extern Boolean ChkExcludeCPUExt
(CPUVar MatchCPU
, tErrorNum ErrorNum
)
{
tExcludeContext Context
;
if (MomCPU
!= MatchCPU
)
return True
;
Context.
pExcludeCPUDef = LookupCPUDefByVar
(MatchCPU
);
if (Context.
pExcludeCPUDef)
{
*Context.
Str = '\0';
Context.
First = True
;
Context.
pLastCPUDef = NULL
;
Context.
ExcludeMask = 1;
Context.
ExcludeCPUFirst = MatchCPU
;
strmaxcat
(Context.
Str, getmessage
(Num_ErrMsgOnlyCPUSupported1
), sizeof(Context.
Str));
IterateCPUList
(IterateExclude
, &Context
);
IterateExclude
(NULL
, &Context
);
WrXError
(GetDefaultCPUErrorNum
(ErrorNum
), Context.
Str);
}
else
WrError
(GetDefaultCPUErrorNum
(ErrorNum
));
return False
;
}
/*!------------------------------------------------------------------------
* \fn ChkExcludeCPUList(int ErrorNum, ...)
* \brief check whether currently selected CPU is one of the given ones and issue error if it is
* \param ErrorNum error to issue if not OK (0 = default message)
* \param ... List of CPUs terminated by CPUNone
* \return Index (-1...-n) of matching CPU or 0 if current CPU does not match any
* ------------------------------------------------------------------------ */
int ChkExcludeCPUList
(int ErrorNum
, ...
)
{
va_list ap
;
int Index
= -1, FoundIndex
= 0;
CPUVar ThisCPU
;
va_start(ap
, ErrorNum
);
while (True
)
{
ThisCPU
= va_arg(ap
, CPUVar
);
if (ThisCPU
== CPUNone
)
break;
if (MomCPU
== ThisCPU
)
{
FoundIndex
= Index
;
break;
}
}
va_end(ap
);
if (FoundIndex
< 0)
{
tExcludeContext Context
;
*Context.
Str = '\0';
Context.
First = True
;
Context.
pExcludeCPUDef =
Context.
pLastCPUDef = NULL
;
strmaxcat
(Context.
Str, getmessage
(Num_ErrMsgOnlyCPUSupported1
), sizeof(Context.
Str));
/* convert vararg list to bitmap */
Context.
ExcludeMask = 0;
Context.
ExcludeCPUFirst = CPUNone
;
va_start(ap
, ErrorNum
);
while (TRUE
)
{
ThisCPU
= va_arg(ap
, CPUVar
);
if (ThisCPU
== CPUNone
)
break;
if (!Context.
pExcludeCPUDef)
Context.
pExcludeCPUDef = LookupCPUDefByVar
(ThisCPU
);
if (Context.
ExcludeCPUFirst == CPUNone
)
{
Context.
ExcludeCPUFirst = ThisCPU
;
Context.
ExcludeMask = 1;
}
else if (ThisCPU
> Context.
ExcludeCPUFirst)
Context.
ExcludeMask |= 1 << (ThisCPU
- Context.
ExcludeCPUFirst);
else if (ThisCPU
< Context.
ExcludeCPUFirst)
{
Context.
ExcludeMask <<= Context.
ExcludeCPUFirst - ThisCPU
;
Context.
ExcludeMask |= 1;
Context.
ExcludeCPUFirst = ThisCPU
;
}
}
va_end(ap
);
IterateCPUList
(IterateExclude
, &Context
);
IterateExclude
(NULL
, &Context
);
WrXError
(GetDefaultCPUErrorNum
((tErrorNum
)ErrorNum
), Context.
Str);
}
return FoundIndex
;
}
/*!------------------------------------------------------------------------
* \fn ChkExactCPUList(int ErrorNum)
* \brief check whether currently selected CPU is one of the given ones and issue error if not
* \param ErrorNum error to issue if not OK (0 = default message)
* \param ... List of CPUs terminated by CPUNone
* \return Index (0...) of matching CPU or -1 if current CPU does not match
* ------------------------------------------------------------------------ */
extern int ChkExactCPUList
(int ErrorNum
, ...
)
{
va_list ap
;
String Str
;
CPUVar ThisCPU
, NextCPU
;
const tCPUDef
*pCPUDef
;
Boolean First
= True
;
int FoundIndex
= 0;
va_start(ap
, ErrorNum
);
while (True
)
{
ThisCPU
= va_arg(ap
, CPUVar
);
if ((ThisCPU
== CPUNone
) || (MomCPU
== ThisCPU
))
break;
FoundIndex
++;
}
va_end(ap
);
if (ThisCPU
!= CPUNone
)
return FoundIndex
;
va_start(ap
, ErrorNum
);
*Str
= '\0';
strmaxcat
(Str
, getmessage
(Num_ErrMsgOnlyCPUSupported1
), sizeof(Str
));
ThisCPU
= CPUNone
;
while (True
)
{
NextCPU
= va_arg(ap
, CPUVar
);
pCPUDef
= (ThisCPU
!= CPUNone
) ? LookupCPUDefByVar
(ThisCPU
) : NULL
;
if (pCPUDef
)
{
if (!First
)
strmaxcat
(Str
, (NextCPU
== CPUNone
) ? getmessage
(Num_ErrMsgOnlyCPUSupportedOr
) : ", ", sizeof(Str
));
strmaxcat
(Str
, pCPUDef
->Name
, sizeof(Str
));
First
= False
;
}
if (NextCPU
== CPUNone
)
break;
ThisCPU
= NextCPU
;
}
va_end(ap
);
strmaxcat
(Str
, getmessage
(Num_ErrMsgOnlyCPUSupported2
), sizeof(Str
));
WrXError
(GetDefaultCPUErrorNum
((tErrorNum
)ErrorNum
), Str
);
return -1;
}
/*!------------------------------------------------------------------------
* \fn ChkExactCPUMaskExt(Word CPUMask, CPUVar FirstCPU, tErrorNum ErrorNum)
* \brief check whether currently selected CPU is one of the given ones and issue error if not
* \param CPUMask bit mask of allowed CPUs
* \param CPUVar CPU corresponding to bit 0 in mask
* \param ErrorNum error to issue if not OK (0 = default message)
* \param ... List of CPUs terminated by CPUNone
* \return Index (0...) of matching CPU or -1 if current CPU does not match
* ------------------------------------------------------------------------ */
int ChkExactCPUMaskExt
(Word CPUMask
, CPUVar FirstCPU
, tErrorNum ErrorNum
)
{
int Bit
= MomCPU
- FirstCPU
;
String Str
;
const tCPUDef
*pCPUDef
;
Boolean First
= True
;
CPUVar ThisCPU
;
if (CPUMask
& (1 << Bit
))
return Bit
;
*Str
= '\0';
strmaxcat
(Str
, getmessage
(Num_ErrMsgOnlyCPUSupported1
), sizeof(Str
));
for (Bit
= 0, ThisCPU
= FirstCPU
; Bit
< 16; Bit
++, ThisCPU
++)
{
if (!(CPUMask
& (1 << Bit
)))
continue;
CPUMask
&= ~
(1 << Bit
);
pCPUDef
= LookupCPUDefByVar
(ThisCPU
);
if (pCPUDef
)
{
if (!First
)
strmaxcat
(Str
, CPUMask
? ", " : getmessage
(Num_ErrMsgOnlyCPUSupportedOr
), sizeof(Str
));
strmaxcat
(Str
, pCPUDef
->Name
, sizeof(Str
));
First
= False
;
}
}
strmaxcat
(Str
, getmessage
(Num_ErrMsgOnlyCPUSupported2
), sizeof(Str
));
WrXError
(ErrorNum
? ErrorNum
: ErrNum_InstructionNotSupported
, Str
);
return -1;
}
/*!------------------------------------------------------------------------
* \fn ChkSamePage(LargeWord Addr1, LargeWord Addr2, unsigned PageBits)
* \brief check whether two addresses are of same page
* \param CurrAddr, DestAddr addresses to check
* \param PageBits page size in bits
* \param DestFlags symbol flags of DestAddr
* \return TRUE if OK
* ------------------------------------------------------------------------ */
Boolean ChkSamePage
(LargeWord CurrAddr
, LargeWord DestAddr
, unsigned PageBits
, tSymbolFlags DestFlags
)
{
LargeWord Mask
= ~
((1ul
<< PageBits
) - 1);
Boolean Result
= ((CurrAddr
& Mask
) == (DestAddr
& Mask
))
|| mFirstPassUnknownOrQuestionable
(DestFlags
);
if (!Result
)
WrError
(ErrNum_JmpTargOnDiffPage
);
return Result
;
}