/* cond.c - conditional assembly pseudo-ops, and .include
  Copyright (C) 1990-2024 Free Software Foundation, Inc.

  This file is part of GAS, the GNU Assembler.

  GAS 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 3, or (at your option)
  any later version.

  GAS 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 GAS; see the file COPYING.  If not, write to the Free
  Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
  02110-1301, USA.  */

#include "as.h"
#include "sb.h"
#include "macro.h"

#include "obstack.h"

/* This is allocated to grow and shrink as .ifdef/.endif pairs are
  scanned.  */
struct obstack cond_obstack;

struct file_line
{
 const char *file;
 unsigned int line;
};

/* We push one of these structures for each .if, and pop it at the
  .endif.  */

struct conditional_frame
{
 /* The source file & line number of the "if".  */
 struct file_line if_file_line;
 /* The source file & line of the "else".  */
 struct file_line else_file_line;
 /* The previous conditional.  */
 struct conditional_frame *previous_cframe;
 /* Have we seen an else yet?  */
 int else_seen;
 /* Whether we are currently ignoring input.  */
 int ignoring;
 /* Whether a conditional at a higher level is ignoring input.
    Set also when a branch of an "if .. elseif .." tree has matched
    to prevent further matches.  */
 int dead_tree;
 /* Macro nesting level at which this conditional was created.  */
 int macro_nest;
};

static void initialize_cframe (struct conditional_frame *cframe);
static char *get_mri_string (int, int *);

static struct conditional_frame *current_cframe = NULL;

/* Performs the .ifdef (test_defined == 1) and
  the .ifndef (test_defined == 0) pseudo op.  */

void
s_ifdef (int test_defined)
{
 /* Points to name of symbol.  */
 char *name;
 /* Points to symbol.  */
 symbolS *symbolP;
 struct conditional_frame cframe;
 char c;

 /* Leading whitespace is part of operand.  */
 SKIP_WHITESPACE ();
 name = input_line_pointer;

 if (!is_name_beginner (*name) && *name != '"')
   {
     as_bad (_("invalid identifier for \".ifdef\""));
     obstack_1grow (&cond_obstack, 0);
     ignore_rest_of_line ();
     return;
   }

 c = get_symbol_name (& name);
 symbolP = symbol_find (name);
 (void) restore_line_pointer (c);

 initialize_cframe (&cframe);

 if (cframe.dead_tree)
   cframe.ignoring = 1;
 else
   {
     int is_defined;

     /* Use the same definition of 'defined' as .equiv so that a symbol
        which has been referenced but not yet given a value/address is
        considered to be undefined.  */
     is_defined =
       symbolP != NULL
       && (S_IS_DEFINED (symbolP) || symbol_equated_p (symbolP))
       && S_GET_SEGMENT (symbolP) != reg_section;

     cframe.ignoring = ! (test_defined ^ is_defined);
   }

 current_cframe =
   (struct conditional_frame *) obstack_alloc (&cond_obstack, sizeof cframe);
 memcpy (current_cframe, &cframe, sizeof cframe);

 if (LISTING_SKIP_COND ()
     && cframe.ignoring
     && (cframe.previous_cframe == NULL
         || ! cframe.previous_cframe->ignoring))
   listing_list (2);

 demand_empty_rest_of_line ();
}

void
s_if (int arg)
{
 expressionS operand;
 struct conditional_frame cframe;
 int t;
 char *stop = NULL;
 char stopc = 0;

 if (flag_mri)
   stop = mri_comment_field (&stopc);

 /* Leading whitespace is part of operand.  */
 SKIP_WHITESPACE ();

 if (current_cframe != NULL && current_cframe->ignoring)
   {
     operand.X_add_number = 0;
     while (! is_end_of_line[(unsigned char) *input_line_pointer])
       ++input_line_pointer;
   }
 else
   {
     expression_and_evaluate (&operand);
     if (operand.X_op != O_constant)
       as_bad (_("non-constant expression in \".if\" statement"));
   }

 switch ((operatorT) arg)
   {
   case O_eq: t = operand.X_add_number == 0; break;
   case O_ne: t = operand.X_add_number != 0; break;
   case O_lt: t = operand.X_add_number < 0; break;
   case O_le: t = operand.X_add_number <= 0; break;
   case O_ge: t = operand.X_add_number >= 0; break;
   case O_gt: t = operand.X_add_number > 0; break;
   default:
     abort ();
     return;
   }

 /* If the above error is signaled, this will dispatch
    using an undefined result.  No big deal.  */
 initialize_cframe (&cframe);
 cframe.ignoring = cframe.dead_tree || ! t;
 current_cframe =
   (struct conditional_frame *) obstack_alloc (&cond_obstack, sizeof cframe);
 memcpy (current_cframe, & cframe, sizeof cframe);

 if (LISTING_SKIP_COND ()
     && cframe.ignoring
     && (cframe.previous_cframe == NULL
         || ! cframe.previous_cframe->ignoring))
   listing_list (2);

 if (flag_mri)
   mri_comment_end (stop, stopc);

 demand_empty_rest_of_line ();
}

/* Performs the .ifb (test_blank == 1) and
  the .ifnb (test_blank == 0) pseudo op.  */

void
s_ifb (int test_blank)
{
 struct conditional_frame cframe;

 initialize_cframe (&cframe);

 if (cframe.dead_tree)
   cframe.ignoring = 1;
 else
   {
     int is_eol;

     SKIP_WHITESPACE ();
     is_eol = is_end_of_line[(unsigned char) *input_line_pointer];
     cframe.ignoring = (test_blank == !is_eol);
   }

 current_cframe =
   (struct conditional_frame *) obstack_alloc (&cond_obstack, sizeof cframe);
 memcpy (current_cframe, &cframe, sizeof cframe);

 if (LISTING_SKIP_COND ()
     && cframe.ignoring
     && (cframe.previous_cframe == NULL
         || ! cframe.previous_cframe->ignoring))
   listing_list (2);

 ignore_rest_of_line ();
}

/* Get a string for the MRI IFC or IFNC pseudo-ops.  */

static char *
get_mri_string (int terminator, int *len)
{
 char *ret;
 char *s;

 SKIP_WHITESPACE ();
 s = ret = input_line_pointer;
 if (*input_line_pointer == '\'')
   {
     ++s;
     ++input_line_pointer;
     while (! is_end_of_line[(unsigned char) *input_line_pointer])
       {
         *s++ = *input_line_pointer++;
         if (s[-1] == '\'')
           {
             if (*input_line_pointer != '\'')
               break;
             ++input_line_pointer;
           }
       }
     SKIP_WHITESPACE ();
   }
 else
   {
     while (*input_line_pointer != terminator
            && ! is_end_of_line[(unsigned char) *input_line_pointer])
       ++input_line_pointer;
     s = input_line_pointer;
     while (s > ret && (s[-1] == ' ' || s[-1] == '\t'))
       --s;
   }

 *len = s - ret;
 return ret;
}

/* The MRI IFC and IFNC pseudo-ops.  */

void
s_ifc (int arg)
{
 char *stop = NULL;
 char stopc = 0;
 char *s1, *s2;
 int len1, len2;
 int res;
 struct conditional_frame cframe;

 if (flag_mri)
   stop = mri_comment_field (&stopc);

 s1 = get_mri_string (',', &len1);

 if (*input_line_pointer != ',')
   as_bad (_("bad format for ifc or ifnc"));
 else
   ++input_line_pointer;

 s2 = get_mri_string (';', &len2);

 res = len1 == len2 && strncmp (s1, s2, len1) == 0;

 initialize_cframe (&cframe);
 cframe.ignoring = cframe.dead_tree || ! (res ^ arg);
 current_cframe =
   (struct conditional_frame *) obstack_alloc (&cond_obstack, sizeof cframe);
 memcpy (current_cframe, &cframe, sizeof cframe);

if (LISTING_SKIP_COND ()
     && cframe.ignoring
     && (cframe.previous_cframe == NULL
         || ! cframe.previous_cframe->ignoring))
   listing_list (2);

 if (flag_mri)
   mri_comment_end (stop, stopc);

 demand_empty_rest_of_line ();
}

void
s_elseif (int arg)
{
 if (current_cframe == NULL)
   {
     as_bad (_("\".elseif\" without matching \".if\""));
   }
 else if (current_cframe->else_seen)
   {
     as_bad (_("\".elseif\" after \".else\""));
     as_bad_where (current_cframe->else_file_line.file,
                   current_cframe->else_file_line.line,
                   _("here is the previous \".else\""));
     as_bad_where (current_cframe->if_file_line.file,
                   current_cframe->if_file_line.line,
                   _("here is the previous \".if\""));
   }
 else
   {
     current_cframe->else_file_line.file
       = as_where (&current_cframe->else_file_line.line);

     current_cframe->dead_tree |= !current_cframe->ignoring;
     current_cframe->ignoring = current_cframe->dead_tree;
   }

 if (current_cframe == NULL || current_cframe->ignoring)
   {
     while (! is_end_of_line[(unsigned char) *input_line_pointer])
       ++input_line_pointer;

     if (current_cframe == NULL)
       return;
   }
 else
   {
     expressionS operand;
     int t;

     /* Leading whitespace is part of operand.  */
     SKIP_WHITESPACE ();

     expression_and_evaluate (&operand);
     if (operand.X_op != O_constant)
       as_bad (_("non-constant expression in \".elseif\" statement"));

     switch ((operatorT) arg)
       {
       case O_eq: t = operand.X_add_number == 0; break;
       case O_ne: t = operand.X_add_number != 0; break;
       case O_lt: t = operand.X_add_number < 0; break;
       case O_le: t = operand.X_add_number <= 0; break;
       case O_ge: t = operand.X_add_number >= 0; break;
       case O_gt: t = operand.X_add_number > 0; break;
       default:
         abort ();
         return;
       }

     current_cframe->ignoring = current_cframe->dead_tree || ! t;
   }

 if (LISTING_SKIP_COND ()
     && (current_cframe->previous_cframe == NULL
         || ! current_cframe->previous_cframe->ignoring))
   {
     if (! current_cframe->ignoring)
       listing_list (1);
     else
       listing_list (2);
   }

 demand_empty_rest_of_line ();
}

void
s_endif (int arg ATTRIBUTE_UNUSED)
{
 struct conditional_frame *hold;

 if (current_cframe == NULL)
   {
     as_bad (_("\".endif\" without \".if\""));
   }
 else
   {
     if (LISTING_SKIP_COND ()
         && current_cframe->ignoring
         && (current_cframe->previous_cframe == NULL
             || ! current_cframe->previous_cframe->ignoring))
       listing_list (1);

     hold = current_cframe;
     current_cframe = current_cframe->previous_cframe;
     obstack_free (&cond_obstack, hold);
   }                           /* if one pop too many */

 if (flag_mri)
   {
     while (! is_end_of_line[(unsigned char) *input_line_pointer])
       ++input_line_pointer;
   }

 demand_empty_rest_of_line ();
}

void
s_else (int arg ATTRIBUTE_UNUSED)
{
 if (current_cframe == NULL)
   {
     as_bad (_("\".else\" without matching \".if\""));
   }
 else if (current_cframe->else_seen)
   {
     as_bad (_("duplicate \".else\""));
     as_bad_where (current_cframe->else_file_line.file,
                   current_cframe->else_file_line.line,
                   _("here is the previous \".else\""));
     as_bad_where (current_cframe->if_file_line.file,
                   current_cframe->if_file_line.line,
                   _("here is the previous \".if\""));
   }
 else
   {
     current_cframe->else_file_line.file
       = as_where (&current_cframe->else_file_line.line);

     current_cframe->ignoring =
       current_cframe->dead_tree | !current_cframe->ignoring;

     if (LISTING_SKIP_COND ()
         && (current_cframe->previous_cframe == NULL
             || ! current_cframe->previous_cframe->ignoring))
       {
         if (! current_cframe->ignoring)
           listing_list (1);
         else
           listing_list (2);
       }

     current_cframe->else_seen = 1;
   }

 if (flag_mri)
   {
     while (! is_end_of_line[(unsigned char) *input_line_pointer])
       ++input_line_pointer;
   }

 demand_empty_rest_of_line ();
}

void
s_ifeqs (int arg)
{
 char *s1, *s2;
 int len1, len2;
 int res;
 struct conditional_frame cframe;

 s1 = demand_copy_C_string (&len1);

 SKIP_WHITESPACE ();
 if (*input_line_pointer != ',')
   {
     as_bad (_(".ifeqs syntax error"));
     ignore_rest_of_line ();
     return;
   }

 ++input_line_pointer;

 s2 = demand_copy_C_string (&len2);

 res = len1 == len2 && strncmp (s1, s2, len1) == 0;

 initialize_cframe (&cframe);
 cframe.ignoring = cframe.dead_tree || ! (res ^ arg);
 current_cframe =
   (struct conditional_frame *) obstack_alloc (&cond_obstack, sizeof cframe);
 memcpy (current_cframe, &cframe, sizeof cframe);

 if (LISTING_SKIP_COND ()
     && cframe.ignoring
     && (cframe.previous_cframe == NULL
         || ! cframe.previous_cframe->ignoring))
   listing_list (2);

 demand_empty_rest_of_line ();
}

int
ignore_input (void)
{
 char *s;

 s = input_line_pointer;

 if (NO_PSEUDO_DOT || flag_m68k_mri)
   {
     if (s[-1] != '.')
       --s;
   }
 else
   {
     if (s[-1] != '.')
       return (current_cframe != NULL) && (current_cframe->ignoring);
   }

 /* We cannot ignore certain pseudo ops.  */
 switch (s[0])
   {
   case 'i': case  'I':
     if (s[1] == 'f' || s[1] == 'F')
       return 0;
     break;
   case 'e': case 'E':
     if (!strncasecmp (s, "else", 4)
         || !strncasecmp (s, "endif", 5)
         || !strncasecmp (s, "endc", 4))
       return 0;
     break;
   case 'l': case 'L':
     if (!strncasecmp (s, "linefile", 8))
       return 0;
     break;
   }

 return (current_cframe != NULL) && (current_cframe->ignoring);
}

static void
initialize_cframe (struct conditional_frame *cframe)
{
 memset (cframe, 0, sizeof (*cframe));
 cframe->if_file_line.file
           = as_where (&cframe->if_file_line.line);
 cframe->previous_cframe = current_cframe;
 cframe->dead_tree = current_cframe != NULL && current_cframe->ignoring;
 cframe->macro_nest = macro_nest;
}

/* Give an error if a conditional is unterminated inside a macro or
  the assembly as a whole.  If NEST is non negative, we are being
  called because of the end of a macro expansion.  If NEST is
  negative, we are being called at the of the input files.  */

void
cond_finish_check (int nest)
{
 if (current_cframe != NULL && current_cframe->macro_nest >= nest)
   {
     if (nest >= 0)
       as_bad (_("end of macro inside conditional"));
     else
       as_bad (_("end of file inside conditional"));

     as_bad_where (current_cframe->if_file_line.file,
                   current_cframe->if_file_line.line,
                   _("here is the start of the unterminated conditional"));
     if (current_cframe->else_seen)
       as_bad_where (current_cframe->else_file_line.file,
                     current_cframe->else_file_line.line,
                     _("here is the \"else\" of the unterminated conditional"));
     cond_exit_macro (nest);
   }
}

/* This function is called when we exit out of a macro.  We assume
  that any conditionals which began within the macro are correctly
  nested, and just pop them off the stack.  */

void
cond_exit_macro (int nest)
{
 while (current_cframe != NULL && current_cframe->macro_nest >= nest)
   {
     struct conditional_frame *hold;

     hold = current_cframe;
     current_cframe = current_cframe->previous_cframe;
     obstack_free (&cond_obstack, hold);
   }
}