Iterating Systems.Collections.List in Fujitsu COBOL

1.1k views Asked by At

How do I loop through a collection?

I'm on a trial version of Fujitsu/Alchemy compiler, and getting slow and poor support from the vendor.

I basically want to pass a List from C# to COBOL, and then let the COBOL use it and potentially update it.

In C#, the normal way of iterating through a collection is to use the "foreach" construct.

However, the C# "foreach" construct is a shortcut for the following:

private static void test1()
{
    List<IDMSMapField> list1 = new List<IDMSMapField>();
    int listSize = list1.Count;
    // was just checking exact variablename and case here to copy into COBOL code.
    int itemNumber = 0;

    System.Collections.Generic.List<IDMSMapField>.Enumerator enumerator1 = list1.GetEnumerator();

    while (enumerator1.MoveNext())
    {
        Console.Write("Okay" + enumerator1.Current);
    }
}

I can write this in COBOL if you can help me figure out to declare this class:

System.Collections.Generic.List<IDMSMapField>.Enumerator

The "Enumerator" structure is documented on Microsoft's MSDN site.

It tells that "Enumerator" is a Struct, not a Class!

From what I can tell in the manual "CreatingCOBOLfromDotnetFrameworkDox.pdf", structures are defined as classes in the COBOL REPOSITORY.

Example from the manual:

Define specifiers for structure in REPOSITORY, and any struct members:
CLASS STRUCT-name AS "struct-namespace"
PROPERTY PROP-struct-member AS "external-property-name"
Handle structures like classes. E.g. object to store a struct instance:
01 struct-object OBJECT REFERENCE STRUCT-name.

Below, I am repeating some of the variations I tried that have all failed to compile, because of "cannot be resolved" error. If you can show me howto to declare this properly, I think we can move forward.

1.

    REPOSITORY.
           CLASS  CLASS-LIST         AS "System.Collections.Generic.List<>"
           CLASS  STRUCT-Enumerator  AS "System.Collections.Generic.List<>.Enumerator"
           .

Error on second line:

error JMN1795I-S: The named reference 'System.Collections.Generic.List<>.Enumerator' cannot be resolved.

  1. Exact same error for this:

        REPOSITORY.
               CLASS  CLASS-LIST         AS "System.Collections.Generic.List<>"
               CLASS  STRUCT-Enumerator  AS "System.Collections.Generic.List<T>.Enumerator"
               .
    

error JMN1795I-S: The named reference 'System.Collections.Generic.List.Enumerator' cannot be resolved.

  1. Same error for this:

    REPOSITORY. CLASS CLASS-LIST AS "System.Collections.Generic.List<>" CLASS STRUCT-Enumerator as "System.Collections.Generic.List.Enumerator" .

error JMN1795I-S: The named reference 'System.Collections.Generic.List.Enumerator' cannot be resolved.

The other alternative is to treat it as an array, but I'm stuck on that as well.

REPOSITORY.
    CLASS LIST-IDMSMapField    AS "System.Collections.Generic.List<>[]"
    CLASS CLASS-IDMSMapField   AS "Lightyear.ERCB.IDMSDC.IDMSMapField"
    CLASS CLASS-LIST-IDMSMapField EXPANDS LIST-IDMSMapField USING CLASS-IDMSMapField.

METHOD-ID. TW1DR4000-PF06 AS "TW1DR4000_PF06".
DATA DIVISION.
WORKING-STORAGE SECTION.

01 MapFieldItem USAGE OBJECT REFERENCE CLASS-IDMSMapField.

LINKAGE SECTION.

01 MapFieldList USAGE OBJECT REFERENCE CLASS-LIST-IDMSMapField.

PROCEDURE DIVISION...
...
SET MapFieldItem TO MapFieldList(1).

error JMN2671I-S: ':' must be specified in the reference modifier. ':' is assumed.

I think the compiler sees the (1) as a substring operation perhaps.

2

There are 2 answers

2
NealWalters On BEST ANSWER

It took a while, but here's a complete working sample. There were a couple of other tricks getting to the properties, and moving the property values such as Count to a COBOL variable with the proper USAGE clause. The "EXPANDS" keyword (in the repository) is another key part of the solution.

In my actual program, I'll receive the list in the LINKAGE section, and I'll have a list of more complex objects... but the sample-code is a simpler scenario that stands-alone and runs "as is".

   IDENTIFICATION DIVISION.
   PROGRAM-ID. MAIN AS "COBOLEnumerationSample.Main".
   ENVIRONMENT DIVISION.
   CONFIGURATION SECTION.
   SPECIAL-NAMES.
   REPOSITORY.
       CLASS List        AS "System.Collections.Generic.List<>"
       CLASS SysString   AS "System.String"
       CLASS SysBoolean  AS "System.Boolean" 
       CLASS ListString EXPANDS List USING SysString
       CLASS Enumerator  AS "System.Collections.Generic.List<>+Enumerator"
       CLASS StringEnumerator EXPANDS Enumerator USING SysString
       PROPERTY PROP-Count   AS "Count"      
       PROPERTY PROP-Current AS "Current"
       .       
   DATA DIVISION.
   WORKING-STORAGE SECTION.
   01 myList              OBJECT REFERENCE ListString.
   01 myEnum              OBJECT REFERENCE StringEnumerator.   
   01 myBooleanEnumResult OBJECT REFERENCE SysBoolean. 
   01 myDotNetString      OBJECT REFERENCE SysString.  
   01 myLoopCounter       PIC 9(4)  value zero. 
   01 myCobolCount        PIC S9(9) COMP-5 VALUE ZERO.
   01 myCobolString       PIC X(30) value spaces. 
   01 YN-END-LOOP         PIC X     value "N". 
   01 WS-ACCEPT-INPUT     PIC X(80) value spaces. 

   PROCEDURE DIVISION.
   1000-START.
       INVOKE ListString "NEW" returning myList.
       INVOKE myList "Add" using "Apples"
       INVOKE myList "Add" using "Bananas"
       INVOKE myList "Add" using "Orange"


       SET myCobolCount to PROP-Count of myList 
       DISPLAY "Size of MyList = " myCobolCount

       INVOKE myList "GetEnumerator" returning myEnum.


       PERFORM UNTIL YN-END-LOOP = "Y" 
             INVOKE myEnum "MoveNext" returning myBooleanEnumResult  
  * How to test for Boolean, True = B'1' and False = B'0' 
             IF myBooleanEnumResult = B'0' 
                MOVE "Y" TO YN-END-LOOP 
             ELSE       
                SET myDotNetString TO PROP-Current of myEnum 
                ADD 1 TO myLoopCounter
                SET myCobolString to myDotNetString 
                DISPLAY myLoopCounter " " myCobolString 
             END-IF    

       END-PERFORM 

       DISPLAY "END OF PROGRAM - PRESS ENTER TO END" 
       ACCEPT WS-ACCEPT-INPUT
       .

   END PROGRAM MAIN.
1
Stephen Gennard On

Using Micro Focus's .NET implementation the code can use a "perform varying through" extension to go through the list.

So the tweaked version is:

   IDENTIFICATION DIVISION.
   PROGRAM-ID. MAIN AS "COBOLEnumerationSample.Main".
   ENVIRONMENT DIVISION.
   CONFIGURATION SECTION.
   SPECIAL-NAMES.
   REPOSITORY.
       CLASS List        AS "System.Collections.Generic.List"
       CLASS SysString   AS "System.String"
       CLASS SysBoolean  AS "System.Boolean"
       CLASS ListString EXPANDS List USING SysString
       PROPERTY PROP-Count   AS "Count"
       PROPERTY PROP-Current AS "Current"
       .
   DATA DIVISION.
   WORKING-STORAGE SECTION.
   01 myList              OBJECT REFERENCE ListString.
   01 myLoopCounter       PIC 9(4)  value zero.
   01 myCobolCount        PIC S9(9) COMP-5 VALUE ZERO.
   01 myCobolString       PIC X(30) value spaces.
   01 WS-ACCEPT-INPUT     PIC X(80) value spaces.

   PROCEDURE DIVISION.
   1000-START.
       INVOKE ListString "NEW" returning myList.
       INVOKE myList "Add" using "Apples"
       INVOKE myList "Add" using "Bananas"
       INVOKE myList "Add" using "Orange"

       SET myCobolCount to PROP-Count of myList
       DISPLAY "Size of MyList = " myCobolCount

       move 0 to myLoopCounter
       PERFORM varying myCobolString through myList
           DISPLAY myLoopCounter " " myCobolString
           ADD 1 to myLoopCounter
       END-PERFORM

       DISPLAY "END OF PROGRAM - PRESS ENTER TO END"
       ACCEPT WS-ACCEPT-INPUT
       .

   END PROGRAM MAIN.

Next, rather than using the repository syntax and expands, you can use a inline declaration, for example:

   IDENTIFICATION DIVISION.
   PROGRAM-ID. MAIN AS "COBOLEnumerationSample.Main".
   WORKING-STORAGE SECTION.
   01 myList         type "System.Collections.Generic.List"[string].
   01 myLoopCounter   PIC 9(4)  value zero.
   01 myCobolCount    PIC S9(9) COMP-5 VALUE ZERO.
   01 myCobolString   PIC X(30) value spaces.
   01 WS-ACCEPT-INPUT PIC X(80) value spaces.

   PROCEDURE DIVISION.
   1000-START.
       INVOKE type "System.Collections.Generic.List"[string]::"NEW"
             returning myList
       INVOKE myList "Add" using "Apples"
       INVOKE myList "Add" using "Bananas"
       INVOKE myList "Add" using "Orange"


       SET myCobolCount TO myList::"Count"
       DISPLAY "Size of MyList = " myCobolCount

       MOVE 0 TO myLoopCounter
       PERFORM varying myCobolString through myList
           DISPLAY myLoopCounter " " myCobolString
           ADD 1 TO myLoopCounter
       END-PERFORM

       DISPLAY "END OF PROGRAM - PRESS ENTER TO END"
       ACCEPT WS-ACCEPT-INPUT
       .

   END PROGRAM MAIN.

Anyway, I just love that we can do this in COBOL, no matter which product is being used.. Enjoy...