/*
 * Copyright (c) 2021-2025 Symas Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following disclaimer
 *   in the documentation and/or other materials provided with the
 *   distribution.
 * * Neither the name of the Symas Corporation nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "cobol-system.h"
#include "coretypes.h"
#include "tree.h"
#include "../../libgcobol/ec.h"
#include "../../libgcobol/common-defs.h"
#include "util.h"
#include "cbldiag.h"
#include "symbols.h"
#include "inspect.h"
#include "../../libgcobol/io.h"
#include "genapi.h"
#include "genutil.h"
#include "gengen.h"
#include "structs.h"
#include "../../libgcobol/gcobolio.h"
#include "../../libgcobol/libgcobol.h"
#include "show_parse.h"

void
set_up_on_exception_label(cbl_label_t *arithmetic_label)
  {
  if( arithmetic_label )
    {
    if( !arithmetic_label->structs.arith_error )
      {
      arithmetic_label->structs.arith_error
        = (cbl_arith_error_t *)xmalloc(sizeof(struct cbl_arith_error_t) );
      // Set up the address pairs for this clause
      gg_create_goto_pair(&arithmetic_label->structs.arith_error->over.go_to,
                          &arithmetic_label->structs.arith_error->over.label);
      gg_create_goto_pair(&arithmetic_label->structs.arith_error->into.go_to,
                          &arithmetic_label->structs.arith_error->into.label);
      gg_create_goto_pair(&arithmetic_label->structs.arith_error->bottom.go_to,
                          &arithmetic_label->structs.arith_error->bottom.label);
      }
    }
  }

void
set_up_compute_error_label(cbl_label_t *compute_label)
  {
  if( compute_label )
    {
    if( !compute_label->structs.compute_error )
      {
      compute_label->structs.compute_error
        = (cbl_compute_error_t *)
          xmalloc(sizeof(struct cbl_compute_error_t) );
      compute_label->structs.compute_error->compute_error_code
        = gg_define_int(0);
      }
    }
  }

static void
set_up_arithmetic_error_handler(cbl_label_t *error,
                                cbl_label_t *not_error)
  {
  Analyze();
  // There might, or might not, be error and/or not_error labels:
  set_up_on_exception_label(error);
  set_up_on_exception_label(not_error);
  }

static void
arithmetic_operation(size_t nC, cbl_num_result_t *C,
                      size_t nA, cbl_refer_t *A,
                      size_t nB, cbl_refer_t *B,
                      cbl_arith_format_t format,
                      cbl_label_t *error,
                      cbl_label_t *not_error,
                      tree compute_error, // Pointer to int
                      const char *operation,
                      cbl_refer_t *remainder = NULL)
  {
  Analyze();
  SHOW_PARSE
    {
    SHOW_PARSE_HEADER
    SHOW_PARSE_TEXT_AB("performing ", operation, "")
    }

  TRACE1
    {
    TRACE1_HEADER
    TRACE1_TEXT_ABC("calling ", operation, "")
    TRACE1_END
    for(size_t ii=0; ii<nA; ii++)
      {
      TRACE1_INDENT
      gg_fprintf( trace_handle,
                  1, "parameter A[%ld]: ",
                  build_int_cst_type(SIZE_T, ii));
      TRACE1_REFER("", A[ii], "");
      }
    for(size_t ii=0; ii<nB; ii++)
      {
      TRACE1_INDENT
      gg_fprintf( trace_handle,
                  1, "parameter B[%ld]: ",
                  build_int_cst_type(SIZE_T, ii));
      TRACE1_REFER("", B[ii], "");
      }
    TRACE1_END
    }

  // We need to split up cbl_num_result_t into two arrays, one for the refer_t
  // and a second for the cbl_round_t enums.

  // Allocate nC+1 in case this is a divide with a REMAINDER

  cbl_refer_t *results = (cbl_refer_t *)xmalloc((nC+1) * sizeof( cbl_refer_t ));
  int ncount = 0;

  if( nC+1 <= MIN_FIELD_BLOCK_SIZE )
    {
    // We know there is room in our existing buffer
    }
  else
    {
    // We might have to allocate more space:
    gg_call(VOID,
            "__gg__resize_int_p",
            gg_get_address_of(var_decl_arithmetic_rounds_size),
            gg_get_address_of(var_decl_arithmetic_rounds),
            build_int_cst_type(SIZE_T, nC+1),
            NULL_TREE);
    }

  // We have to take into account the possibility the quotient of the division
  // can affect the disposition of the remainder.  In particular, some of the
  // NIST tests have the construction

  // DIVIDE A BY B GIVING C REMAINDER TABLE(C)

  // Which seems, somehow, unnatural.

  cbl_refer_t temp_remainder;
  cbl_field_t temp_field = {};

  if( remainder )
    {
    // We need a duplicate of the remainder, because we have to take into count
    // the possibility of a size error in moving the remainder into place
    temp_field.type = remainder->field->type;
    temp_field.attr = (remainder->field->attr | intermediate_e) & ~initialized_e;
    temp_field.level = 1;
    temp_field.data.memsize   = remainder->field->data.memsize ;
    temp_field.data.capacity  = remainder->field->data.capacity;
    temp_field.data.digits    = remainder->field->data.digits  ;
    temp_field.data.rdigits   = remainder->field->data.rdigits ;
    temp_field.data.initial   = remainder->field->data.initial ;
    temp_field.data.picture   = remainder->field->data.picture ;
    parser_symbol_add(&temp_field);
    temp_remainder.field = &temp_field;

    // For division, the optional remainder goes onto the beginning of the
    // list
    results[ncount++] = temp_remainder;
    }
  for(size_t i=0; i<nC; i++)
    {
    results[ncount] = C[i].refer;
    gg_assign(  gg_array_value(var_decl_arithmetic_rounds, ncount),
                build_int_cst_type(INT, C[i].rounded));
    ncount += 1;
    }

  // REMAINDER_PRESENT means what it says.
  // ON_SIZE_ERROR means that the ON SIZE ERROR phrase is present

  int call_flags =   (( error || not_error ) ? ON_SIZE_ERROR : 0)
                   + (remainder ? REMAINDER_PRESENT : 0);

  gcc_assert(compute_error);

  // Having done all that work, we now need to break out the various different
  // arithmetic routines that implement the various possibilities,

  build_array_of_treeplets(1, nA, A);
  build_array_of_treeplets(2, nB, B);
  build_array_of_treeplets(3, ncount, results);

  gg_call(VOID,
          operation,
          build_int_cst_type(INT, format),
          build_int_cst_type(SIZE_T, nA),
          build_int_cst_type(SIZE_T, nB),
          build_int_cst_type(SIZE_T, ncount),
          var_decl_arithmetic_rounds,
          build_int_cst_type(INT, call_flags),
          compute_error,
          NULL_TREE);
  TRACE1
    {
    for(size_t ii=0; ii<nC; ii++)
      {
      break;  // Breaks on ADD 1 SUB2 GIVING SUB4 both PIC S9(3) COMP
      TRACE1_INDENT
      gg_fprintf( trace_handle,
                  1, "result: C[%ld]: ",
                  build_int_cst_type(SIZE_T, ii));
      TRACE1_REFER("", C[ii].refer, "");
      }
    TRACE1_END
    }

  // We just did an operation.
  IF( gg_indirect(compute_error), ne_op, integer_zero_node )
    {
    gg_call(  VOID,
              "__gg__process_compute_error",
              gg_indirect(compute_error),
              NULL_TREE);
    }
  ELSE
    ENDIF

  if( remainder )
    {
    parser_move(*remainder, temp_remainder);
    }

  SHOW_PARSE
    {
    SHOW_PARSE_END
    }

  // We need to release all of the refers we allocated:
  free(results);
  }

static void
arithmetic_error_handler( cbl_label_t *error,
                          cbl_label_t *not_error,
                          tree compute_error) // Pointer to int with bits
  {
  Analyze();
  if( error )
    {
    // We had an ON SIZE ERROR phrase
    IF( gg_indirect(compute_error), ne_op, integer_zero_node)
      {
      // The ON SIZE ERROR imperative takes precedence over exception processing
      // So, we set the global exception code to zero.  This leaves intact the
      // stashed data needed for FUNCTION EXCEPTION-STATUS, but will preclude
      // any declarative processing
      gg_assign(var_decl_exception_code, integer_zero_node);

      // There was some kind of error, so we execute the ON SIZE ERROR
      // imperative
      gg_append_statement( error->structs.arith_error->into.go_to );
      }
    ELSE
      ENDIF
    }

  if( not_error )
    {
    IF( gg_indirect(compute_error), eq_op, integer_zero_node)
      {
      // There wasn't a computation error
      gg_append_statement( not_error->structs.arith_error->into.go_to );
      }
    ELSE
    ENDIF
    }

  // With the operation and the two possible GO TOs laid down, it's time
  // to create the target labels for exiting the ON [NOT] SIZE ERROR blocks:
  if( error )
    {
    gg_append_statement( error->structs.arith_error->bottom.label );
    }
  if( not_error )
    {
    gg_append_statement( not_error->structs.arith_error->bottom.label );
    }
  }

static bool
is_somebody_float(size_t nA, cbl_refer_t *A)
  {
  bool retval = false;
  for(size_t i=0; i<nA; i++)
    {
    if(A[i].field->type == FldFloat)
      {
      retval = true;
      break;
      }
    }
  return retval;
  }

static bool
is_somebody_float(size_t nC, cbl_num_result_t *C)
  {
  bool retval = false;
  for(size_t i=0; i<nC; i++)
    {
    if(C[i].refer.field->type == FldFloat)
      {
      retval = true;
      break;
      }
    }
  return retval;
  }

static bool
all_results_binary(size_t nC, cbl_num_result_t *C)
  {
  bool retval = true;

  for(size_t i=0; i<nC; i++)
    {
    if(C[i].refer.field->data.digits != 0 || C[i].refer.field->type == FldFloat )
      {
      retval = false;
      break;
      }
    }
  return retval;
  }

static tree
largest_binary_term(size_t nA, cbl_refer_t *A)
  {
  tree retval = NULL_TREE;
  uint32_t max_capacity = 0;
  int      is_negative  = 0;

  for(size_t i=0; i<nA; i++)
    {
    if( A[i].field->data.rdigits || A[i].field->type == FldFloat )
      {
      // We are prepared to work only with integers
      retval = NULL_TREE;
      break;
      }
    if(    A[i].field->type == FldLiteralN
//        || A[i].field->type == FldNumericDisplay
        || A[i].field->type == FldNumericBinary
        || A[i].field->type == FldNumericBin5
        || A[i].field->type == FldIndex
        || A[i].field->type == FldPointer  )
      {
      // This is an integer type that can be worked with quickly
      is_negative |= ( A[i].field->attr & signable_e );
      max_capacity = std::max(max_capacity, A[i].field->data.capacity);
      retval = tree_type_from_size(max_capacity, is_negative);
      }
    else
      {
      // This is a type we don't care to deal with for fast arithmetic
      retval = NULL_TREE;
      break;
      }
    }
  return retval;
  }

static bool
fast_add( size_t nC, cbl_num_result_t *C,
          size_t nA, cbl_refer_t *A,
          cbl_arith_format_t format )
  {
  bool retval = false;
  if( all_results_binary(nC, C) )
    {
    Analyze();
    // All targets are non-PICTURE binaries:
    //gg_insert_into_assembler("# DUBNER addition START");
    tree term_type = largest_binary_term(nA, A);
    if( term_type )
      {
      // All the terms are things we can work with.

      // We need to calculate the sum of all the A[] terms using term_type as
      // the intermediate type:

      tree sum     = gg_define_variable(term_type);
      tree addend  = gg_define_variable(term_type);
      get_binary_value( sum,
                        NULL,
                        A[0].field,
                        refer_offset_source(A[0]));

      // Add in the rest of them:
      for(size_t i=1; i<nA; i++)
        {
        get_binary_value( addend,
                          NULL,
                          A[i].field,
                          refer_offset_source(A[i]));
        gg_assign(sum, gg_add(sum, addend));
        }
      //gg_printf("The intermediate sum is %ld\n", gg_cast(LONG, sum), NULL_TREE);

      // We now either accumulate into C[n] or assign to C[n]:
      for(size_t i=0; i<nC; i++ )
        {
        tree dest_type = tree_type_from_size(C[i].refer.field->data.capacity, 0);
        tree dest_addr = gg_add(member(C[i].refer.field->var_decl_node, "data"),
                                refer_offset_dest(C[i].refer));
        tree ptr = gg_cast(build_pointer_type(dest_type), dest_addr);
        if( format == giving_e )
          {
          // We are assigning
          gg_assign(  gg_indirect(ptr),
                      gg_cast(dest_type, sum));
          }
        else
          {
          // We are accumulating
          gg_assign(  gg_indirect(ptr),
                      gg_add( gg_indirect(ptr),
                              gg_cast(dest_type, sum)));
          }
        }
      retval = true;
      }

    //gg_insert_into_assembler("# DUBNER addition END ");
    }
  return retval;
  }

static bool
fast_subtract(size_t nC, cbl_num_result_t *C,
              size_t nA, cbl_refer_t *A,
              size_t nB, cbl_refer_t *B,
              cbl_arith_format_t format)
  {
  bool retval = false;
  if( all_results_binary(nC, C) )
    {
    Analyze();
    // All targets are non-PICTURE binaries:
    //gg_insert_into_assembler("# DUBNER addition START");
    tree term_type = largest_binary_term(nA, A);

    if( term_type && format == giving_e )
      {
      tree term_type_B = largest_binary_term(nB, B);
      if( term_type_B )
        {
        if(TREE_INT_CST_LOW(TYPE_SIZE(term_type_B))
                                    > TREE_INT_CST_LOW(TYPE_SIZE(term_type)) )
          {
          term_type = term_type_B;
          }
        }
      else
        {
        term_type = NULL_TREE;
        }
      }

    if( term_type )
      {
      // All the terms are things we can work with.

      // We need to calculate the sum of all the A[] terms using term_type as
      // the intermediate type:

      tree sum     = gg_define_variable(term_type);
      tree addend  = gg_define_variable(term_type);
      get_binary_value(sum, NULL, A[0].field, refer_offset_dest(A[0]));

      // Add in the rest of them:
      for(size_t i=1; i<nA; i++)
        {
        get_binary_value(sum, NULL, A[i].field, refer_offset_dest(A[i]));
        gg_assign(sum, gg_add(sum, addend));
        }
      //gg_printf("The intermediate sum is %ld\n", gg_cast(LONG, sum), NULL_TREE);

      if( format == giving_e )
        {
        // We now subtract the sum from B[0]
        get_binary_value(addend, NULL, B[0].field, refer_offset_dest(B[0]));
        gg_assign(sum, gg_subtract(addend, sum));
        }

      // We now either accumulate into C[n] or assign to C[n]:
      for(size_t i=0; i<nC; i++ )
        {
        tree dest_type = tree_type_from_size(C[i].refer.field->data.capacity, 0);
        tree dest_addr = gg_add(member(C[i].refer.field->var_decl_node, "data"),
                                refer_offset_dest(C[i].refer));
        tree ptr = gg_cast(build_pointer_type(dest_type), dest_addr);
        if( format == giving_e )
          {
          // We are assigning
          gg_assign(  gg_indirect(ptr),
                      gg_cast(dest_type, sum));
          }
        else
          {
          // We are subtracting the sum from C[i]
          gg_assign(  gg_indirect(ptr),
                      gg_subtract(gg_indirect(ptr),
                                  gg_cast(dest_type, sum)));
          }
        }
      retval = true;
      }
    }
  return retval;
  }

static bool
fast_multiply(size_t nC, cbl_num_result_t *C,
              size_t nA, cbl_refer_t *A,
              size_t nB, cbl_refer_t *B)
  {
  bool retval = false;
  if( all_results_binary(nC, C) )
    {
    Analyze();
    // All targets are non-PICTURE binaries:
    //gg_insert_into_assembler("# DUBNER addition START");
    tree term_type = largest_binary_term(nA, A);

    if( term_type && nB )
      {
      tree term_type_B = largest_binary_term(nB, B);
      if( term_type_B )
        {
        if(TREE_INT_CST_LOW(TYPE_SIZE(term_type_B))
                                    > TREE_INT_CST_LOW(TYPE_SIZE(term_type)) )
          {
          term_type = term_type_B;
          }
        }
      else
        {
        term_type = NULL_TREE;
        }
      }

    if( term_type )
      {
      // All the terms are things we can work with.

      tree valA    = gg_define_variable(term_type);
      tree valB    = gg_define_variable(term_type);
      get_binary_value(valA, NULL, A[0].field, refer_offset_dest(A[0]));

      if( nB )
        {
        // This is a MULTIPLY Format 2
        get_binary_value(valB, NULL, B[0].field, refer_offset_dest(B[0]));
        }

      if(nB)
        {
        gg_assign(valA, gg_multiply(valA, valB));
        }

      // We now either multiply into C[n] or assign A * B to C[n]:
      for(size_t i=0; i<nC; i++ )
        {
        tree dest_type = tree_type_from_size(C[i].refer.field->data.capacity, 0);
        tree dest_addr = gg_add(member(C[i].refer.field->var_decl_node, "data"),
                                refer_offset_dest(C[i].refer));
        tree ptr = gg_cast(build_pointer_type(dest_type), dest_addr);
        if( nB )
          {
          // We put A * B into C
          gg_assign(gg_indirect(ptr), gg_cast(dest_type, valA));
          }
        else
          {
          // We multiply C = valA * C
          gg_assign(gg_indirect(ptr),
                    gg_multiply(gg_indirect(ptr), valA));
          }
        }
      retval = true;
      }

    //gg_insert_into_assembler("# DUBNER addition END ");
    }
  return retval;
  }

static bool
fast_divide(size_t nC, cbl_num_result_t *C,
            size_t nA, cbl_refer_t *A,
            size_t nB, cbl_refer_t *B,
            cbl_refer_t             remainder)
  {
  bool retval = false;
  if( all_results_binary(nC, C) )
    {
    Analyze();
    // All targets are non-PICTURE binaries:
    //gg_insert_into_assembler("# DUBNER addition START");
    tree term_type = largest_binary_term(nA, A);

    if( term_type && nB )
      {
      tree term_type_B = largest_binary_term(nB, B);
      if( term_type_B )
        {
        if(TREE_INT_CST_LOW(TYPE_SIZE(term_type_B))
                                    > TREE_INT_CST_LOW(TYPE_SIZE(term_type)) )
          {
          term_type = term_type_B;
          }
        }
      else
        {
        term_type = NULL_TREE;
        }
      }

    if( term_type )
      {
      // All the terms are things we can work with.

      tree divisor  = gg_define_variable(term_type);
      tree dividend = gg_define_variable(term_type);
      tree quotient = NULL_TREE;
      get_binary_value(divisor, NULL, A[0].field, refer_offset_dest(A[0]));

      if( nB )
        {
        // This is a MULTIPLY Format 2, where we are dividing A into B and
        // assigning that to C
        get_binary_value(dividend, NULL, B[0].field, refer_offset_dest(B[0]));

        quotient = gg_define_variable(term_type);
        // Yes, in this case the divisor and dividend are switched.  Things are
        // tough all over.
        gg_assign(quotient, gg_divide(divisor, dividend));
        }

      // We now either divide into C[n] or assign dividend/divisor to C[n]:
      for(size_t i=0; i<nC; i++ )
        {
        tree dest_type = tree_type_from_size(C[i].refer.field->data.capacity, 0);
        tree dest_addr = gg_add(member(C[i].refer.field->var_decl_node, "data"),
                                refer_offset_dest(C[i].refer));
        tree ptr = gg_cast(build_pointer_type(dest_type), dest_addr);
        if( nB )
          {
          // We put A * B into C
          gg_assign(gg_indirect(ptr), gg_cast(dest_type, quotient));
          }
        else
          {
          // We divide the divisor into C
          gg_assign(gg_indirect(ptr),
                    gg_divide(gg_indirect(ptr), divisor));
          }

        // This is where we handle any remainder, keeping in mind that for
        // nB != 0, the actual dividend is in the value we have named "divisor".
        //
        // And, yes, I hate comments like that, too.

        // We calculate the remainder by calculating
        //    dividend minus quotient * divisor
        if( remainder.field )
          {
          tree dest_addr = gg_add(member(remainder.field->var_decl_node, "data"),
                                  refer_offset_dest(remainder));
          dest_type = tree_type_from_size(remainder.field->data.capacity, 0);
          ptr = gg_cast(build_pointer_type(dest_type), dest_addr);

          gg_assign(gg_indirect(ptr),
                    gg_cast(dest_type, gg_subtract(divisor,
                                       gg_multiply(quotient, dividend))));
          }
        }
      retval = true;
      }

    //gg_insert_into_assembler("# DUBNER addition END ");
    }
  return retval;
  }

void
parser_add( size_t nC, cbl_num_result_t *C,
            size_t nA, cbl_refer_t *A,
            cbl_arith_format_t format,
            cbl_label_t *error,
            cbl_label_t *not_error,
            void        *compute_error_p ) // Cast this to a tree / int *
  {
  Analyze();
  SHOW_PARSE
    {
    SHOW_PARSE_HEADER
    fprintf(stderr, " A[%ld]:", nA);
    for(size_t i=0; i<nA; i++)
      {
      if(i > 0)
        {
        fprintf(stderr, ",");
        }
      fprintf(stderr, "%s", A[i].field->name);
      }

    fprintf(stderr, "%s", format==giving_e? " GIVING" : "");

    fprintf(stderr, " C[%ld]:", nC);
    for(size_t i=0; i<nC; i++)
      {
      if(i > 0)
        {
        fprintf(stderr, ",");
        }
      fprintf(stderr, "%s", C[i].refer.field->name);
      }

    SHOW_PARSE_END
    }

  TRACE1
    {
    TRACE1_HEADER
    TRACE1_END
    }

  tree compute_error = (tree)compute_error_p;
  if( compute_error == NULL )
    {
    gg_assign(var_decl_default_compute_error, integer_zero_node);
    compute_error = gg_get_address_of(var_decl_default_compute_error);
    }
  bool handled = false;

  if( fast_add( nC, C,
                nA, A,
                format) )
    {
    handled = true;
    }
  else
    {
    bool computation_is_float =    is_somebody_float(nA, A)
                                || is_somebody_float(nC, C);
    // We now start deciding which arithmetic routine we are going to use:
    if( computation_is_float )
      {
      switch( format )
        {
        case no_giving_e:
          {
          // Float format 1

          set_up_arithmetic_error_handler(error,
                                          not_error);
          // Do phase 1, which calculates the subtotal and puts it into a
          // temporary location
          arithmetic_operation( 0, NULL,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__add_float_phase1");

          // Do phase 2, which accumulates the subtotal into each target location in turn
          for(size_t i=0; i<nC; i++)
            {
            arithmetic_operation(1, &C[i],
                                  0, NULL,
                                  0, NULL,
                                  format,
                                  error,
                                  not_error,
                                  compute_error,
                                  "__gg__addf1_float_phase2");
            }
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case giving_e:
          {
          // Float format 2
          set_up_arithmetic_error_handler(error,
                                          not_error);
          // Do phase 1, which calculates the subtotal and puts it into a
          // temporary location
          arithmetic_operation( 0, NULL,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__add_float_phase1");

          // Do phase 2, which puts the subtotal into each target location in turn
          for(size_t i=0; i<nC; i++)
            {
            arithmetic_operation(1, &C[i],
                                  0, NULL,
                                  0, NULL,
                                  format,
                                  error,
                                  not_error,
                                  compute_error,
                                  "__gg__float_phase2_assign_to_c");
            }
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case corresponding_e:
          {
          // Float format 3
          gcc_assert(nA == nC);

          set_up_arithmetic_error_handler(error,
                                          not_error);
          arithmetic_operation(nC, C,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__addf3");
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case not_expected_e:
          gcc_unreachable();
          break;
        }
      }
    else
      {
      switch( format )
        {
        case no_giving_e:
          {
          // Fixed format 1

          set_up_arithmetic_error_handler(error,
                                          not_error);
          // Do phase 1, which calculates the subtotal and puts it into a
          // temporary location
          arithmetic_operation( 0, NULL,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__add_fixed_phase1");

          // Do phase 2, which accumulates the subtotal into each target location in turn
          for(size_t i=0; i<nC; i++)
            {
            arithmetic_operation(1, &C[i],
                                  0, NULL,
                                  0, NULL,
                                  format,
                                  error,
                                  not_error,
                                  compute_error,
                                  "__gg__addf1_fixed_phase2");
            }
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case giving_e:
          {
          // Fixed format 2

          set_up_arithmetic_error_handler(error,
                                          not_error);
          // Do phase 1, which calculates the subtotal and puts it into a
          // temporary location
          arithmetic_operation( 0, NULL,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__add_fixed_phase1");

          // Do phase 2, which puts the subtotal into each target location in turn
          for(size_t i=0; i<nC; i++)
            {
            arithmetic_operation(1, &C[i],
                                  0, NULL,
                                  0, NULL,
                                  format,
                                  error,
                                  not_error,
                                  compute_error,
                                  "__gg__fixed_phase2_assign_to_c");
            }
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case corresponding_e:
          {
          // Fixed format 3
          gcc_assert(nA == nC);

          set_up_arithmetic_error_handler(error,
                                          not_error);
          arithmetic_operation(nC, C,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__addf3");
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case not_expected_e:
          gcc_unreachable();
          break;
        }
      }
    }

  assert( handled );
  }

void
parser_add( cbl_refer_t cref,
            cbl_refer_t aref,
            cbl_refer_t bref,
            cbl_round_t rounded)
  {
  // This is the simple and innocent C = A + B
  cbl_num_result_t C[1];
  C[0].rounded = rounded;
  C[0].refer = cref;

  cbl_refer_t A[2];
  A[0] = aref;
  A[1] = bref;

  parser_add( 1, C,
              2, A,
              giving_e,
              NULL,
              NULL );
  }

void
parser_multiply(size_t nC, cbl_num_result_t *C,
                size_t nA, cbl_refer_t *A,
                size_t nB, cbl_refer_t *B,
                cbl_label_t *error,
                cbl_label_t *not_error,
                void *compute_error_p ) // This is a pointer to an int
  {
  Analyze();
  SHOW_PARSE
    {
    SHOW_PARSE_HEADER
    SHOW_PARSE_END
    }

  if( fast_multiply(nC, C,
                    nA, A,
                    nB, B) )
    {

    }
  else
    {
    tree compute_error = (tree)compute_error_p;

    if( compute_error == NULL )
      {
      gg_assign(var_decl_default_compute_error, integer_zero_node);
      compute_error = gg_get_address_of(var_decl_default_compute_error);
      }

    if( nB == 0 )
      {
      // This is a FORMAT 1 multiply

      set_up_arithmetic_error_handler(error,
                                      not_error);
      // Phase 1 just converts identifier 1 to its intermediate form
      arithmetic_operation( 0, NULL,
                            nA, A,
                            0, NULL,
                            not_expected_e,
                            error,
                            not_error,
                            compute_error,
                            "__gg__multiplyf1_phase1");

      // Phase2 multiplies the intermediate by each destination in turn
      for(size_t i=0; i<nC; i++)
        {
        arithmetic_operation( 1, &C[i],
                              0, NULL,
                              0, NULL,
                              not_expected_e,
                              error,
                              not_error,
                              compute_error,
                              "__gg__multiplyf1_phase2");
        }
      arithmetic_error_handler( error,
                                not_error,
                                compute_error);

      }
    else
      {
      // This is a FORMAT 2 multiply
      set_up_arithmetic_error_handler(error,
                                      not_error);
      arithmetic_operation( nC, C,
                            nA, A,
                            nB, B,
                            not_expected_e,
                            error,
                            not_error,
                            compute_error,
                            "__gg__multiplyf2");
      arithmetic_error_handler( error,
                                not_error,
                                compute_error);
      }
    }
  TRACE1
    {
    TRACE1_HEADER
    TRACE1_FIELD("result operand C[0]: ", C[0].refer.field, "");
    TRACE1_END
    }
  }

void
parser_divide(  size_t nC, cbl_num_result_t *C,  // C = A / B
                size_t nA, cbl_refer_t *A,
                size_t nB, cbl_refer_t *B,
                cbl_refer_t remainder,
                cbl_label_t *error,
                cbl_label_t *not_error,
                void *compute_error_p ) // This is a pointer to an int
  {
  Analyze();
  SHOW_PARSE
    {
    SHOW_PARSE_HEADER
    SHOW_PARSE_END
    }

  if( fast_divide(nC, C,
                  nA, A,
                  nB, B,
                  remainder) )
    {

    }
  else
    {
    tree compute_error = (tree)compute_error_p;

    if( compute_error == NULL )
      {
      gg_assign(var_decl_default_compute_error, integer_zero_node);
      compute_error = gg_get_address_of(var_decl_default_compute_error);
      }

    if( nB == 0 && !remainder.field )
      {
      // This is a format 1 division
      set_up_arithmetic_error_handler(error,
                                      not_error);
      arithmetic_operation(0, NULL,
                            nA, A,
                            0, NULL,
                            not_expected_e,
                            NULL,
                            NULL,
                            compute_error,
                            "__gg__multiplyf1_phase1");

      for(size_t i=0; i<nC; i++)
        {
        arithmetic_operation(1, &C[i],
                              0, NULL,
                              0, NULL,
                              not_expected_e,
                              error,
                              not_error,
                              compute_error,
                              "__gg__dividef1_phase2");
        }
      arithmetic_error_handler( error,
                                not_error,
                                compute_error);
      }

    if( nB && !remainder.field )
      {
      // This is a format 2/3 division
      set_up_arithmetic_error_handler(error,
                                      not_error);
      arithmetic_operation(nC, C,
                            1,  A,
                            1,  B,
                            not_expected_e,
                            error,
                            not_error,
                            compute_error,
                            "__gg__dividef23");

      arithmetic_error_handler( error,
                                not_error,
                                compute_error);
      }

    if( remainder.field )
      {
      // This is a format 4/5 division
      set_up_arithmetic_error_handler(error,
                                      not_error);
      arithmetic_operation(1,  C,
                            1,  A,
                            1,  B,
                            not_expected_e,
                            error,
                            not_error,
                            compute_error,
                            "__gg__dividef45",
                            &remainder);

      arithmetic_error_handler( error,
                                not_error,
                                compute_error);
      }
    }

  TRACE1
    {
    TRACE1_HEADER
    TRACE1_END
    }
  }

void
parser_multiply(cbl_refer_t cref,
                cbl_refer_t aref,
                cbl_refer_t bref,
                cbl_round_t rounded )
  {
  cbl_num_result_t C[1];
  C[0].rounded = rounded;
  C[0].refer = cref;

  cbl_refer_t A[1];
  A[0] = aref;

  cbl_refer_t B[1];
  B[0] = bref;

  parser_multiply(1, C,
                  1, B,
                  1, A,
                  NULL,
                  NULL );
  }

void
parser_divide(  cbl_refer_t cref,
                cbl_refer_t aref,
                cbl_refer_t bref,
                cbl_round_t rounded,
                cbl_refer_t remainder_ref )
  {
  cbl_num_result_t C[1];
  C[0].rounded = rounded;
  C[0].refer = cref;

  cbl_refer_t A[1];
  A[0] = aref;

  cbl_refer_t B[1];
  B[0] = bref;

  parser_divide(  1, C,
                  1, A,
                  1, B,
                  remainder_ref,
                  NULL,
                  NULL );
  }

void
parser_op( struct cbl_refer_t cref,
           struct cbl_refer_t aref,
           int op,
           struct cbl_refer_t bref,
           struct cbl_label_t *compute_error_label)
  {
  Analyze();
  set_up_compute_error_label(compute_error_label);

  gg_assign(var_decl_default_compute_error, integer_zero_node);
  tree compute_error =    compute_error_label
                        ? gg_get_address_of( compute_error_label->
                                             structs.compute_error->
                                             compute_error_code)
                        : gg_get_address_of(var_decl_default_compute_error) ;
  SHOW_PARSE
    {
    SHOW_PARSE_HEADER
    SHOW_PARSE_REF(" ", cref)
    SHOW_PARSE_TEXT(" = ")
    SHOW_PARSE_REF("", aref)
    char ach[4] = "   ";
    ach[1] = op;
    SHOW_PARSE_TEXT(ach);
    SHOW_PARSE_REF("", bref)
    SHOW_PARSE_END
    }

  // We have to do the trace in before/after mode; parser_op(a, a, op, a)
  // is a legitimate call.
  TRACE1
    {
    TRACE1_HEADER
    char ach[4] = "   ";
    ach[1] = op;
    TRACE1_TEXT_ABC("operation is \"", ach, "\"")
    TRACE1_INDENT
    TRACE1_REFER("operand A: ", aref, "")
    TRACE1_INDENT
    TRACE1_REFER("operand B: ", bref, "")
    TRACE1_INDENT
    TRACE1_TEXT_ABC("result will be ", cref.field->name, "")
    TRACE1_END
    }

  struct cbl_num_result_t for_call = {};
  for_call.rounded = truncation_e;
  for_call.refer = cref;

  switch(op)
    {
    case '+':
      {
      cbl_refer_t A[2];
      A[0] = aref;
      A[1] = bref;
      parser_add( 1, &for_call,
                  2, A,
                  giving_e,
                  NULL,
                  NULL,
                  compute_error );
      break;
      }

    case '-':
      {
      cbl_refer_t A[1];
      cbl_refer_t B[1];
      A[0] = bref;
      B[0] = aref;
      // Yes, the A-ness and B-ness are not really consistent
      parser_subtract(1, &for_call,
                      1, A,
                      1, B,
                      giving_e,
                      NULL,
                      NULL,
                      compute_error );
      break;
      }

    case '*':
      {
      cbl_refer_t A[1];
      cbl_refer_t B[1];
      A[0] = bref;
      B[0] = aref;
      parser_multiply(1, &for_call,
                      1, A,
                      1, B,
                      NULL,
                      NULL,
                      compute_error );
      break;
      }

    case '/':
      {
      cbl_refer_t A[1];
      cbl_refer_t B[1];
      A[0] = aref;
      B[0] = bref;
      parser_divide(1, &for_call,
                    1, A,
                    1, B,
                    NULL,
                    NULL,
                    NULL,
                    compute_error );
      break;
      }

    case '^':
      {
      arithmetic_operation(   1, &for_call,
                              1, &aref,
                              1, &bref,
                              no_giving_e,
                              NULL,
                              NULL,
                              compute_error,
                              "__gg__pow",
                              NULL);
      break;
      }
    default:
      cbl_internal_error( "parser_op() doesn't know how to "
             "evaluate \"%s = %s %c %s\"\n",
             cref.field->name,
             aref.field->name,
             op,
             bref.field->name);
      break;
    }
  }

void
parser_subtract(size_t nC, cbl_num_result_t *C, // C = B - A
                size_t nA, cbl_refer_t *A,
                size_t nB, cbl_refer_t *B,
                cbl_arith_format_t format,
                cbl_label_t *error,
                cbl_label_t *not_error,
                void        *compute_error_p ) // Cast this to a tree / int *
  {
  Analyze();
  SHOW_PARSE
    {
    SHOW_PARSE_HEADER
    fprintf(stderr, " A[%ld]:", nA);
    for(size_t i=0; i<nA; i++)
      {
      if(i > 0)
        {
        fprintf(stderr, ",");
        }
      fprintf(stderr, "%s", A[i].field->name);
      }

    fprintf(stderr, " B[%ld]:", nB);
    for(size_t i=0; i<nB; i++)
      {
      if(i > 0)
        {
        fprintf(stderr, ",");
        }
      fprintf(stderr, "%s", B[i].field->name);
      }

    fprintf(stderr, " C[%ld]:", nC);
    for(size_t i=0; i<nC; i++)
      {
      if(i > 0)
        {
        fprintf(stderr, ",");
        }
      fprintf(stderr, "%s", C[i].refer.field->name);
      }

    SHOW_PARSE_END
    }

  //  We are going to look for configurations that allow us to do binary
  //  arithmetic and quickly assign the results:

  //  no_giving_e is format 1; giving_e is format 2.

  bool handled = false;

  tree compute_error = (tree)compute_error_p;
  if( compute_error == NULL )
    {
    gg_assign(var_decl_default_compute_error, integer_zero_node);
    compute_error = gg_get_address_of(var_decl_default_compute_error);
    }

  if( fast_subtract(nC, C,
                    nA, A,
                    nB, B,
                    format) )
    {
    handled = true;
    }
  else
    {
    bool computation_is_float =    is_somebody_float(nA, A)
                                || is_somebody_float(nC, C);

    // We now start deciding which arithmetic routine we are going to use:

    if( computation_is_float )
      {
      switch( format )
        {
        case no_giving_e:
          {
          // Float format 1

          set_up_arithmetic_error_handler(error,
                                          not_error);
          // Do phase 1, which calculates the subtotal and puts it into a
          // temporary location
          arithmetic_operation( 0, NULL,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__add_float_phase1");

          // Do phase 2, which subtracts the subtotal from each target in turn
          for(size_t i=0; i<nC; i++)
            {
            arithmetic_operation(1, &C[i],
                                  0, NULL,
                                  0, NULL,
                                  format,
                                  error,
                                  not_error,
                                  compute_error,
                                  "__gg__subtractf1_float_phase2");
            }
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;

          break;
          }

        case giving_e:
          {
          // Float SUBTRACT Format 2

          gcc_assert(nB == 1);
          set_up_arithmetic_error_handler(error,
                                          not_error);
          // Do phase 1, which calculates the subtotal and puts it into a
          // temporary location
          arithmetic_operation( 0, NULL,
                                nA, A,
                                nB, B,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__subtractf2_float_phase1");

          // Do phase 2, which puts the subtotal into each target location in turn
          for(size_t i=0; i<nC; i++)
            {
            arithmetic_operation(1, &C[i],
                                  0, NULL,
                                  0, NULL,
                                  format,
                                  error,
                                  not_error,
                                  compute_error,
                                  "__gg__fixed_phase2_assign_to_c");
            }
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case corresponding_e:
          {
          // Float format 3
          gcc_assert(nA == nC);

          set_up_arithmetic_error_handler(error,
                                          not_error);
          arithmetic_operation(nC, C,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__subtractf3");
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;

          break;
          }

        case not_expected_e:
          gcc_unreachable();
          break;
        }
      }
    else
      {
      switch( format )
        {
        case no_giving_e:
          {
          // Fixed format 1

          set_up_arithmetic_error_handler(error,
                                          not_error);
          // Do phase 1, which calculates the subtotal and puts it into a
          // temporary location
          arithmetic_operation( 0, NULL,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__add_fixed_phase1");

          // Do phase 2, which subtracts the subtotal from each target in turn
          for(size_t i=0; i<nC; i++)
            {
            arithmetic_operation(1, &C[i],
                                  0, NULL,
                                  0, NULL,
                                  format,
                                  error,
                                  not_error,
                                  compute_error,
                                  "__gg__subtractf1_fixed_phase2");
            }
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;

          break;
          }

        case giving_e:
          {
          // Fixed SUBTRACT Format 2

          gcc_assert(nB == 1);
          set_up_arithmetic_error_handler(error,
                                          not_error);
          // Do phase 1, which calculates the subtotal and puts it into a
          // temporary location
          arithmetic_operation( 0, NULL,
                                nA, A,
                                nB, B,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__subtractf2_fixed_phase1");

          // Do phase 2, which puts the subtotal into each target location in turn
          for(size_t i=0; i<nC; i++)
            {
            arithmetic_operation( 1, &C[i],
                                  0, NULL,
                                  0, NULL,
                                  format,
                                  error,
                                  not_error,
                                  compute_error,
                                  "__gg__fixed_phase2_assign_to_c");
            }
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case corresponding_e:
          {
          // Fixed format 3
          gcc_assert(nA == nC);

          set_up_arithmetic_error_handler(error,
                                          not_error);
          arithmetic_operation(nC, C,
                                nA, A,
                                0, NULL,
                                format,
                                error,
                                not_error,
                                compute_error,
                                "__gg__subtractf3");
          arithmetic_error_handler( error,
                                    not_error,
                                    compute_error);

          handled = true;
          break;
          }

        case not_expected_e:
          gcc_unreachable();
          break;
        }
      }
    }

  if( !handled )
    {
    abort();
    }
  TRACE1
    {
    TRACE1_HEADER
    TRACE1_END
    }
  }

void
parser_subtract(cbl_refer_t cref, // cref = aref - bref
                cbl_refer_t aref,
                cbl_refer_t bref,
                cbl_round_t rounded )
  {
  cbl_num_result_t C[1];
  C[0].rounded = rounded;
  C[0].refer = cref;

  cbl_refer_t A[1];
  A[0] = aref;

  cbl_refer_t B[1];
  B[0] = bref;

  parser_subtract(1, C,   // Beware: C = A - B, but the order has changed
                  1, B,
                  1, A,
                  giving_e,
                  NULL,
                  NULL );
  }