>From 203cbbc22fd8c8ffbc29eb846d8901e4346e95f8 Mon Sep 17 00:00:00 2001
From: Robert Dubner mailto:[email protected]
Date: Thu, 5 Feb 2026 10:45:40 -0500
Subject: [PATCH] cobol: Use _perform_line_pairs instead of injecting
encoded
 label names.

The gcobol front end has been communicating with GDB-COBOL by encoding
information into labels that are injected into the assembly language
with ASM_EXPR nodes.  That behavior is, at best, questionable.

These changes replace the "proccall" and "procret" types of those labels
in favor of a static _perform_line_pairs table that contains the same
information and is accessible by GDB-COBOL by virtue of its known name.

That table allows GDB-COBOL to "NEXT over COBOL PERFORM" statements in a
way that is familiar to users who have used "NEXT over function call".

Eventually that information should find its way into the .debug_info
section, but at the present time I don't know how to do that on either
the compiler or debugger sides.

Most of these changes involve eliminating gg_insert_into_assembler calls
and replacing them with the perform_is_armed/perform_line_pairs logic.

Some COBOL variable initialization changes crept in here, as well.

gcc/cobol/ChangeLog:

        * genapi.cc (DEFAULT_LINE_NUMBER): Remove unused #define.
        (parser_statement_begin): Implement perform_is_armed logic.
        (initialize_variable_internal): Handle both real and int types in
        SHOW_PARSE tracing.
        (section_label): Comment a renumbered insert_nop() for gdb-cobol
        logic.
        (paragraph_label): Likewise.
        (leave_procedure): Eliminate call to gg_insert_into_assembler().
        (parser_enter_section): Renumber insert_nop().
        (parser_perform): Eliminate call to gg_insert_into_assembler().
        (parser_perform_times): Likewise.
        (internal_perform_through): Likewise.
        (internal_perform_through_times): Likewise.
        (parser_leave_file): Create the static _perform_line_pairs table.
        (parser_sleep): Renumber insert_nop().
        (parser_division): Remove calls to initialize_the_data().
        (parser_perform_start): New call to insert_nop().
        (parser_perform_conditional): Likewise.
        (perform_outofline_before_until): Expanded comment.
        (perform_outofline_after_until): Eliminate call to
        gg_insert_into_assembler().
        (perform_outofline_testafter_varying): Likewise.
        (perform_outofline_before_varying): Likewise.
        (perform_inline_testbefore_varying):  New call to insert_nop().
        (create_and_call): Change a comment.
        * gengen.cc (gg_create_goto_pair): Change characteristics of a
        label.
        * parse.y: Change how data are initialized.
        * parse_ante.h (field_type_update): Likewise.
        * symbols.cc (cbl_field_t::set_signable): Likewise.
        (cbl_field_t::encode): Likewise.
        * symbols.h (struct cbl_field_t): Likewise.
        * util.cc (symbol_field_type_update): Likewise.
        (cbl_field_t::encode_numeric): Likewise.

libgcobol/ChangeLog:

        * valconv.cc (__gg__string_to_numeric_edited): Explanatory
comment.
---
 gcc/cobol/genapi.cc    | 227 ++++++++++++++++++++---------------------
 gcc/cobol/gengen.cc    |   4 +
 gcc/cobol/parse.y      |  78 +++++++++-----
 gcc/cobol/parse_ante.h |  11 +-
 gcc/cobol/symbols.cc   |  47 ++++++++-
 gcc/cobol/symbols.h    |   1 +
 gcc/cobol/util.cc      |  34 ++++--
 libgcobol/valconv.cc   |   8 +-
 8 files changed, 250 insertions(+), 160 deletions(-)

diff --git a/gcc/cobol/genapi.cc b/gcc/cobol/genapi.cc
index fac689e3f67..40be939dd72 100644
--- a/gcc/cobol/genapi.cc
+++ b/gcc/cobol/genapi.cc
@@ -72,8 +72,6 @@ static tree label_list_back_label;
 static void hijack_for_development(const char *funcname);
 
 static size_t sv_data_name_counter = 1;
-static int call_counter = 1;
-static int pseudo_label = 1;
 
 static bool suppress_cobol_entry_point = false;
 static char ach_cobol_entry_point[256] = "";
@@ -84,7 +82,8 @@ int  show_parse_indent = 0;
 
 static bool sv_is_i_o = false;
 
-#define DEFAULT_LINE_NUMBER 2
+static int perform_is_armed = 0;
+static std::map<int, int> perform_line_pairs;
 
 #ifdef LINE_TICK
 /*  This code is used from time to time when sorting out why compilation
@@ -1151,6 +1150,18 @@ parser_statement_begin( const cbl_name_t
statement_name,
 
   gcc_assert( gg_trans_unit.function_stack.size() );
 
+  // If a PERFORM is armed, that's the line that the PERFORM is on.  The
+  // cobol_location().first_line here is the major statement following
the
+  // the PERFORM statement.  (We don't use .loc information in GDB
because of
+  // the difficulty in teasing out which is the "primary" .loc from the
+  // 'is_stmt' and 'discriminator'.  If that's possible, I haven't yet
figured
+  // how.)
+  if( perform_is_armed )
+    {
+    perform_line_pairs[perform_is_armed] = cobol_location().first_line;
+    perform_is_armed = 0;
+    }
+
   // In the cases where enabled_exceptions.size() is non-zero, or when
   // there is a possibility of an EC-I-O exception because this is a file
   // operation, we need to store the location information and do the
exception
@@ -1314,14 +1325,22 @@ initialize_variable_internal( cbl_refer_t refer,
           default:
             {
             char ach[128];
-            real_to_decimal (ach,
-                             TREE_REAL_CST_PTR
(parsed_var->data.value_of()),
-                             sizeof(ach), 16, 0);
+            if( TREE_CODE(TREE_TYPE(parsed_var->data.value_of())) ==
REAL_TYPE)
+              {
+              real_to_decimal (ach,
+                               TREE_REAL_CST_PTR
(parsed_var->data.value_of()),
+                               sizeof(ach), 16, 0);
+              }
+            else
+              {
+              wi::tree_to_wide_ref iii =
+                                    wi::to_wide(
parsed_var->data.value_of() );
+              print_dec(iii, ach, SIGNED);
+              }
             SHOW_PARSE_TEXT(ach);
             break;
             }
           }
-
         }
       SHOW_PARSE_TEXT("<<")
       }
@@ -2464,7 +2483,8 @@ section_label(struct cbl_proc_t *procedure)
     }
   assembler_label(psz2);
   free(psz2);
-  insert_nop(108);
+  // Needed so that GDB-COBOL can trap at a section name.
+  insert_nop(101);
   }
 
 static void
@@ -2537,7 +2557,7 @@ paragraph_label(struct cbl_proc_t *procedure)
   //
   // Yes, trying to understand this causes headaches for many people who
read
   // this.  Take an aspirin.
-  insert_nop(109);
+  insert_nop(102);
   }
 
 static void
@@ -2631,15 +2651,7 @@ leave_procedure(struct cbl_proc_t *procedure, bool
/*section*/)
     // procedure->bottom.label);
     // Procedure can be null, for example at the beginning of a
     // new program, or after somebody else has cleared it out.
-
     gg_append_statement(procedure->exit.label);
-
-    char *psz;
-    psz = xasprintf("_procret." HOST_SIZE_T_PRINT_DEC ":",
-                    (fmt_size_t)symbol_label_id(procedure->label));
-    token_location_override(current_location_minus_one());
-    gg_insert_into_assembler(psz);
-    free(psz);
     pseudo_return_pop(procedure);
     gg_append_statement(procedure->bottom.label);
     }
@@ -2817,7 +2829,7 @@ parser_enter_section(cbl_label_t *label)
 
   // This NOP is needed to give GDB a line number for the entry point of
   // paragraphs
-  insert_nop(101);
+  insert_nop(103);
 
   struct cbl_proc_t *procedure = find_procedure(label);
   gg_append_statement(procedure->top.label);
@@ -3151,11 +3163,8 @@ parser_perform(cbl_label_t *label, bool
suppress_nexting)
 
   if( !suppress_nexting )
     {
-    sprintf(ach,
-            "_proccall." HOST_SIZE_T_PRINT_DEC ".%d:",
-            (fmt_size_t)symbol_label_id(label),
-            call_counter++);
-    gg_insert_into_assembler( ach );
+    // Flag this source-code line as being a PERFORM statement.
+    perform_is_armed = CURRENT_LINE_NUMBER ;
     }
 
   // We do the indirect jump in order to prevent the compiler from
complaining
@@ -3198,12 +3207,7 @@ parser_perform_times( cbl_label_t *proc_1,
cbl_refer_t count )
     TRACE1_END
     }
 
-  char ach[256];
-  size_t our_pseudo_label = pseudo_label++;
-  sprintf(ach,
-          "_proccallb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  gg_insert_into_assembler( ach );
+  perform_is_armed = CURRENT_LINE_NUMBER ;
 
   tree counter       = gg_define_variable(LONG);
 
@@ -3221,12 +3225,6 @@ parser_perform_times( cbl_label_t *proc_1,
cbl_refer_t count )
     gg_decrement(counter);
     }
     WEND
-
-  sprintf(ach,
-          "_procretb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  token_location_override(current_location_minus_one());
-  gg_insert_into_assembler(ach);
   }
 
 static void
@@ -3303,12 +3301,7 @@ internal_perform_through( cbl_label_t *proc_1,
 
   if( !suppress_nexting )
     {
-    char ach[256];
-    sprintf(ach,
-            "_proccall." HOST_SIZE_T_PRINT_DEC ".%d:",
-            (fmt_size_t)symbol_label_id(proc_2),
-            call_counter++);
-    gg_insert_into_assembler(ach);
+    perform_is_armed = CURRENT_LINE_NUMBER ;
     }
 
   gg_append_statement(proc1->top.go_to);
@@ -3356,13 +3349,7 @@ internal_perform_through_times(   cbl_label_t
*proc_1,
     TRACE1_END
     }
 
-  size_t our_pseudo_label = pseudo_label++;
-
-  char ach[256];
-  sprintf(ach,
-          "_proccallb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  gg_insert_into_assembler( ach );
+  perform_is_armed = CURRENT_LINE_NUMBER ;
 
   tree counter       = gg_define_variable(LONG);
   get_binary_value( counter,
@@ -3375,12 +3362,6 @@ internal_perform_through_times(   cbl_label_t
*proc_1,
     gg_decrement(counter);
     }
     WEND
-
-  sprintf(ach,
-          "_procretb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  token_location_override(current_location_minus_one());
-  gg_insert_into_assembler( ach );
   }
 
 void
@@ -3573,6 +3554,41 @@ parser_leave_file()
     // We are leaving the top-level file, which means this compilation is
     // done, done, done.
 
+    // This is where we create the file-static table of PERFORM/FOLLOWING
line
+    // number pairs so that the GDB-COBOL debugger can know where to
"return"
+    // to after a NEXT is issued on a PERFORM statement.
+
+    // We need to create a file-static static array of 32-bit integers.
The
+    // array is terminated with a {0,0} pair:
+    tree array_of_int_type = build_array_type_nelts(INT,
(perform_line_pairs.size()+1)*2);
+    tree array_of_int = gg_define_variable( array_of_int_type,
+                                            "_perform_line_pairs",
+                                            vs_file_static);
+    // We have the array.  Now we need to build the constructor for it
+    tree constr = make_node(CONSTRUCTOR);
+    TREE_TYPE(constr) = array_of_int_type;
+    TREE_STATIC(constr)    = 1;
+    TREE_CONSTANT(constr)  = 1;
+
+    // The first element of the array contains the number of elements to
follow
+    size_t i = 0;
+    for(auto it : perform_line_pairs)
+      {
+      CONSTRUCTOR_APPEND_ELT( CONSTRUCTOR_ELTS(constr),
+                              build_int_cst_type(SIZE_T, i++),
+                              build_int_cst_type(INT, it.first) );
+      CONSTRUCTOR_APPEND_ELT( CONSTRUCTOR_ELTS(constr),
+                              build_int_cst_type(SIZE_T, i++),
+                              build_int_cst_type(INT, it.second) );
+      }
+    CONSTRUCTOR_APPEND_ELT( CONSTRUCTOR_ELTS(constr),
+                            build_int_cst_type(SIZE_T, i++),
+                            integer_zero_node );
+    CONSTRUCTOR_APPEND_ELT( CONSTRUCTOR_ELTS(constr),
+                            build_int_cst_type(SIZE_T, i++),
+                            integer_zero_node );
+    DECL_INITIAL(array_of_int) = constr;
+
     // There is, however, one thing left to do.  If the command line says
     // that this module needs a main entry point, then this is where
     // we create a main() function.  We build it at the end, so that all
of
@@ -6292,7 +6308,7 @@ void parser_sleep(const cbl_refer_t &seconds)
     // This is a naked place-holding CONTINUE.  Generate some do-nothing
     // code that will stick some .LOC information into the assembly
language,
     // so that GDB-COBOL can display the CONTINUE statement.
-    insert_nop(103);
+    insert_nop(104);
     }
   }
 
@@ -7312,12 +7328,10 @@ parser_division(cbl_division_t division,
   if( division == environment_div_e )
     {
     Analyze();
-    initialize_the_data();
     }
   else if( division == procedure_div_e )
     {
     Analyze();
-    initialize_the_data();
 
     // Do some symbol table index bookkeeping.  current_program_index()
is valid
     // at this point in time:
@@ -8270,7 +8284,8 @@ parser_perform_start( struct cbl_perform_tgt_t *tgt
)
   // Give GDB-COBOL something to chew on when NEXTing.  This instruction
will
   // get the line number of the PERFORM N TIMES code.
   gg_append_statement(tgt->addresses.top.label);
-  insert_nop(104);
+  // Necessary for GDB-COBOL PERFORM <inline> processing.
+  insert_nop(105);
   }
 
 void
@@ -8314,6 +8329,9 @@ parser_perform_conditional( struct cbl_perform_tgt_t
*tgt )
 
   // The next instructions that the parser will give us are the
conditional
   // calculation, so the first thing that goes down is the condover:
+  /* The following NOP is needed to make NEXT OVER PERFORM BEFORE/AFTER
UNTIL 
+     behaves properly.  */
+  insert_nop(106);
   gg_append_statement(tgt->addresses.condover[i].go_to);
 
   // And then, of course, we need to be able to jump back here to
actually
@@ -8373,17 +8391,26 @@ perform_outofline_before_until(struct
cbl_perform_tgt_t *tgt,
 
   /*
       TOP:
-                  IF CONDITION 0
-                      GOTO EXIT
-                  ELSE
-                      EXECUTE BODY
-                      GOTO TOP
+         GOTO condinto
+         condback:
+            IF CONDITION 0
+                GOTO EXIT
+            ELSE
+                EXECUTE BODY
+                GOTO TOP
       EXIT:
+
+      GOTO jumpover
+         condinto:
+         <conditional calculation>
+         GOTO condback
+      jumpover:
   */
 
   create_iline_address_pairs(tgt);
 
   // Tag the top of the perform
+  
   gg_append_statement(tgt->addresses.top.label);
 
   // Go do the conditional calculation:
@@ -8394,12 +8421,7 @@ perform_outofline_before_until(struct
cbl_perform_tgt_t *tgt,
   // where to return:
   gg_append_statement(tgt->addresses.condback[0].label);
 
-  char ach[256];
-  size_t our_pseudo_label = pseudo_label++;
-  sprintf(ach,
-          "_proccallb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  gg_insert_into_assembler( ach );
+  perform_is_armed = CURRENT_LINE_NUMBER ;
 
   parser_if(varys[0].until);
     {
@@ -8419,11 +8441,6 @@ perform_outofline_before_until(struct
cbl_perform_tgt_t *tgt,
 
   // Label the bottom of the PERFORM
   gg_append_statement(  tgt->addresses.exit.label );
-  sprintf(ach,
-          "_procretb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  token_location_override(current_location_minus_one());
-  gg_insert_into_assembler( ach );
   }
 
 static void
@@ -8441,21 +8458,23 @@ perform_outofline_after_until(struct
cbl_perform_tgt_t *tgt,
 
   /*
       TOP:
-                  EXECUTE BODY
-                  IF CONDITION 0
-                      GOTO EXIT
-                  ELSE
-                      ADD BY_0 to VARYING_0
-                      GOTO TOP
+          EXECUTE BODY
+          GOTO condinto
+          condback:
+          IF CONDITION 0
+              GOTO EXIT
+          ELSE
+              GOTO TOP
       EXIT:
+
+      GOTO jumpover
+         condinto:
+         <conditional calculation>
+         GOTO condback
+      jumpover:
   */
 
-  char ach[256];
-  size_t our_pseudo_label = pseudo_label++;
-  sprintf(ach,
-          "_proccallb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  gg_insert_into_assembler( ach );
+  perform_is_armed = CURRENT_LINE_NUMBER ;
 
   create_iline_address_pairs(tgt);
 
@@ -8483,11 +8502,6 @@ perform_outofline_after_until(struct
cbl_perform_tgt_t *tgt,
   parser_fi();
   // Label the bottom of the PERFORM
   gg_append_statement(  tgt->addresses.exit.label );
-  sprintf(ach,
-          "_procretb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  token_location_override(current_location_minus_one());
-  gg_insert_into_assembler( ach );
   }
 
 static void
@@ -8547,12 +8561,7 @@ perform_outofline_testafter_varying(struct
cbl_perform_tgt_t *tgt,
   // only need N-1; we don't use the zeroth pair.  But the code
   // is cleaner if we just build all N of them.
 
-  char ach[256];
-  size_t our_pseudo_label = pseudo_label++;
-  sprintf(ach,
-          "_proccallb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  gg_insert_into_assembler( ach );
+  perform_is_armed = CURRENT_LINE_NUMBER ;
 
   create_iline_address_pairs(tgt);
 
@@ -8604,11 +8613,6 @@ perform_outofline_testafter_varying(struct
cbl_perform_tgt_t *tgt,
     }
   // Arriving here means that we all of the conditions were
   // true.  So, we're done.
-  sprintf(ach,
-          "_procretb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  token_location_override(current_location_minus_one());
-  gg_insert_into_assembler( ach );
   }
 
 static void
@@ -8665,12 +8669,7 @@ perform_outofline_before_varying(   struct
cbl_perform_tgt_t *tgt,
   tree label[MAX_AFTERS];
   build_N_pairs(go_to, label, N);
 
-  char ach[256];
-  size_t our_pseudo_label = pseudo_label++;
-  sprintf(ach,
-          "_proccallb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  gg_insert_into_assembler( ach );
+  perform_is_armed = CURRENT_LINE_NUMBER ;
 
   // Initialize all varying:
 
@@ -8748,11 +8747,6 @@ perform_outofline_before_varying(   struct
cbl_perform_tgt_t *tgt,
   // the EXIT: label.
   // We have, you see, reached the egress:
   gg_append_statement(  tgt->addresses.exit.label );
-  sprintf(ach,
-          "_procretb." HOST_SIZE_T_PRINT_DEC ":",
-          (fmt_size_t)our_pseudo_label);
-  token_location_override(current_location_minus_one());
-  gg_insert_into_assembler( ach );
   }
 
 static void
@@ -8983,6 +8977,9 @@ perform_inline_testbefore_varying(  struct
cbl_perform_tgt_t *tgt,
       SHOW_PARSE_END
       }
     gg_append_statement(tgt->addresses.condback[i].label);
+    // Needed to make GDB NEXT over PERFORM in-line VARYING UNTIL work
+    // predictably.
+    insert_nop(107);
 
     // Test that conditional
     parser_if(varys[i].until);
@@ -13407,10 +13404,6 @@ create_and_call(size_t narg,
 
   if( returned.field )
     {
-    // Because the CALL had a RETURNING clause, RETURN-CODE doesn't
return a
-    // value.  So, we make sure it is zero
-    //// gg_assign(var_decl_return_code, build_int_cst_type(SHORT, 0));
-
     // We expect the return value to be a 64-bit or 128-bit integer.  How
     // we treat that returned value depends on the target.
 
diff --git a/gcc/cobol/gengen.cc b/gcc/cobol/gengen.cc
index fa792d618bb..208d4910527 100644
--- a/gcc/cobol/gengen.cc
+++ b/gcc/cobol/gengen.cc
@@ -1843,6 +1843,10 @@ gg_create_goto_pair(tree *goto_expr,
                                   void_type_node);
   DECL_CONTEXT(label_decl) = current_function->function_decl;
   TREE_USED(label_decl) = 1;
+  DECL_EXTERNAL(label_decl) = 1;
+  TREE_PUBLIC(label_decl) = 1;
+  TREE_ADDRESSABLE(label_decl) = 1;
+  TREE_STATIC(label_decl) = 1;
 
   *goto_expr  = build1(GOTO_EXPR, void_type_node, label_decl);
   *label_expr = build1(LABEL_EXPR, void_type_node, label_decl);
diff --git a/gcc/cobol/parse.y b/gcc/cobol/parse.y
index bcd8d6f3105..1311797f03d 100644
--- a/gcc/cobol/parse.y
+++ b/gcc/cobol/parse.y
@@ -1894,10 +1894,17 @@ comminit:       COMMON     {
                 ;
 
 
-env_div:        %empty              { current_division =
environment_div_e; }
-        |       ENVIRONMENT_DIV '.' { current_division =
environment_div_e; }
+env_div:        %empty {
+                  current_division = environment_div_e;
+                  parser_division( environment_div_e, NULL, 0, NULL );
+                }
+        |       ENVIRONMENT_DIV '.' {
+                  current_division = environment_div_e;
+                  parser_division( environment_div_e, NULL, 0, NULL );
+                }
         |       ENVIRONMENT_DIV '.' {
                   current_division = environment_div_e;
+                  parser_division( environment_div_e, NULL, 0, NULL );
                 } env_sections
                 ;
 
@@ -3159,12 +3166,14 @@ when_set_to:    %empty
         |       WHEN SET TO
         ;
 
-data_div:       %empty 
-        |       DATA_DIV
-        |       DATA_DIV { current_division = data_div_e; } data_sections
-                {
+data_div:       %empty   { parser_division( data_div_e, NULL, 0, NULL );
}
+        |       DATA_DIV { parser_division( data_div_e, NULL, 0, NULL );
}
+        |       DATA_DIV {
+                  current_division = data_div_e;
+                  parser_division( data_div_e, NULL, 0, NULL ); 
+                }
+                data_sections {
                   current_data_section = not_data_datasect_e;
-                  parser_division( data_div_e, NULL, 0, NULL );
                 }
                 ;
 
@@ -4091,8 +4100,9 @@ data_descr1:    level_name
                   if( $field->has_attr(blank_zero_e) ) {
                     switch($field->type) {
                     case FldNumericEdited:
-                      if( $field->has_attr(signable_e) ) {
-                        error_msg(@2,  "%s has 'S' in PICTURE, cannot be
BLANK WHEN ZERO",
+                      // Test appears to be invalid.
+                      if( false && $field->has_attr(signable_e) ) {
+                        error_msg(@2,  "%s has signed PICTURE, cannot be
BLANK WHEN ZERO",
                                   $field->name );
                       }
                       break;
@@ -4446,6 +4456,10 @@ picture_clause: PIC signed nps[fore] nines nps[aft]
                   if( field->has_attr(signable_e) && ! $signed ) {
                     dbgmsg("%s PICTURE must be signed for SIGN IS",
field->name);
                   }
+                  if( field->type == FldNumericEdited && $signed ) {
+                    gcc_assert(field->has_attr(blank_zero_e));
+                    error_msg(@signed, "%<S%> in PICTURE invalid with
BLANK WHEN ZERO");
+                  }
                   field->attr |= $signed;
                   field->data.digits = $nines;
                   auto nchar = type_capacity(field->type, $nines);
@@ -4474,6 +4488,10 @@ picture_clause: PIC signed nps[fore] nines nps[aft]
                   }
                   field->data.digits = $left + $rdigits;
                   field->attr |= $signed;
+                  if( field->type == FldNumericEdited && $signed ) {
+                    gcc_assert(field->has_attr(blank_zero_e));
+                    error_msg(@signed, "%<S%> in PICTURE invalid with
BLANK WHEN ZERO");
+                  }
 
                   if( field->is_binary_integer() ) {
                     field->set_capacity(type_capacity(field->type,
@@ -4511,6 +4529,10 @@ picture_clause: PIC signed nps[fore] nines nps[aft]
                   }
                   ERROR_IF_CAPACITY(@PIC, field);
                   field->attr |= $signed;
+                  if( $signed ) {
+                    gcc_assert(field->has_attr(blank_zero_e));
+                    error_msg(@signed, "%<S%> in PICTURE invalid with
BLANK WHEN ZERO");
+                  }
                   field->data.digits = size;
                   field->set_capacity(++size);
                   field->data.rdigits = $rdigits;
@@ -4573,6 +4595,7 @@ picture_clause: PIC signed nps[fore] nines nps[aft]
                   field->data.digits   =  digits_of_picture($picture,
false);
                   field->data.rdigits  = rdigits_of_picture($picture);
                   if( is_picture_scaled($picture) ) field->attr |=
scaled_e;
+                  field->set_signable();
                   auto nchar = length_of_picture($picture);
                   field->set_capacity(nchar);
                   field->blank_initial(nchar);
@@ -5038,19 +5061,24 @@ based_clause:   BASED
                 }
                 ;
 
-blank_zero_clause: blank_when_zero
-                { cbl_field_t *field = current_field();
-                  // the BLANK WHEN ZERO clause defines the item as
numeric-edited.
+blank_zero_clause: BLANK when ZERO
+                { // BLANK WHEN ZERO defines the item as numeric-edited.
+                  cbl_field_t *field = current_field();
+                  auto attr = blank_zero_e;
                   if( !field_type_update(field, FldNumericEdited, @1) ) {
-                    YYERROR;
+                    attr = none_e;
+                    if( field->type == FldNumericDisplay ) {
+                      assert(field->has_attr(signable_e));
+                      error_msg(@$, "signed NUMERIC DISPLAY type "
+                                    "cannot have BLANK WHEN ZERO");
+                    } else {
+                      assert(is_numeric(field));
+                      error_msg(@$, "NUMERIC type cannot have BLANK WHEN
ZERO");
+                    }                      
                   }
-                  field->attr |= blank_zero_e;
+                  field->set_attr(attr);
                 }
                 ;
-blank_when_zero:
-                BLANK WHEN ZERO
-        |       BLANK      ZERO
-                ;
 
 synched_clause: SYNCHRONIZED
         |       SYNCHRONIZED LEFT
@@ -5197,14 +5225,14 @@ volatile_clause:
 procedure_div:  %empty {
                  if( !procedure_division_ready(@$, NULL, NULL) ) YYABORT;
                 }
-        |       PROCEDURE_DIV '.' {
-                  if( !procedure_division_ready(@$, NULL, NULL) )
YYABORT;
-                } declaratives sentences
-        |       PROCEDURE_DIV procedure_args '.' declaratives sentences
         |       PROCEDURE_DIV procedure_args '.'
+        |       PROCEDURE_DIV procedure_args '.' declaratives sentences
                 ;
 
-procedure_args: USING procedure_uses[args]
+procedure_args: %empty {
+                  if( !procedure_division_ready(@$, NULL, NULL) )
YYABORT;
+                }
+        |       USING procedure_uses[args]
                 {
                   if( !procedure_division_ready(@args, NULL, $args) )
YYABORT;
                 }
@@ -11824,6 +11852,10 @@ user_default:   DEFAULT
                 }
                 ;
 
+when:           %empty
+        |       WHEN
+                ;
+
 with:           %empty
         |       WITH
                 ;
diff --git a/gcc/cobol/parse_ante.h b/gcc/cobol/parse_ante.h
index 12d944f3ab7..88950a95995 100644
--- a/gcc/cobol/parse_ante.h
+++ b/gcc/cobol/parse_ante.h
@@ -2949,8 +2949,9 @@ field_type_update( cbl_field_t *field,
cbl_field_type_t type,
                    bool is_usage = false)
 {
   // preserve NumericEdited if already established
-  if( !is_usage && field->has_attr(blank_zero_e) ) {
-    if( type == FldNumericDisplay && field->type == FldNumericEdited ) {
+  if( !is_usage ) {
+    if( field->type == FldNumericEdited && type == FldNumericDisplay ) {
+      assert(field->has_attr(blank_zero_e));
       return true;
     }
   }
@@ -2971,8 +2972,10 @@ field_type_update( cbl_field_t *field,
cbl_field_type_t type,
   }
 
   if( ! symbol_field_type_update(field, type, is_usage) ) {
-    error_msg(loc, "cannot set USAGE of %s to %s (from %s)", field->name,
-             cbl_field_type_str(type) + 3,
cbl_field_type_str(field->type) + 3);
+    if( type != FldNumericEdited ) { // caller prints message
+      error_msg(loc, "cannot set USAGE of %s to %s (from %s)",
field->name,
+                cbl_field_type_str(type) + 3,
cbl_field_type_str(field->type) + 3);
+    }
     return false;
   }
 
diff --git a/gcc/cobol/symbols.cc b/gcc/cobol/symbols.cc
index a94ef8bddfa..731f51afbdb 100644
--- a/gcc/cobol/symbols.cc
+++ b/gcc/cobol/symbols.cc
@@ -816,6 +816,40 @@ cbl_field_t::clear_attr( cbl_field_attr_t attr ) {
   return this->attr &= ~uint64_t(attr);
 }
 
+// Test various ways a Numeric Edited picture can describe a signed
value.
+uint64_t 
+cbl_field_t::set_signable() {
+  gcc_assert(type == FldNumericEdited);
+  gcc_assert(data.picture);
+  char *p = xstrdup(data.picture);
+  char *pend;
+  // Look to see if this is a floating-point numeric-edited:
+  pend = strchr(p, ascii_e);
+  if( !pend ) {
+    pend = strchr(p, ascii_E);
+  }
+  if( pend ) {
+    // We end our inspection at the 'E'
+    *pend = '\0';
+  }
+  size_t len = strlen(p);
+  if( p[0] == ascii_plus || p[0] == ascii_minus ) {
+    // The very first character is plus or minus
+    set_attr(signable_e);
+  } else if( len >= 1 && (p[len-1] == ascii_plus || p[len-1] ==
ascii_minus)) {
+    // The very last character is plus or minus
+    set_attr(signable_e);
+  }
+  else if( len >= 2 &&
+     (   (TOUPPER(p[len-2]) == ascii_D && TOUPPER(p[len-1]) == ascii_B)
+      || (TOUPPER(p[len-2]) == ascii_C && TOUPPER(p[len-1]) == ascii_R) )
) {
+    // The last two characters are DB or CR
+    set_attr(signable_e);
+  }
+  free(p);
+  return attr;
+}
+
 static uint32_t
 field_memsize( const struct cbl_field_t *field ) {
   uint32_t n = field->occurs.ntimes();
@@ -4094,10 +4128,15 @@ cbl_field_t::encode( size_t srclen, cbl_loc_t loc
) {
 
     if( inbytesleft == 0 ) {
       if( data.all() ) {
-        for( size_t len = outbuf - data.initial;
-             outbuf + len <= data.initial + data.capacity();
-             outbuf += len ) {
-          std::copy( data.initial, data.initial + len, outbuf );
+        size_t len = outbuf - data.initial;
+        // We need to repeatedly append the first len bytes of
data.initial to
+        // data.initial until it is full.  Thus ALL "ABC" becomes
"ABCABC..."
+        char *d = const_cast<char*>(data.initial);
+        size_t source_i = 0;
+        size_t dest_i   = len;
+        while( dest_i < static_cast<size_t>(data.capacity()) ) {
+          d[dest_i++] = d[source_i++];
+          source_i %= len;
         }
       }
       if( is_literal(this) ) {
diff --git a/gcc/cobol/symbols.h b/gcc/cobol/symbols.h
index 7a362564efe..511198a870e 100644
--- a/gcc/cobol/symbols.h
+++ b/gcc/cobol/symbols.h
@@ -889,6 +889,7 @@ struct cbl_field_t {
   uint64_t set_attr( cbl_field_attr_t attr );
   uint64_t clear_attr( cbl_field_attr_t attr );
   const char * attr_str( const std::vector<cbl_field_attr_t>& attrs )
const;
+  uint64_t set_signable();
 
   bool is_justifiable() const {
     if( type == FldAlphanumeric ) return true;
diff --git a/gcc/cobol/util.cc b/gcc/cobol/util.cc
index f27db2ad6e2..42c67cafca8 100644
--- a/gcc/cobol/util.cc
+++ b/gcc/cobol/util.cc
@@ -970,25 +970,38 @@ symbol_field_type_update( cbl_field_t *field,
    *  Concrete type candidate
    */
   switch(field->usage) {
-  case FldInvalid:
-    field->type = candidate;
-    field->attr |= numeric_group_attrs(field);
-    // update encoding
+  case FldInvalid: // no USAGE clause yet, and not now either
+    // maybe update encoding
     switch( field->type ) {
-    case FldNumericDisplay:
     case FldAlphaEdited:
     case FldNumericEdited:
+      field->type = candidate;
+      field->attr |= numeric_group_attrs(field);
       return field->codeset.set();
+    case FldNumericDisplay:
+      // If the field is already defined as Numeric Display, it cannot be
+      // converted to Numeric Edited if it is signed.
+      if( candidate == FldNumericEdited) {
+        if( field->has_attr(signable_e) ) return false;
+      }
+      break;
     default:
+      // If the field is already defined as a binary numeric type (not
+      // Display), it cannot be converted to NumericEdited.
+      if( candidate == FldNumericEdited) {
+        if( is_numeric(field->type) ) return false;
+      }
       break;
     }
+    field->type = candidate;
+    field->attr |= numeric_group_attrs(field);
     return true;
   case FldDisplay:
     if( is_displayable(candidate) ) {
       field->type = candidate;
       field->attr |= numeric_group_attrs(field);
-      if( ! field->codeset.valid() ) return field->codeset.set();
-      return true;
+      if( field->codeset.valid() ) return true;
+      return field->codeset.set();
     }
     break;
   case FldAlphaEdited:
@@ -1586,7 +1599,11 @@ cbl_field_t::encode_numeric( const char input[],
cbl_loc_t loc,
             }
           if( l_digits - l_rdigits > data.digits - data.rdigits )
             {
-            error_msg(loc, "VALUE has too many integer digits");
+            // This error is caught earlier by validate_numeric_edited
+            if( type != FldNumericEdited )
+              {
+              error_msg(loc, "VALUE has too many integer digits");
+              }
             }
           }
         }
@@ -1606,6 +1623,7 @@ cbl_field_t::encode_numeric( const char input[],
cbl_loc_t loc,
     switch(type)
       {
       case FldNumericBin5:
+      case FldIndex:
       case FldLiteralN:
         {
         binary_initial(retval, this, value, l_rdigits);
diff --git a/libgcobol/valconv.cc b/libgcobol/valconv.cc
index 012f881d4cd..ef5faae576c 100644
--- a/libgcobol/valconv.cc
+++ b/libgcobol/valconv.cc
@@ -231,10 +231,10 @@ __gg__string_to_numeric_edited( char * const dest,
 
   int dlength = expand_picture(dest, picture);
 
-  // At the present time, I am taking a liberty. In principle, a 'V'
-  // character is supposed to be logical decimal place rather than a
physical
-  // one.  In practice, I am not sure what that would mean in a numeric
edited
-  // value.  So, I am treating V as a decimal point.
+  // We need to treat 'V' as a decimal point in order to handle
+  //    01 foo pic 999v999 BLANK WHEN ZERO.
+  // The "BLANK WHEN ZERO" turns the field into a numeric-edited type,
but the
+  // 'V' is still in the picture string.
 
   for(int i=0; i<dlength; i++)
     {
-- 
2.34.1


Reply via email to