I'm attempting to do some data validation and am trying to use an if statement to see if what is in the variable ERROR-FLAG and RECORD-CODE is "NO" and "VC". Example below..
MOVE "NO" TO ERROR-FLAG.
MOVE "NO" TO ERROR-FLAG2.
IF VEND-NUM = SPACES
MOVE "YES" TO ERROR-FLAG
MOVE "********" TO BC-AST-OUT
MOVE "B" TO B-ERROR-OUT
END-IF.
IF VEND-NUM IS NOT NUMERIC AND ERROR-FLAG IS NOO
MOVE "YES" TO ERROR-FLAG
MOVE "********" TO BC-AST-OUT
MOVE "C" TO C-ERROR-OUT
END-IF.
IF RECORD-CODE IS NOT VC
MOVE "YES" TO ERROR-FLAG
MOVE "**" TO A-AST-OUT
MOVE "A" TO A-ERROR-OUT
END-IF.
NOO AND VC are defined in the working storage as "NO" and "VC" respectively. I can't seem to figure this out, any and all help is much appreciated! I'm not sure if it matters but VEND-NUM and RECORD-CODE are read in.
Error code
176 IGYPS2074-S "NOO" was defined as a type that was invalid in this context. The statement was discarded.
Same message on line: 195 205 210 224
181 IGYPS2074-S "VC" was defined as a type that was invalid in this context. The statement was discarded.
I want it to determine if ERROR-FLAG IS "NO" or not. If it's "NO" I want it to do the following move instructions for the if.
In your IF statements, you are using NOO and VC as though they were 88-level condition names or a "class test" that you can define using SPECIAL-NAMES.
As Bruce Martin has pointed out, one way is to use IS EQUAL TO/IS NOT EQUAL TO in your IF statements.
The clearer way is to actually use 88's. An example.
01 FILLER PIC X VALUE SPACE.
88 DATA-IN-ERROR VALUE "Y".
88 DATA-NOT-IN-ERROR VALUE "N".
SET DATA-NOT-IN-ERROR TO TRUE
IF VEND-NUM = SPACES
SET DATA-IN-ERROR TO TRUE
MOVE "********" TO BC-AST-OUT
MOVE "B" TO B-ERROR-OUT
END-IF
IF VEND-NUM IS NOT NUMERIC
AND DATA-NOT-IN-ERROR
...
END-IF
The names are for illustrating this, and can be improved for your situation.
A fuller example:
05 RECORD-CODE PIC XX.
88 RECORD-CODE-IS-VC VALUE "VC".
...
01 FILLER PIC X.
88 ERROR-FOUND VALUE "Y".
88 ERROR-FOUND-FALSE VALUE "N".
SET FIRST-ERROR-FOUND-FALSE TO TRUE
IF VEND-NUM = SPACES
SET FIRST-ERROR-FOUND TO TRUE
MOVE "********" TO BC-AST-OUT
MOVE "B" TO B-ERROR-OUT
END-IF
IF VEND-NUM IS NOT NUMERIC
AND FIRST-ERROR-FOUND-FALSE
SET ERROR-FOUND TO TRUE
MOVE "********" TO BC-AST-OUT
MOVE "C" TO C-ERROR-OUT
END-IF
IF NOT RECORD-CODE-IS-VC
SET ERROR-FOUND TO TRUE
MOVE "**" TO A-AST-OUT
MOVE "A" TO A-ERROR-OUT
END-IF
Further re-working:
EVALUATE TRUE
WHEN VEND-NUM = SPACES
MOVE "********" TO BC-AST-OUT
MOVE "B" TO B-ERROR-OUT
WHEN VEND-NUM NOT NUMERIC
MOVE "********" TO BC-AST-OUT
MOVE "C" TO B-ERROR-OUT
WHEN NOT RECORD-CODE-IS-VC
MOVE "**" TO A-AST-OUT
MOVE "A" TO A-ERROR-OUT
END-EVALUATE
If you need the error-code for elsewhere, simply insert the SET statement.
You are using NOO as if it was a CLASS. I will skip what a CLASS is here (but NUMERIC is a CLASS that regroup numeric values "0, 1, 2, 3, 4, 5, 6, 7, 8, 9" for example). I will just explain how to compile your code and make it easier to read and understand.
I will give you a solution for "ERROR-FLAG" and "NOO", it is the same for "RECORD-CODE" and "VC".
Here you want to test if the value of "ERROR-FLAG" is equal to "NOO". In COBOL you could literally write:
IF VEND-NUM IS NOT NUMERIC AND ERROR-FLAG IS EQUAL TO NOO
Also it is may be easier to read this way:
IF VEND-NUM IS NOT NUMERIC AND ERROR-FLAG = NOO
This is strictly the same.
In order to make your code more maintainable, I strongly recommend you to use parenthesis like this:
IF (VEND-NUM IS NOT NUMERIC) AND (ERROR-FLAG = NOO)
Finally, COBOL gives you a great tool: level-88 declaration. In your case, you could declare a level-88 value on ERROR-FLAG like this:
01 ERROR-FLAG PIC X(02).
88 ERROR-FLAG-NOO value 'NO'.
In this case, when "ERROR-FLAG" contains the value "NO", then ERROR-FLAG-NOO is true (it works like a boolean).
Your IF statement could then be:
IF (VEND-NUM IS NOT NUMERIC) AND (ERROR-FLAG-NOO)
That is for the first step: be able to compile your program and make it a bit easier to understand through level-88 values. In a second time, you could use an EVALUATE statement. In you second IF statement you are testing if ERROR-FLAG is "NO" because you don't want to do the second test if the first one is not correct. You could do:
EVALUTE TRUE
WHEN VEND-NUM = SPACES
...
WHEN VEND-NUM IS NOT NUMERIC
...
WHEN RECORD-CODE NOT = VC
...
WHEN OTHER
...
END-EVALUATE
In this case, if the first WHEN is true, the code following the when (which I wrote "...") will be executed. The following WHEN will not be tested and the EVALUATE statement will go to "END-EVALUATE". If the first WHEN statement is false, the second WHEN statement will be tested. And so on. If all the WHEN statements are false, the "WHEN OTHER" statement will always be executed. You can find documentation on EVALUATE statement fairly easily on the internet.
Related
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.
I realize I have asked a similar question before, but the whole thing is more complicated than I thought.
To cut to the chase, I need to convert a string that contains numbers and letters into a string that only contains numbers, while keeping the numbers that were already there, in the right position.
The letters need to be converted to their corresponding position in the Alphabet + 9. So, A = 10, B= 11.... Z = 35.
So, basically, a string that looks like this:
'GB00LOYD1023456789A1B2'
will have to become:
'161100212429131023456789101112'.
I bolded the letters in both examples so you can see the difference more clearly. Depending on the input, the content will be longer or shorter than this example. Letters will be alternated by numbers and vice versa.
What's the best way to do this?
What's the best way to do this?
That is a matter of opinion.
The REPLACING option of the INSPECT verb requires the replacing and replaced character strings to be the same size, so that's right out because you need to replace one character with two. This is true at least for IBM COBOL.
A way to do this would be to loop through your input string and do a class check on each character. Something like...
01 Stuff.
05 in-posn pic s999 packed-decimal value +0.
05 out-posn pic s999 packed-decimal value +1.
05 in-string pic x(022) value 'GB00LOYD1023456789A1B2'.
05 out-string pic x(100) value spaces.
05 replacer pic x(002) value spaces.
perform varying in-posn from 1 by 1
until in-posn > length of in-string
if in-string(in-posn:1) alphabetic
evaluate in-string(in-posn:1)
when 'A' move '10' to replacer
when 'B' move '11' to replacer
.
.
.
when 'Z' move '35' to replacer
end-evaluate
string replacer delimited size
into out-string
pointer out-posn
end-string
else
string in-string(in-posn:1) delimited size
into out-string
pointer out-posn
end-string
end-if
end-perform
There are variations available. You could replace the evaluate with a couple of table lookups. You could store the length of in-string before beginning the loop. You could store in-string(in-posn:1) rather than hoping the compiler will do that for you.
This is just freehand but I think it conveys the idea.
so my input file would be something like:
words words nothing important
words words nothing important
the quick brown fox 23
the quick brown fox 14
words words nothing important
words words nothing important
now, I'd like to be able to grab the 1st instance of "fox" and capture the "23" in WS-FIRST then grab the second instance of "fox" and capture "14" in WS-SECOND.
I'll be replacing "the quick brown fox" with a different string as well, but it's the same on both lines, so pretty easy.
The text is fixed in content and fixed in position and the number is also fixed in content, position and length.
01 field-we-are-about-to-change.
05 FILLER.
10 the-bit-you-want-to-change PIC X(length of that text, you count).
88 its-the-text-we-want VALUE ' the quick brown fox '.
10 our-numeric-value PIC XX.
10 FILLER PIC X(what is left of the input line).
01 WS-FIRST PIC XX.
01 WS-SECOND PIC XX.
01 FILLER PIC X VALUE "N".
88 first-not-found VALUE "N".
88 first-found VALUE "Y".
MOVE your-input TO field-we-are-about-to-change
IF its-the-text-we-want
MOVE replacement-text TO the-bit-you-want-to-change
IF first-not-found
SET first-found TO TRUE
MOVE our-numeric-value TO WS-FIRST
ELSE
MOVE our-numeric-value TO WS-SECOND
END-IF
END-IF
If the input is fixed, just use definitions to treat it as fixed. Lots of variations possible.
Your problem sounds like it would be well suited by a finite state machine or a simple parser. This sounds like homework, so I won't write the code for you, but I'll offer some hints that might point you in the right direction.
Process your input in a char by char loop, resist the urge to scan for words.
Compare your input at that char to the longest literal first, so "quick brown fox" will hit before "fox". This is called "greedy" scanning, and it is usually, but not always, the right way to go.
Unless your search is case insensitive, you must case fold the input with function upper/lower-case
When comparing to literals, place them first in the condion, this can make it easier to get a compiler error instead of a subtle bug in some cases and languages
So a main look might look like:
Perform Read-An-Input-Line
Perform until no-more-input
Perform varying II from 1 by 1
until II > length of Input-Line
Evaluate true
when 'the quick brown fox' = function lower-case( Input-Line (II:) )
...do replace for that string...
when 'fox' = function lower-case( Input-Line (II:) )
Move Input-Line (II + 5 : 2) to WS-Got-A-Number
End-Evaluate
End-Perform
Perform Read-An-Input-Line
End-Perform
I hope some of that helps.
INPUT: WS-Variable contains '345xABCx12'
Code: IF WS-Variable string contains 'ABC' Display ' SKIP!!!' Else Perform something.
If variable contains 'abc'
display skipped
else
process-para
end if.
You are looking for the INSPECT verb... Try something like...
IDENTIFICATION DIVISION.
PROGRAM-ID. EXAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 TESTDATA PIC X(50).
01 COUNTER PIC 9(4).
PROCEDURE DIVISION.
MOVE '12345XXABCXX12345' TO TESTDATA
MOVE ZERO TO COUNTER
INSPECT TESTDATA TALLYING COUNTER FOR ALL 'ABC'
IF COUNTER > 0
DISPLAY 'SKIP! ' TESTDATA
ELSE
DISPLAY 'DONT SKIP ' TESTDATA
END-IF
MOVE '12345XXZZZXX12345' TO TESTDATA
MOVE ZERO TO COUNTER
INSPECT TESTDATA TALLYING COUNTER FOR ALL 'ABC'
IF COUNTER > 0
DISPLAY 'SKIP! ' TESTDATA
ELSE
DISPLAY 'DONT SKIP ' TESTDATA
END-IF
GOBACK
.
Did you try something like:?
If variable equal to 'abc'
display "skipped"
else
perform process-para
end-if
This would assume variable is defined as PIC XXX or X(3).
If that does not suit, please update your question with a fuller description, some sample input, expected output and what you have tried.
Now it turns out you are looking for 'abc' at a variable location within a piece of data.
There are several ways to do it.
Easiest is
INSPECT field-you-want-to-look-at
TALLYING a-count
FOR ALL value-you-want-to-search-for
a-count can be defined as BINARY PIC 9(4). value-you-want-to-search-for as PIC XXX VALUE 'abc'.
MOVE ZERO TO a-count before the INSPECT.
After the INSPECT you can test a-count which will tell you how many occurences of value-you-want-to-search-for there are in field-you-want-to-look-at.
The reason to use a data-definition (PIC XXX) instead of a literal ('abc') is for ease of maintenance and understanding. There may be more than one place where 'abc' is required in the program, and it may mean the same thing in both places, or one thing in one place and something else in another. With a data-name from the definition you can describe what 'abc' means in each instance. If the value of 'abc' (or one of the 'abc's) needs to change, there is only one place it needs to be changed - in the working-storage.
If (and assuming Enterprise COBOL on the Mainframe due to the COOLGEN reference) you use compiler option OPT(STD) or OPT(FULL) a data-name which is referenced but is never the "target" of anything, ie it has a constant value, is treated as a constant. So you get a named constant as well.
INSPECT FLIGHTPLAN-REFERENCE
TALLYING NO-OF-ENTRIES-TO-EU-AIRSPACE
FOR ALL EU-FLIGHTPLAN-CODE
Is much more easy to understand than
INSPECT VAR1 TALLYING A-COUNT FOR ALL 'abc'
I'm in my second quarter of college and taking "Advanced COBOL" we just received an assignment that requires us to code in some validation procedures for different data. I have everything done except on small validation procedure.
There is a field called "PART-NUMBER" that is 8 bytes long. The first 5 columns must be a number. The 6th column must be a capital letter and the last 2 columns must be in the range of 01-68 or 78-99. The only problem I have is figuring out how to validate that the 6th column is capital.
Here is the code I am using:
From working storage:
01 DETAIL-LINE.
05 PART-NUMBER.
10 PART-FIRST-FIVE-DL PIC X(5).
10 PART-LETTER-DL PIC X.
88 CAPITAL-LETTER VALUE 'A' THRU 'Z'.
10 PART-LAST-TWO-DL PIC XX.
From 300-VALIDATE-PART-NUMBER
EVALUATE PART-LETTER-DL ALPHABETIC
WHEN TRUE EVALUATE CAPITAL-LETTER
WHEN FALSE MOVE 'YES' TO RECORD-ERROR-SWITCH
MOVE 'PART NUMBER' TO FIELD-NAME
MOVE PART-NO-IN TO FIELD-VALUE
MOVE 'YES' TO PART-NO-ERROR
END-EVALUATE
WHEN FALSE MOVE 'YES' TO RECORD-ERROR-SWITCH
MOVE 'PART NUMBER' TO FIELD-NAME
MOVE PART-NO-IN TO FIELD-VALUE
MOVE 'YES' TO PART-NO-ERROR
END-EVALUATE
I know I'm probably not doing this in a very efficient way but for now I just need to get it to work. I've read the whole chapter on data validation from the book and this is sort of a last minute error (program is due tomorrow) so the teacher is unavailable. I would greatly appreciate any help I can get with this. I'm really lost on how I'm supposed to validate capital letters. The method I'm using now reports an error if anything other than A or Z is in the 6th column of the part number.
I don't see anything fundamentally wrong with your code. I put it into a
driver program, compiled and ran it. I got the expected results: Error reported only
when the 6th character of PART-NUMBER was not an upper case letter.
Your COBOL coding style is very different from what I am used to seeing (not wrong, just
different).
Most veteran COBOL programmers would code something like:
IF PART-LETTER-DL IS ALPHABETIC AND
CAPITAL-LETTER
CONTINUE
ELSE
MOVE 'PART NUMBER' TO FIELD-NAME
MOVE PART-NO-IN TO FIELD-VALUE
MOVE 'YES' TO PART-NO-ERROR
END-IF
The IF applies both of your edit criteria and does nothing if both pass (CONTINUE), otherwise
an error is reported (ELSE part). The above does essentially the same thing your code
example does except using IF as opposed to EVALUATE.
I give you full marks for testing both ALPHABETIC and capital letter
using an 88 level range (THRU). A lot of programmers would only use the 88 level, making the
implicit assumption that 'A' THRU 'Z' covers only alphabetic characters - this is dead wrong
in some environments (EBCDIC character sets in particular).
P.S. I see you guys must have the same teacher that Kimmy had!
One thing you should be concerned about is the "Value 'A' thru 'Z'". It will only work on ASCII machines.
If you actually code Value 'A', 'B', 'C', ... 'Z'. It will work on all platforms.
For capital letters you can test the ALPHABETIC-UPPER condition:
IF PART-LETTER-DL NOT EQUAL SPACE AND PART-LETTER-DL IS ALPHABETIC-UPPER
...
END-IF.
ALPHABETIC-LOWER can be used too, but remember that SPACE is considered ALPHABETIC, so testing SPACE is necessary, if you just want capital letters.
For EBCDIC, drop the ALPHABETIC test and just use the 88:
88 CAPITAL-LETTER VALUE 'A' THRU 'I'
'J' THRU 'R'
'S' THRU 'Z'.
Specifying individual letters works, but generates 26 comparisons! The above generates three. The ALPHABETIC plus 'A' THRU 'Z' only two, but does carry some in-built confusion (space is alphabetic, and the THRU includes non-printable digits in the range X'C1' to X'E9').