/* 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;
while (True)
{
if (ThisCPU == CPUNone)
break;
if (MomCPU == ThisCPU)
{
FoundIndex = Index;
break;
}
}
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;
while (TRUE)
{
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;
}
}
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;
while (True)
{
if ((ThisCPU == CPUNone) || (MomCPU == ThisCPU))
break;
FoundIndex++;
}
if (ThisCPU != CPUNone)
return FoundIndex;
*Str = '\0';
strmaxcat(Str, getmessage(Num_ErrMsgOnlyCPUSupported1), sizeof(Str));
ThisCPU = CPUNone;
while (True)
{
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;
}
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;
}