In my SPSS-dataset I have a string-variable QuestionA containing the answer to a certain question. However, instead of just one answer, it is possible to check more than one answer.
For example, if one checks answers 02, 05 and 07 it is saved in the variable QuestionA as the string "02;05;07".
I would like to create a variable for specific answer 02. Let's call that variable Answer02. It should contain a 0 if QuestionA does not contain 02 anywhere in its text, and a 1 if QuestionA actually contains 02 anywhere.
For me the catch lies in the fact that one could also check answer 01 as well which makes the answer contained in QuestionA 01;02.
The answer should be generic, if possible, so that I can also create a variable Answer05 in similar fashion.
This should give you a flavour:
DATA LIST FREE / Q (A9).
BEGIN DATA
"01" "02" "03" "01,02" "02,03" "04,05"
END DATA.
DO REPEAT A=A1 to A3 /B="01" "02" "03".
IF CHAR.INDEX(Q,B)>0 A=1.
END REPEAT.
RECODE A1 to A3 (SYSMIS=0).
EXE.
If you are just interested in that one case, this code is simpler.
COMPUTE Answer02=char.index(QuestionA, "02") > 0.
Related
I understand that HIGH-VALUES correspond to the highest in the collating sequence, however I do not understand why it may be a preferred method when using conditionals.
Example:
01 StudentRecord.
88 EndOfStudentFile VALUE HIGH-VALUES.
02 StudentID PIC X(7).
02 FILLER PIC X(23).
...
AT END SET EndOfStudentFile TO TRUE
Why not simply use VALUE 0 and SET EndOfStudentFile to 1 ?
Whats the advantage of using HIGH-VALUES in these cases?
Appreciate any input on this matter...
The conditional 88 in your example is for the StudentRecord, so it sets/queries that. I think that it may be more appropriate to use VALUE ALL HIGH-VALUES - as it stands it will set the first byte to HIGH-VALUE and then pad the record (with spaces).
VALUE 0/1 would not be possible for that as the record - because it is a group - is alphanumeric, and should not be assigned a numeric value.
... the question "is xyz preferred" is often more a question of style and only rarely "best practice". The commonly good thing is to ensure a consistent use/style so that others reading the code can understand it better.
In this specific case it could be used to "store" the information "all students were processed" which then can be queried later via IF EndOfStudentFile and if for some reason there is another START >= StudentID (I assume that is an ORGANIZATION INDEXED file here) on the file it likely will not found "another" record (still possible here, a student with an id containing ALL HIGH-VALUE would be found).
Just to clarify '88' levels do not represent real storeage.
They are conditionals which refer to the immediately preceding variable definition.
So:
If EndOfStudentFile..
is just as shortcut for
If StudentRecord is equal to High-Values...
1) Read a line of 2000 characters and replace all SPACES with a single "+" plus character. i.e. Convert "A B" to "A+B" or "A B" to "A+B"
2)Read a line of 2000 characters, then search for a specific patterns like "PWD" or "INI" or etc and finally store next 6 characters into a variable.
3) Read a line of 2000 characters and store the last word in the string to a variable.
Edit:
I use Micro Focus COBOL.
This is a screenshot of my piece of code so far.
My code is below. It removes a few spaces but not all. Try writing any sentence with random numbers of spaces in between words in and input file for test-data.
IDENTIFICATION DIVISION.
PROGRAM-ID. SALAUT.
ENVIRONMENT DIVISION.
FILE-CONTROL.
SELECT IN-FILE ASSIGN TO "INFILE"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-IN-FILE-STATUS.
SELECT OUT-FILE ASSIGN TO "OUTFILE"
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS WS-OUT-FILE-STATUS.
DATA DIVISION.
FILE SECTION.
FD IN-FILE.
01 FS-IN-FILE PIC X(200).
FD OUT-FILE.
01 FS-OUT-FILE PIC X(200).
WORKING-STORAGE SECTION.
01 WS-ATMA-C.
03 WS-OUT-FILE-STATUS PIC X(02).
03 WS-IN-FILE-STATUS PIC X(02).
03 WS-LOOP-COUNTER PIC 9(03) VALUE 1.
03 WS-IN-EOF PIC X value 'N'.
03 WS-IN-FILE-LEN PIC 9(03).
03 WS-IN-SPACE-CNT PIC 9(03) VALUE 1.
03 FS-IN-FILE-2 PIC X(200).
03 WS-TRIL-SPACE-CNT PIC 9(03).
03 WS-TOT-SPACE-CNT PIC 9(03).
PROCEDURE DIVISION.
MAIN-PARA.
OPEN INPUT IN-FILE.
IF WS-IN-FILE-STATUS <> '00'
EXHIBIT 'IN-FILE-OPEN-ERROR : STOP-RUN'
EXHIBIT NAMED WS-IN-FILE-STATUS
PERFORM MAIN-PARA-EXIT
END-IF.
OPEN OUTPUT OUT-FILE.
IF WS-OUT-FILE-STATUS <> '00'
EXHIBIT 'OUT-FILE-OPEN-ERROR : STOP-RUN'
EXHIBIT NAMED WS-OUT-FILE-STATUS
PERFORM MAIN-PARA-EXIT
END-IF.
PERFORM SPACE-REMOVER-PARA THRU SPACE-REMOVER-PARA-EXIT.
CLOSE IN-FILE.
IF WS-IN-FILE-STATUS <> '00'
EXHIBIT 'IN-FILE-CLOSE-ERROR : STOP-RUN'
EXHIBIT NAMED WS-IN-FILE-STATUS
PERFORM MAIN-PARA-EXIT
END-IF.
CLOSE OUT-FILE.
IF WS-OUT-FILE-STATUS <> '00'
EXHIBIT 'IN-FILE-CLOSE-ERROR : STOP-RUN'
EXHIBIT NAMED WS-OUT-FILE-STATUS
PERFORM MAIN-PARA-EXIT
END-IF.
MAIN-PARA-EXIT.
STOP RUN.
SPACE-REMOVER-PARA.
PERFORM UNTIL WS-IN-EOF = 'Y'
INITIALIZE FS-IN-FILE FS-OUT-FILE WS-IN-FILE-LEN FS-IN-FILE-2
READ IN-FILE
AT END
MOVE 'Y' TO WS-IN-EOF
NOT AT END
INSPECT FS-IN-FILE TALLYING WS-IN-FILE-LEN FOR CHARACTERS
EXHIBIT NAMED WS-IN-FILE-LEN
MOVE 1 TO WS-LOOP-COUNTER
IF WS-IN-FILE-LEN <> 0
PERFORM UNTIL WS-IN-SPACE-CNT <= ZEROS
INSPECT FS-IN-FILE TALLYING WS-TOT-SPACE-CNT FOR ALL " "
INSPECT FUNCTION REVERSE (FS-IN-FILE) TALLYING
WS-TRIL-SPACE-CNT FOR LEADING " "
INITIALIZE WS-IN-SPACE-CNT
COMPUTE WS-IN-SPACE-CNT =
WS-TOT-SPACE-CNT - WS-TRIL-SPACE-CNT
PERFORM VARYING WS-LOOP-COUNTER FROM 1 BY 1
UNTIL WS-LOOP-COUNTER >=
WS-IN-FILE-LEN - (2 * WS-TRIL-SPACE-CNT)
IF FS-IN-FILE(WS-LOOP-COUNTER:2) = " "
STRING FS-IN-FILE(1:WS-LOOP-COUNTER - 1) DELIMITED BY SIZE
FS-IN-FILE(WS-LOOP-COUNTER + 2
: WS-IN-FILE-LEN - WS-LOOP-COUNTER - 2)
DELIMITED BY SIZE
INTO FS-IN-FILE-2
END-STRING
INITIALIZE FS-IN-FILE
MOVE FS-IN-FILE-2 TO FS-IN-FILE
INITIALIZE FS-IN-FILE-2
END-IF
END-PERFORM
INITIALIZE WS-LOOP-COUNTER WS-TRIL-SPACE-CNT WS-TOT-SPACE-CNT
END-PERFORM
WRITE FS-OUT-FILE FROM FS-IN-FILE
IF WS-OUT-FILE-STATUS <> '00'
EXHIBIT 'OUT-FILE-WRITE-ERROR : STOP-RUN'
EXHIBIT NAMED WS-OUT-FILE-STATUS
PERFORM MAIN-PARA-EXIT
END-IF
END-IF
END-READ
END-PERFORM.
SPACE-REMOVER-PARA-EXIT.
EXIT.
As INSPECT REPLACING only allows to replace the same number of bytes you can not use it. As Brian pointed out your COBOL runtime may comes with options like GnuCOBOL's FUNCTION SUBSTITUTE. In any case the question "Which COBOL" is still useful to be answered.
To do Thraydor's approach use UNSTRING to a table using a string pointer. Something along
MOVE 1 TO strpoint
PERFORM VARYING table-idx FROM 1 BY 1
UNTIL table-idx = table-max
UNSTRING your2000line DELIMITED BY ALL SPACES
INTO tmp-table (table-idx)
WITH POINTER strpoint
NOT ON OVERFLOW
EXIT PERFORM
END-UNSTRING
END-PERFORM
Another approach which always work is a simple PERFORM over the 2000 bytes with a bunch of IF your2000line (pos:1) statements (if possible: combine it to a single EVALUATE) checking byte by byte (comparing the last byte for removing the duplicate bytes) transferring the source with replacements to a temporary field and MOVE it back once you're finished
Please edit your question to show what exactly you've tried and you can get much better answers.
Firstly, bear in mind that COBOL is a language of dialects. There are also active commercial compilers which target the 1974, 1985, 2002 (now obsolete, incorporated in 2014) and 2014 Standards. All with their own Language Extensions, which may or many not be honoured in a different COBOL compiler.
If you are targeting your learning to a particular environment (IBM Mainframe COBOL you have said) then use that dialect as a subset of what is available to you in the actual COBOL you are using. Which means using the IBM Manuals.
Don't pick and chose stuff from places and use it just because it somehow seemed like a good idea at the time.
I have to admit that EXHIBIT was great fun to use, but it was only ever a Language Extension, and IBM dropped it by at least the later releases of OS/VS COBOL. It, like ON, was a "debugging" statement, although that didn't prevent their being used "normally". There's additional overhead to using EXHIBIT over a simple DISPLAY. IBM Enterprise COBOL only has a simple DISPLAY.
Whilst you may think it fun to use pictograms (the "oh my goodness, what symbol should I use for this" of a figure attempting to pull his own hair out) be aware that that particular symbol was a latecomer to the 2014 Standard, and if it appears in Enterprise COBOL within the next 20 to 50 years I'd be surprised (very low of the list of things to do, another cute way to write "not equal to" when many already exist, and COBOL even has an ELSE).
Some pointers. Don't have a procedure called "remove-all-the-spaces" if what it does is itself is "everything-including-install-a-new-kitchen-sink". Is it any wonder you can't find why it doesn't work?
Many, many, many COBOL programs have the task of reading a file, until the end, and processing the records in the file. Get yourself one of those working well first. Is that relevant to the "business process" the program is addressing? No, it's just technical stuff, which you can't do without so hide it somewhere. Where? in PERFORMed procedures (paragraphs or SECTIONS). Don't expect someone who quickly wants to know what your program is doing to want to read the stuff which every program does. Hide it.
You can find quite a bit of general advice here about writing COBOL programs. Pay attention to those which advise of the use of full-stops/periods, priming reads, and the general structure of COBOL programs.
It is very important to describe things accurately. Work on good, descriptive, accurate names for data-names and procedures. A file is a collection of records.
You have cut down the size of your data to make testing easier, without realising that you have a problem with your data-definitions when you go back to full-length data. Your "counters" can only hold three digits, when they need to be able to cope with the numbers up to 2000.
There is no point in doing something to a piece of data, and then immediately squishing that something with something else which is not related in any way to the original something.
MOVE SPACE TO B
MOVE A TO B
The first MOVE is redundant, superflous, and does nothing but suck up CPU time and confuse the next reader of your program. "Is there some code missing, because otherwise that's just plain dumb".
This is a variant of that example with the MOVE, and you are doing this all over the place:
INITIALIZE WS-IN-SPACE-CNT
COMPUTE WS-IN-SPACE-CNT =
WS-TOT-SPACE-CNT - WS-TRIL-SPACE-CNT
The INITIALIZE is a waste of space, resources, and an introducer of confusion, and extra lines of code to make your program more difficult to understand.
Also, don't "reset" things after they are used, so that they are "ready for next time". That creates dependencies which a future amender of your program will not expect. Even when expected/noticed, they make the code harder to follow.
Exactly what is wrong with your code is impossible to say without knowing what you think is wrong with it. For instance, there is not even a sign of a "+" replacing any spaces, so if you feel that is what it wrong, you simply haven't coded for it.
You've also only attempted one of the three tasks. If once of those not working is what you think is wrong...
Knowing what you think is wrong is one thing, but there are a lot of other problems. If you sit down and sort those out, methodically, then you'll come up with a "structurally" COBOL program which you'll find its easier to understand what your own code does, and where problems lie.
A B C D E
A+B+C+D+E
To get from the first to the second using STRING, look into Simon's suggestion to use WITH POINTER.
Another approach you could take would be using reference-modification.
Either way, you'd be build your result field a piece at a time
This field intentionally blank
A
A+B
A+B+C
A+B+C+D
A+B+C+D+E
Rather than tossing all the data around each time. There are also other ways to code it, but that can be for later.
i have string as ' #$rahul ' and i have to calculate number of alpha bates without using inspect verb. Also not using by ord clause for ASCII value. My instructor told me to use empty array but how it is used?? I tried but it counts for symbols also.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TABLE.
05 WS-A OCCURS 3 TIMES INDEXED BY I.
10 WS-B PIC A(2).
10 WS-C OCCURS 2 TIMES INDEXED BY J.
15 WS-D PIC X(3).
PROCEDURE DIVISION.
MOVE '####DEF34GHIJKL56MNOPQR' TO WS-TABLE.
PERFORM A-PARA VARYING I FROM 1 BY 1 UNTIL I >3
STOP RUN.
A-PARA.
PERFORM C-PARA VARYING J FROM 1 BY 1 UNTIL J>2.
C-PARA.
if ws-table(1) equals to spaces
continue
else
add +1 to ws-count
end-if
DISPLAY WS-C(I,J).
Apart from your table-definition and actual use of the table, you have basically got the idea already, except you are not sure what, specifically, to test for.
What you need to do is find the section in your COBOL documentation on class condition and class tests.
I suspect this bit of code:
if ws-table(1) equals to spaces
continue
else
add +1 to ws-count
end-if
Has been added in haste. With your data, ws-table(1) will never be space, and ws-count is not defined.
Back to your definition. You are defining a structure with three parts (WS-A OCCURS 3) each of which consists of a two-byte alphabetic field followed by two three-byte alphanumeric fields. That definition is of no direct use to your task.
01 the-data.
05 FILLER OCCURS 24 TIMES
INDEXED BY data-byte-index.
10 the-data-byte PIC X.
That will allow you to look at each byte individually. Note that you can always use good names, which will make your programs easier to understand, reduce the chance of careless errors, and make people's lives, including your own when you return to a program some time later, generally easier.
Note, you can also use reference-modification and lose out on the readability for the benefit of less typing.
Format of your program
Unless it is dictated to you (and although I've never seen it before in over 30 years, I have seen it a couple of time recently) there is absolutely no point in "indenting" things like the WORKKING-STORAGE section, or even paragraph/SECTION labels. They already have all the indentation they need, and further indentation adds nothing, which requiring more typing, and also causing experienced COBOL programmers to wonder why you are doing that.
Since the 1985 Standard for COBOL, the use of full-stops/periods in the PROCEDURE DIVISION is greatly relaxed. Since a full-stop/period in the wrong place can cause errors, this was a good thing. It will also be good if you take full advantage of it. Commas look far too much like full-stops/periods to be of any use in code. They never have to be there, so having them benefits nothing. Also noise-words like THEN can/should be avoided. Unlike commas, spacing can be a boon to the format of a program.
Here's your code above, reformatted:
MOVE '####DEF34GHIJKL56MNOPQR'
TO WS-TABLE
PERFORM A-PARA
VARYING I
FROM 1
BY 1
UNTIL I > 3
STOP RUN
.
A-PARA.
PERFORM C-PARA
VARYING J
FROM 1
BY 1
UNTIL J > 2
.
C-PARA.
if ws-table ( 1 ) equal to space
continue
else
add +1 to ws-count
end-if
DISPLAY
WS-C ( I J )
.
Use some proper names, and it's start to look like a real program.
Note, not all people agree on how a program should be formatted. Seriously.
My problem is, given a variable which I read from a file, see if it contains or matches another string.
In other words, find in a file all the records whose variable
BRADD PIC X(30)
matches or contains a string introduced by keyboard.
I'm very confident this problem is resolved through the INSPECT instruction, and I've tried something like this in my code:
READ BRANCHFILE NEXT RECORD
AT END SET EndOfFile TO TRUE
END-READ.
PERFORM UNTIL EndOfFile
INSPECT BBRADD
TALLYING CONT for CHARACTERS
BEFORE INITIAL CITY
IF CONT>1
DISPLAY " BRANCH CODE :" BBRID
DISPLAY " BRANCH NAME :" BBRNAME
DISPLAY " BRANCH ADDRESS :" BBRADD
DISPLAY " PHONE :" BBRPH
DISPLAY " E-MAIL :" BEMAIL
DISPLAY " MANAGER NAME :" BMGRNAME
DISPLAY " ------------------"
DISPLAY " ------------------"
END-IF
READ BRANCHFILE NEXT RECORD
AT END SET EndOfFile TO TRUE
END-READ
MOVE 0 TO CONT
END-PERFORM.
Where CITY is the variable I introduce through keyboard.
¿Anyone knows how to find a "substring" in a "string"?
For example, if I introduced "Zaragoza" my program have to print all the records in the file which variable BBRADD contains "Zaragoza".
01 BRANCHREC.
88 EndOfFile VALUE HIGH-VALUE.
02 BBRID PIC X(6).
02 BBRNAME PIC X(15).
02 BBRADD PIC X(30).
02 BBRPH PIC X(10).
02 BEMAIL PIC X(20).
02 BMGRNAME PIC X(25).
You would need to set CONT to zero before the INSPECT, every time.
CONT just gets updated from its initial value when the INSPECT starts. After you find your first one, every record will look like it has CITY in it.
If may initially seem odd that it works that way, but if it didn't you'd be limited on the occasions when that is how you want it to work.
Ah, looking a little closer, you are setting CONT to an initial value, you are just doing it in an unexpected place. If it needs to be zero, set it to zero immediately before it should be zero. Much easier to find, less easy for someone changing the program in the future to make a mess of.
However, you have another problem. Let's say CITY is PIC X(20). The user enters SEVILLA and your INSPECT will now search for SEVILLA followed by 13 spaces. Ideally you'd want SEVILLA followed by one space.
You need to be able to test for a value that the user has entered, with a trailing blank, but not more.
The current popular way to do this is with reference-modification.
You need to take your user-input, find out how many trailing spaces it contains, calculate how long the data is, add one for the trailing blank, and hold that value in a field (preferably a BINARY field).
Then your INSPECT can look like this:
INSPECT BBRADD
TALLYING CONT for CHARACTERS
BEFORE INITIAL CITY ( 1 : length-of-data-plus-one )
However, then you have a problem if SEVILLA is actually in the start of the field.
So you make a small change, not to count characters which appear before it, but to count occurrences of it.
INSPECT BBRADD
TALLYING CONT for ALL
CITY ( 1 : length-of-data-plus-one )
Many people will instead code a PERFORM loop with reference-modification and do the test that way. With the final version of the INSPECT above have to code the termination logic yourself. For learning purposes it would be good to do it both ways.
When doing file-io, always use and check the FILE STATUS. Put your READ into a paragraph and perform it, you don't need two different pieces of code. If you use the FILE STATUS you don't need the AT END (or the END-READ) as the field you use to receive the FILE STATUS value will be "10" for end-of-file. Just use your 88 on that field, with the value of "10".
The Edit on your question now indicates where your existing 88-level is.
On the one hand, this is a good idea, because the end-of-file is associated with the record, and there can be no valid accidental content.
On the other hand, this is not a "portable" solution: if you use other COBOLs you may find that once end-of-file is reached it is no longer valid to access data under the FD. In the standard what happens in this situation is not defined, so you get differences amongst compilers.
You can retain the 88 on the group-item had have it portable by using READ ... INTO ... and having your record-layout in WORKING-STORAGE. This takes slightly longer to execute, as the data has to be transferred from one location to another.
I prefer the 88 on the FILE STATUS field and simplify the READ by being able to remove the AT END and END-READ. I already can't access the record-area under the FD so I can't accidentally get wrong values which look good.
I'm learning COBOL now and really liking the 88-type of variables, and I want to know if there are anything like them in another languages (most known languages also, such as C, Objective-C), even using a library.
The only thing I can think being similar is using
#define booleanResult (variableName==95)
But it isn't possible to set boolenResult to true and make variableName assume 95 as value.
05 nicely-named-data PIC X.
88 a-meangingful-condition VALUE "A".
88 another-meaingingful-condition
VALUE "A" "B"
"X" THRU "Z"
SPACE ZERO.
IF a-meaningful-condition
IF another-meaningful-condition
SET a-meaningful-condition TO TRUE
SET another-meaningful-condition
TO TRUE
The IFs test the value referenced by the data-name (conditional variable) that the 88 (condition name) is associated with, for a single value or one of multiple value, which can included ranges (THRU) and figurative-constants (ZERO, SPACE, LOW-VALUES, etc).
The SET, which in this form is a more recent addition to COBOL from the 1985 Standard, will change the value of the data-name to the first value specified on the 88, such that if you immediately referenced the 88 in a test, the test would be true.
COBOL does not have booleans in the sense of something resolving to 0 or 1, or anything else, being false/true.
Any language which supports Objects could be used to mimic the behaviour. Perhaps you've even done it already without really realising it.
As NealB points out in the comments, functions could be used (or a procedure, or a transfer of control to another module) but the data and references to it would not be together and protected from accidental mischief.
COBOL has great flexibility in defining data-structures. The 88-level is a powerful aid to maintaining and understanding programs, as well as writing them in the first place.
I don't know of another language which has a direct and natural element which is equivalent to this, but then there are lots of languages I don't know.
Again NealB makes an important point in the comments about the use of THRU/THROUGH to specify a range of values.
Care does need to be taken. Although the author may think that the data that they want to select can be represented by the range "010" THRU "090", they may not realise that what the compiler does is to include every single possible value in that range, by generating code for greater than or equal to "010" and less than or equal to "090".
If using THRU, ensure that your data cannot contain anything in the range which is not expected. If you mean "010" "020" "030" ... "090" that is fine, as long as the data is validated at its entry-point, so that it can never include any intervening values.
The classic example is "A" THRU "Z" on the Mainframe. We all know what the author means, but the compiler takes it literally. You cannot use "A" THRU "Z" on its own for validation, because in EBCDIC there are "gaps" between three groups of letters, and using "A" THRU "Z" would treat those gaps as true for a use of the 88.
Where the 88 level in some COBOL compilers does fall down, is in the missing "FALSE".
To re-use from the above example:
88 a-meaingingful-condition VALUE "A".
88 a-meaingingful-condition-NOT
VALUE "N".
To test the switch/flag, you use the first 88. To turn the flag.switch off, you have to use the second. Not ideal. See one of the links below for an example of FALSE on the 88 definition.
In olden times, flags/switches were set and reset with MOVE statements. As soon as the MOVE is involved, you have the same problem as you have in trying to use functions. There is no bound relationship between the MOVE and the 88-level VALUE.
These days, SET can be used to change the value of a field, to turn a flag/switch on or off.
05 FILLER PIC X.
88 a-meaingingful-condition
VALUE "A".
88 a-meaingingful-condition-NOT
VALUE "N".
The field being tested does not even need a name (it can be FILLER or omitted (an implied FILLER)).
Of course, as NealB points out in a comment on one of the links below, someone can still get at the field with a MOVE using reference-modification on a group item. So...
01 FILLER.
05 FILLER PIC X.
88 a-meaingingful-condition
VALUE "A".
88 a-meaingingful-condition-NOT
VALUE "N".
Now they can't use reference-modification even, as there is no field to name. The value of the field can only come from a VALUE clause on the definition, or from a SET statement setting one of the 88s to TRUE.
At the stage, the value that a flag/switch has, its actual value, becomes irrelevant.
01 FILLER.
05 FILLER PIC X(7).
88 a-meaingingful-condition
VALUE "APPLE".
88 a-meaingingful-condition-NOT
VALUE "BICYCLE".
Because nothing can be used to test against a literal/data-name, and the field cannot be the target of any verb except SET, you no longer have to check that all fields which say they contain N, or Y, or 0, or 1, do so, and they're not the wrong case, and no other values get placed in those fields.
I'm not suggesting the use of APPLE and BICYCLE, just using them to illustrate the point.
An 88 can also have a value expressed in hexadecimal notation, like any alpha-numeric field:
88 a-meaingingful-condition VALUE X"25".
An 88 can also be specified on a group item, typically with a figurative-constant as the value:
01 a-group-item.
88 no-more-data-for-matching VALUE HIGH-VALUES.
05 major-key PIC X(10).
05 minor-key PIC X(5).
In a file-matching process, the keys can be set to high-values at end-of-file, and the use of the keys will still cause the other file(s) to be processed correctly (keys lower than on this file).
Here are links to a number of questions from SO relating directly, or tangentially with important aspects, to 88-levels.
COBOL level 88 data type
Group variable in cobol
In Cobol, to test "null or empty" we use "NOT = SPACE [ AND/OR ] LOW-VALUE" ? Which is it?
Does a prefix of "NO" have any special meaning in a COBOL variable?
COBOL Data Validation for capital letter?
My first programming language was Cobol, now I am using c# and here is my solution to Cobol's 88 level:
In Cobol:
01 ws-valid-countries pic xx.
88 valid-country 'US', 'UK' 'HK'.
move ws-country to ws-valid-countries
if valid-country
perform...
in C#
string[] ValidCountries = {"US","UK","HK"} ;
if ( ValidCountries.Contains(newCountry.Trim().ToUpper()) )
{
// do something
Think of it as a boolean getter (essentially as in your macro) and a setter (forcing the variable to be the corresponding value). Who says COBOL wasn't modern in 1965?
As others said, just some object programming. Which is more powerful, but far less elegant. Like :
01 MY-DATASET.
05 MY-DEPARTEMENT PIC 9(2).
88 ILE-DE-FRANCE VALUES 75, 77, 78, 91 THRU 95.
Can be roughly translated in old VBA in a class named MyDataset :
Public MyDepartement As Integer
Property Get IleDeFrance() As Boolean
Dim MyArray() As Variant
MyArray = Array(75, 77, 78, 91, 92, 93, 94, 95)
IleDeFrance = UBound(Filter(MyArray, MyDepartement, True)) > -1
End Property
(just tested, it works on VBA-excel2013)
And I made the VBA as simple as possible, no clean getter or setter for the departement number, just a public data. As a class is a depot of data plus coded actions against them, you can do more things inside than a simple 88-level(that's probably why this feature didn't make up to more modern languages). But at a the price of complexity & readability.
Less elegant because the array has to be specifically defined, and testing presence in it has to be specified also. While it's inherent to the wonderful 88 level.