01 ws-var-05 pic x value 'n'.
88 ws-var-88 value 'y'
01 ws-var-2 pic 9 value 1.
88 ws-var-88-2 value 2.
.
.
.
* comment ws-var-88-2 is set to true when eof is
* reached in the at end clause of read statement
* need to understand when ws-var-05 evaluates to
* true! Is this right syntax? What happens if we use
* this syntax? Need to understand if this is a
* defect
Perform 1000-para until ws-var-88-2 or ws-var-05.
The line...
Perform 1000-para until ws-var-88-2 or ws-var-05.
...contains a syntax error, at least when compiled with GNU COBOL 1.1.0.
The UNTIL clause of the PERFORM verb can contain conditional expressions. One type of conditional expression is a "condition-name condition" which is an 88-level. However, the name of an identifier (in this case ws-var-05) must be followed by a conditional operator (<, >, =, etc.) and then either another identifier or a literal for the UNTIL clause to be valid.
Is this right syntax?
No.
What happens if we use this syntax?
A compile-time error will occur.
Several things.
It is very much a "best practice" to leave the storage that backs your conditional items (88s) unnamed. This prevents people from accidentally moving the wrong value into them and causing subtle bugs.
For example, this:
01 ws-var-05 pic x value 'n'.
88 ws-var-88 value 'y'
01 ws-var-2 pic 9 value 1.
88 ws-var-88-2 value 2.
Is better written as this:
01 pic x value 'n'.
88 ws-var-88 value 'y'.
01 pic 9 value 1.
88 ws-var-88-2 value 2.
So nobody can ever accidentally do a "Move 'Y' to ws-var-05". This is especially problematic on mainframes, where there are some automatic uppercasing editor options that could really make the difference between 'y' and 'Y' hard to notice.
In the case of this, there are several things, the ws-var-05 is not a complete conditional, you could use "ws-var-88 or ws-var-88-2" and it would be a complete conditional.
You don't need a period at the end, and its use can often be problematic with modern compilers. Since Cobol-85, the END-verbname is the preferred way to terminate a command. You could write this:
Perform 1000-para until ws-var-88-2 or ws-var-05.
as:
Perform 1000-para until (ws-var-88 or ws-var-88-2)
or, IMNSHO, a clearer and cleaner approach, as it separates your terminate condition from the commands you will be executing in the body of the perform:
Perform until (ws-var-88-2 or ws-var-88-2)
Perform 1000-para
End-Perform
I will also always use parens to delimit my intended order of operations, though in this case, it matters not. For more complicated conditions, they can be a big help for the on-call programmer that has to look at something blowing up at 3am.
You trigger your terminal conditionals like so:
1000-para.
Read myfile
at end
set ws-var-88-2 to true
End-Read
If (something-else = time-to-quit)
set ws-var-88 to true
End-If
Exit. *> One of the few places you need a period
*> in modern cobol, adding the "Exit" or
*> "Continue" NOPs clearly calls out to a
*> reader that you are using a period.
Related
I'm building a COBOL program to calculate the average of up to 15 integers. The execution displays a number that is far bigger than intended with a lot of trailing zeroes. Here is the relevant code:
Data Division.
Working-Storage Section.
01 WS-COUNTER PIC 9(10).
01 WS-INPUT-TOTAL PIC 9(10).
01 WS-NEXT-INPUT PIC X(8).
01 WS-CONVERTED-INPUT PIC 9(8).
01 WS-AVG PIC 9(8)V99.
Procedure Division.
PROG.
PERFORM INIT-PARA
PERFORM ADD-PARA UNTIL WS-COUNTER = 15 OR WS-NEXT-INPUT = 'q'
PERFORM AVG-PARA
PERFORM END-PARA.
INIT-PARA.
DISPLAY 'This program calculates the average of inputs.'.
MOVE ZERO TO WS-COUNTER
MOVE ZERO TO WS-INPUT-TOTAL
MOVE ZERO TO WS-AVG.
ADD-PARA.
DISPLAY 'Enter an integer or type q to quit: '
ACCEPT WS-NEXT-INPUT
IF WS-NEXT-INPUT NOT = 'q'
MOVE WS-NEXT-INPUT TO WS-CONVERTED-INPUT
ADD WS-CONVERTED-INPUT TO WS-INPUT-TOTAL
ADD 1 TO WS-COUNTER
END-IF.
AVG-PARA.
IF WS-COUNTER > 1
DIVIDE WS-INPUT-TOTAL BY WS-COUNTER GIVING WS-AVG
DISPLAY 'Your average is ' WS-AVG '.' WS-NEXT-INPUT
END-IF.
The reason I put WS-NEXT-INPUT as alphanumeric and move it to a numeric WS-CONVERTED-INPUT if the IF condition is satisfied is because I want it to be able to take "q" to break the UNTIL loop, but after the condition is satisfied, I want a numeric variable for the arithmetical statements. Here's what it looks like with the numbers 10 and 15 as inputs:
10is program calculates the average of inputs.
Enter an integer or type q to quit:
15
Enter an integer or type q to quit:
q
Your average is 1250000000.
The console is a bit buggy so it forces me to input the 10 in that top left corner most of the time. Don't worry about that.
You see my problem in that execution. The result is supposed to be 00000012.50 instead of 1250000000. I tried inserting a few of my other variables into that display statement and they're all basically as they should be except for WS-INPUT-TOTAL which with that combination of numbers ends up being 0025000000 instead of 0000000025 as I would have expected. Why are these digits being stored in such a weird and unexpected way?
You have that strange output because of undefined behavior - computing with spaces.
The MOVE you present has the exact same USAGE and same size - it will commonly be taken over "as is", it normally does not convert the trailing spaces by some magic, so WS-CONVERTED-INPUT ends up with 10 . As the standard says for the move:
De-editing takes place only when the sending operand is a numeric-edited data item and the receiving item is a numeric or a numeric-edited data item.
and if it would be an edited field then it still should raise an exception on the MOVE:
When a numeric-edited data item is the sending operand of a de-editing MOVE statement and the content of that data item is not a possible result for any editing operation in that data item, the result of the MOVE operation is undefined and an EC-DATA-INCOMPATIBLE exception condition is set to exist.
When computing with spaces you commonly would raise a fatal error, but it seems your compile does not have that activated (and because you didn't share your compile command or even your compiler, we can't help with that).
Different COBOL dialects often use (partial only when checks are not activated which would lead to an abort) zero for invalid data, at least for spaces (but they can use everything. This will then lead to WS-CONVERTED-INPUT "seen as" 10000000 - so your computation will then include those big numbers.
So your program should work if you enter the necessary amount of leading zeroes on input.
General:
"never trust input data - validate" (and error or convert as necessary)
at least if something looks suspicious - activate all runtime checks available, re-try.
Solution - Do an explicit conversion:
MOVE FUNCTION NUMVAL(WS-NEXT-INPUT) TO WS-CONVERTED-INPUT, this will strip surrounding spaces and then convert from left to right until invalid data is found. A good coder would also check up-front using FUNCTION TEST-NUMVAL, otherwise you compute with zero if someone enters "TWENTY".
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...
As I'm new to cobol, please help me with the below piece of code.
WORKING-STORAGE SECTION.
01 BAS-REC.
02 INPT-REC.
49 INPT-LEN PIC S9(4) COMP.
49 INPT-TEXT PIC X(150).
02 INPT1-REC.
49 INPT1-LEN PIC S9(4) COMP.
49 INPT1-TEXT PIC X(150).
02 INPT2-REC.
49 INPT2-LEN PIC S9(4) COMP.
49 INPT2-TEXT PIC X(150).
77 VAR1 PIC X(5) VALUE 'APT'.
77 NUM1 PIC 9(1).
I'm using the level 49 for character varying here (to truncate trailing spaces)
Then I have cursor fetch.
After few modification under PROCEDURE DIVISION I'm doing the below.
PERFORM UNTIL SQLCODE=100
PERFORM VARYING NUM1 FROM 1 BY 1 UNTIL NUM1=6
STRING INPT-REC DELIMITED BY ' ',' ',
VAR1 DELIMITED BY ' ',' '
NUM1 DELIMITED BY ' ' INTO INPT2-REC
EXEC SQL
insert query here (which will run 5 times)
END-EXEC
END- PERFORM
END- PERFORM
but in the table the data got inserted only once but it shold have got inserted 5 times and also the INPT2-REC hasn't been concatenated. The INPT2 -REC just contains the value of INPT-REC alone
My question is this a special characteristic of level 49 or am I wrong somewhere?
Note that if you use INPT-REC2 as a host-variable for a VARCHAR-field you will only see the part from INPT-REC since you never update the length-field: it still contains the length it was assigned from INPT-REC.
So you'll have to somehow get the actual length of INPT2-TEXT (e.g. INSPECT the REVERSE of INPT2-TEXT for LEADING SPACES) and move it to INPT2-LENGTH before your EXEC SQL.
As I already said in my comment: there is nothing special about level 49 - you could as well use 48, 33,30 or 05 with the same results. The samples in the DB2 manual probably use 49 since it is the last valid level-number without any special meaning, so it is least likely to cause problems with any level-numbers already used in the program.
As for the query being executed only once: in your loop you are varying NUM1 but are checking whether I=6 - since we don't see I anywhere in your example I can only guess that it is already equal to 6 upon entering the loop.
Level 49 can be treated specially when Embedded SQL is involved, depending on system; this text is copied from the IBM Knowledge Center
Host structure declarations in COBOL must satisfy the following requirements:
COBOL host structures can have a maximum of two levels, even though the host structure might occur within a structure with multiple levels. However, you can declare a varying-length character string, which must be level 49.
A host structure name can be a group name whose subordinate levels name elementary data items.
If you are using the DB2® precompiler, do not declare host variables or host structures on any subordinate levels after one of the following items:
A COBOL item that begins in area A
Any SQL statement (except SQL INCLUDE)
Any SQL statement within an included member
When the DB2 precompiler encounters one of the preceding items in a host structure, it considers the structure to be complete.
So, this seems like a little implementation detail (level 49 for var char) that may spill over into other implementations of COBOL ESQL. Like many details buried in systems, Knowing that would require knowing that.
This particular detail is news to me as of a few minutes ago.
Looking more just now, this came up in the esqlOC contribution for GnuCOBOL recently. A level 49 specific tweak to ensure there was no need to worry about little end big end storage between host and service. So it seems to be a thing.
And an answer to the original question is; depends on compiler environment and ESQL preprocessor, but yeah maybe level 49 fields can be used for VARCHAR.
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'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.