/* codepseudo.c */
/*****************************************************************************/
/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only                     */
/*                                                                           */
/* AS-Portierung                                                             */
/*                                                                           */
/* Haeufiger benutzte Pseudo-Befehle                                         */
/*                                                                           */
/*****************************************************************************/

/*****************************************************************************
 * Includes
 *****************************************************************************/

#include "stdinc.h"
#include <string.h>
#include <ctype.h>
#include <math.h>

#include "nls.h"
#include "bpemu.h"
#include "be_le.h"
#include "strutil.h"
#include "chunks.h"
#include "asmdef.h"
#include "asmsub.h"
#include "asmpars.h"
#include "asmallg.h"
#include "asmitree.h"
#include "errmsg.h"

#include "codepseudo.h"
#include "motpseudo.h"

/*****************************************************************************
 * Global Functions
 *****************************************************************************/

/*****************************************************************************
 * Function:    IsIndirect
 * Purpose:     check whether argument is syntactically 'indirect', i.e.
 *              enclosed in 'extra' parentheses.
 * Result:      TRUE if indirect
 *****************************************************************************/

Boolean IsIndirectGen(const char *Asc, const char *pBeginEnd)
{
  int z,Level,l;

  if (((l = strlen(Asc)) <= 2)
   || (Asc[0] != pBeginEnd[0])
   || (Asc[l - 1] != pBeginEnd[1]))
    return False;

  Level = 0;
  for (z = 1; z <= l - 2; z++)
  {
    if (Asc[z] == pBeginEnd[0]) Level++;
    if (Asc[z] == pBeginEnd[1]) Level--;
    if (Level < 0) return False;
  }

  return True;
}

Boolean IsIndirect(const char *Asc)
{
  return IsIndirectGen(Asc, "()");
}

/*!------------------------------------------------------------------------
 * \fn     FindDispBaseSplitWithQualifier(const char *pArg, int *pArgLen, tDispBaseSplitQualifier Qualifier, const char *pBracks)
 * \brief  check for argument of type xxx(yyyy)
 * \param  pArg argument to check
 * \param  pArgLen returns argument length
 * \param  Qualifier possible qualifier to allow more positive decisions
 * \param  pBracks Opening/Closing Parentheses
 * \return index to opening parenthese or -1 if not like pattern
 * ------------------------------------------------------------------------ */

typedef struct
{
  as_quoted_iterator_cb_data_t data;
  int nest, split_pos, end_pos, last_nonspace_pos;
  char last_nonspace;
  tDispBaseSplitQualifier qualifier;
  const char *p_bracks;
} disp_base_split_cb_data_t;

/* We are looking for expressions of the form xxx(yyy),
   but we want to avoid false positives on things like
   xxx+(yyy*zzz).  So we look at the (non-blank) character
   right before the opening parenthese in question.  If it is
   something that might be the last letter of an identifier,
   or another parenthized expression, and not an operator,
   it might be OK...

   We generally look for the last candidate in the string,
   i.e. we continue to search after a finding.
 */

static int disp_base_split_cb(const char *p_pos, as_quoted_iterator_cb_data_t *p_cb_data)
{
  disp_base_split_cb_data_t *p_data = (disp_base_split_cb_data_t*)p_cb_data;
  int pos = p_pos - p_cb_data->p_str;

  /* The indirect part put into parentheses must be at the end of the expression.
     So if any non-blank characters follow it, this was a mismatch: */

  if (!as_isspace(*p_pos) && (p_data->split_pos >= 0) && (p_data->end_pos >= 0))
  {
    p_data->split_pos = p_data->end_pos = -1;
  }

  if (*p_pos == p_data->p_bracks[0])
  {
    if (!p_data->nest)
    {
      if ((p_data->last_nonspace_pos < 0) || as_isalnum_ubar(p_data->last_nonspace) || (p_data->last_nonspace == ')') || (p_data->last_nonspace == '\'') || (p_data->last_nonspace == '"'))
        p_data->split_pos = pos;
      else if (p_data->qualifier)
      {
        int qual = p_data->qualifier(p_cb_data->p_str, p_data->last_nonspace_pos, pos);
        if (qual >= 0)
          p_data->split_pos = qual;
      }
    }
    p_data->nest++;
  }
  else if (*p_pos == p_data->p_bracks[1])
  {
    /* verify corresponding closing bracket is at end of expression */
    if ((1 == p_data->nest) && (p_data->split_pos >= 0))
    {
      p_data->end_pos = pos;
    }
    p_data->nest--;
  }
  if (!as_isspace(*p_pos))
  {
    p_data->last_nonspace_pos = pos;
    p_data->last_nonspace = *p_pos;
  }
  return 0;
}

int FindDispBaseSplitWithQualifier(const char *pArg, int *pArgLen, tDispBaseSplitQualifier Qualifier, const char *pBracks)
{
  disp_base_split_cb_data_t data;

  *pArgLen = strlen(pArg);

  if (!*pArgLen || (pArg[*pArgLen - 1] != pBracks[1]))
    return -1;

  data.data.callback_before = False;
  data.data.qualify_quote = QualifyQuote;
  data.nest = 0;
  data.split_pos =
  data.end_pos = -1;
  data.last_nonspace_pos = -1;
  data.last_nonspace = ' ';
  data.qualifier = Qualifier;
  data.p_bracks = pBracks;

  as_iterate_str_quoted(pArg, disp_base_split_cb, &data.data);

  /* Did not find corresponding closing parenthese? */

  if ((data.split_pos >= 0) && (data.end_pos < 0))
    data.split_pos = -1;
  return data.split_pos;
}

/*****************************************************************************
 * Function:    CodeEquate
 * Purpose:     EQU for different segment
 * Result:      -
 *****************************************************************************/

static void code_equate_core(LargeInt value, as_addrspace_t dest_seg)
{
  TempResult t;

  PushLocHandle(-1);
  EnterIntSymbol(&LabPart, value, dest_seg, False);
  PopLocHandle();
  if (MakeUseList)
    if (AddChunk(SegChunks + dest_seg, value, 1, False)) WrError(ErrNum_Overlap);

  as_tempres_ini(&t);
  as_tempres_set_int(&t, value);
  SetListLineVal(&t);
  as_tempres_free(&t);
}

void code_equate_type(as_addrspace_t dest_seg, IntType int_type)
{
  if (ChkArgCnt(1, 1))
  {
    Boolean ok;
    tSymbolFlags flags;
    LargeInt value;

    value = EvalStrIntExpressionWithFlags(&ArgStr[1], int_type, &ok, &flags);
    if (ok && !mFirstPassUnknown(flags))
      code_equate_core(value, dest_seg);
  }
}

void code_equate_range(as_addrspace_t dest_seg, LargeInt min_value, LargeInt max_value)
{
  if (ChkArgCnt(1, 1))
  {
    Boolean ok;
    tSymbolFlags flags;
    LargeInt value;

    value = EvalStrIntExpressionWithFlags(&ArgStr[1], LargeIntType, &ok, &flags);
    if (ok && !mFirstPassUnknown(flags))
    {
      if (min_value > value) WrError(ErrNum_UnderRange);
      else if (
#ifndef HAS64
        (!(max_value & 0x80000000ul)) &&   /* cannot check >=2G range if LargeInt is 32 bits */
#endif
        (value > max_value)) WrError(ErrNum_OverRange);
      else
        code_equate_core(value, dest_seg);
    }
  }
}

/*!------------------------------------------------------------------------
 * \fn     QualifyQuote_SingleQuoteConstant(const char *pStart, const char *pQuotePos)
 * \brief  check whether ' in source is lead-in to character string or int constant
 * \param  pStart complete string
 * \param  pQuotePos single quote position
 * \return True if this is NO lead-in of int constant
 * ------------------------------------------------------------------------ */

Boolean QualifyQuote_SingleQuoteConstant(const char *pStart, const char *pQuotePos)
{
  const char *pRun;
  Boolean OK;
  int Base;

  /* previous character must be H X B O */

  if (pQuotePos == pStart)
    return True;
  switch (as_toupper(*(pQuotePos - 1)))
  {
    case 'B':
      Base = 2; break;
    case 'O':
      Base = 8; break;
    case 'X':
    case 'H':
      Base = 16; break;
    default:
      return True;
  }

  /* Scan for following valid (binary/octal/hex) character(s) */

  for (pRun = pQuotePos + 1; *pRun; pRun++)
  {
    switch (Base)
    {
      case 16: OK = as_isxdigit(*pRun); break;
      case 8: OK = as_isdigit(*pRun) && (*pRun < '8'); break;
      case 2: OK = as_isdigit(*pRun) && (*pRun < '2'); break;
      default: OK = False;
    }
    if (!OK)
      break;
  }

  /* none? -> bad */

  if (pRun <= pQuotePos + 1)
    return True;

  /* If we've hit another ' after them, it is the "harmless" x'...' form,
     and no special treatment is needed */

  if ('\'' == *pRun)
    return True;

  /* Other token or string continues -> cannot be such a constant, otherwise we
     have a match and the ' does NOT lead in a character string: */

  return as_isalnum(*pRun);
}

/*!------------------------------------------------------------------------
 * \fn     string_2_xasm_code(const struct as_nonz_dynstr *p_str, int bytes_per_dword, Boolean big_endian)
 * \brief  put characters from string into xx bit words of machine code - translation done outside!
 * \param  p_str source string
 * \param  bytes_per_dword # of characters in a word
 * \param  big_endian fill words starting with MSB?
 * \return 0 or error code
 * ------------------------------------------------------------------------ */

#define declare_string_2_xasm_code(NAME, TYPE, VAR) \
int NAME(const struct as_nonz_dynstr *p_str, int bytes_per_dword, Boolean big_endian) \
{ \
  int byte_fill, ret; \
  const char *p_ch, *p_end; \
  TYPE character; \
 \
  for (byte_fill = 0, p_ch = p_str->p_str, p_end = p_ch + p_str->len; \
       p_ch < p_end; p_ch++) \
  { \
    if (!byte_fill) \
    { \
      ret = SetMaxCodeLen((CodeLen + 1) * sizeof(TYPE)); \
      if (ret) \
        return ret; \
      VAR[CodeLen++] = 0; \
    } \
    character = *p_ch & 0xff; \
    if (big_endian) \
      VAR[CodeLen - 1] = (VAR[CodeLen - 1] << 8) | character; \
    else \
      VAR[CodeLen - 1] |= character << (byte_fill * 8); \
    if (++byte_fill >= bytes_per_dword) \
      byte_fill = 0; \
  } \
  if (byte_fill && big_endian) \
    VAR[CodeLen - 1] <<= 8 * (bytes_per_dword - byte_fill); \
  return 0; \
}

declare_string_2_xasm_code(string_2_dasm_code, LongWord, DAsmCode)
declare_string_2_xasm_code(string_2_wasm_code, Word, WAsmCode)

/*!------------------------------------------------------------------------
 * \fn     add_null_pseudo(struct sInstTable *p_inst_table)
 * \brief  add handle to ignore empty instruction
 * \param  p_inst_table table to augment
 * ------------------------------------------------------------------------ */

void decode_null(Word code)
{
  UNUSED(code);
}

void add_null_pseudo(struct sInstTable *p_inst_table)
{
  AddInstTable(p_inst_table, "", 0, decode_null);
}
