logical.expressions.in.acodes Article/Article

logical.expressions.in.acodes

Command logical.expressions.in.acodes Article/Article
Applicable release versions:
Category Article (24)
Description constructing logical expressions in "A" processing codes.

contributed by Malcolm Bull. Original article ran in PickWorld Magazine.

In terms of value for money, the A processing code is unrivaled on R83, and is beaten only by the Advanced Pick CALL processing code, as discussed elsewhere in this issue. The A code can perform the actions of a great many other codes:

A1:" ":2 is equivalent to C1;" ";2

A2["1","3"] is equivalent to T1,3

A(1+2)/(3-4) is equivalent to, and considerably easier to write than F;1;2;+;3;4;-;/

and together with the additional fact that the A code can itself directly apply other conversion codes:

A(2*3/"100")(MD2) A3(G1*1)(MTS) A4(G3 1)(MCT)

give it tremendous power.

In this article, I want to look particularly at the logical capabilities of the A code. This form of the A code allows logical comparisons or tests to be made, and a value of 1 (=true) or 0 (=false) will be returned according to the result of that comparison. For example, the code:

A4=5

would return a value of 1 (true) if attribute 4 of a data item were equal to attribute 5, otherwise a value of 0 (false) would be returned. Another example:

AN(QTY)>"0"

would return a value of 1 if the QTY is greater than 0, otherwise a value of 0 will be returned.

In the logical A code, the operators are:

# not equal to
< less than
<= less than or equal to
= equal to
> greater than
>= greater than or equal to

The logical A code can be used as shown here to output a value of 1 or 0, but there are ways of exploiting this further. One possible application might be in a situation where it is required to display today's date if attribute 7, say, were blank, otherwise to display the data held in attribute 8. This might be achieved with a processing code such as:

A(7="")*D+(7#"")*8

If the result were to be further converted to external Date format, this code could be modified to:

A((7="")*D+(7#"")*8))(D)

This will only work successfully for numeric data. For general data, we might use the form:
A("HIGH"["1",(2>5)*"99"]):("OK"["1",(2=5)*"99"]):("LOW"["1",(2<5)*"99"])

On implementations such as Reality and Ultimate (and AP. ed.), the AIF correlative offers alternative means of testing data values and taking appropriate action. For example, the correlative:

AIF 2<5 THEN "LOW" ELSE "OK"

will compare the contents of attribute 2 with those of attribute 5 and output either the word LOW or the word OK according to the result of the comparison. We might need this in a situation where attribute 2 contains the actual stock level of our products, and attribute 5 contains the minimum permissible stock level. In a more complex case, we might use the AIF correlative:

AIF 2=5 THEN "OK" ELSE IF 2<5 THEN "LOW" ELSE "HIGH"

which will further output one of the words HIGH, LOW or OK according to the relative sizes in attributes 2 and 5. On R83 implementations which do not offer AIF, the user must produce these results by exploiting the simple logical operations shown above.

The reports in Figure 1 illustrate how we might perform the logical test:

IF 2 < 5 THEN print 'LOW' ELSE print 'OK'

by means of the A code:

A"OK LOW"[(2<5)*"3"+"1","3"]

returning either of the words OK or LOW, according to the values of attributes 2 and 5. It is worth taking a closer look at the action of the code:

(1) We start with a literal "OK LOW" holding the pieces of text which we wish to display as substrings. Pay particular attention to the fact that this is made up of two substrings "OK " and "LOW" both 3 characters in length.

(2) We first compare attribute 2 with attribute 5:

2<5

returning a value of 1 if attribute 2 is less than attribute 5, or 0 otherwise (if 2 is greater than or equal to 5).

(3) We then multiply the result (1 or 0) by 3 (the length of each substring) to produce a result of either 3 or 0, and then we add 1:

(2<5)*"3"+"1"

to find the starting position of the required substring within the literal string, to produce 4 (if the condition is true) or 1 (if the condition is false).

(4) Finally, starting at this calculated position (1 or 4), we extract 3 characters from the literal string.

"OK LOW"[(2<5)*"3"+"1","3"]

to return either OK (if attribute 2 is greater than or equal to attribute 5) or LOW (if attribute 2 is lower than attribute 5).

The same result could have been produced by longer, but possibly more intelligible form:

A("LOW"["1",(2<5)*"99"]):("OK"["1",(2>=5)*"99"])

If, say, attribute 2 (the actual stock level) were multi-valued and attribute 5 (the minimum stock level) were just a single value, then we should use the form:

2<5R

in each case in order to re-use the single value in attribute 5 with each of attribute 2's multi-values.

Figure 1

LIST STOCK DESCRIPTION QTY MINIMUM LEVEL2

STOCK.. Description.............. QTY MINIMUM... LEVEL

1000 DESK, GREEN-BLUE, ASH 8 30 LOW
2000 SETTEE, YELLOW, OAK 18 30 LOW
3200 SIDEBOARD, NEUTRAL, ASH 15 15 OK
4200 SETTEE, GOLD, ASH 55 30 OK
8763 SIDEBOARD, MAROON, ASH 10 15 LOW
9000 SIDEBOARD, YELLOW, ASH 99 15 OK

SORT STOCK BY-EXP LEVEL2 "LOW" DESCRIPTION QTY MINIMUM LEVEL2

STOCK.. Description.............. QTY MINIMUM... LEVEL

1000 DESK, GREEN-BLUE, ASH 8 30 LOW
2000 SETTEE, YELLOW, OAK 18 30 LOW
4200 SETTEE, GOLD, ASH 10 30 LOW
8763 SIDEBOARD, MAROON, ASH 10 15 LOW
8763 SIDEBOARD, MAROON, ASH 5 15 LOW
8763 SIDEBOARD, MAROON, ASH 0 15 LOW

I use a further - albeit somewhat arcane - application of this same technique to produce reports on a file called ACC.SAVES which records the details of my account-save diskettes. Each item on the file is the name of an account which is (or has been) on my system. A report on the ACC.SAVES file shows the name of the account, the rotation number of the latest account-save diskette, the date when the diskette was produced and an indication of whether or not the account is still on the system. It is this last piece of data, produced by the definition ON/OFF which employs the code:

A"OFFON"[((0(TSYSTEM;X;;1)(T1,1)="D")*"3"+"1"),"3"]

which is illustrated in Figure 2. I leave you to work out exactly how the code checks the entry on the SYSTEM file to see whether or not there is a D-pointer, indicating whether or not each particular account is currently on the system.

Figure 2.

SORT ACC.SAVES PRODUCED LATEST ON/OFF

ACC.SAVES........ Produced............. Latest ON/
disk OFF

ACCESS.TEXT 19:11:53 02 NOV 1992 2 ON
DBASE.TEXT 19:22:51 02 NOV 1992 1 ON
DRILLS 16:48:54 18 JUN 1992 1 OFF
MALCOLM 19:02:56 02 NOV 1992 1 ON
PROGRAMS 19:32:59 02 NOV 1992 1 ON
SB+.DEFN 19:08:26 02 NOV 1992 1 ON
SYSPROG 19:11:53 02 NOV 1992 1 ON
SYS.DEV.TEXT 18:09:17 13 JUL 1992 1 OFF

In such cases, we could pick up a simpler substring, as with:

A"XY"[(2<5)*"1"+"1","1"]

and then use the result (X or Y) to return either of two messages held with item-ids X and Y on a text file:

A("XY"[(2<5)*"1"+"1","1"])(TMESSAGES;X;;1)

This is particularly convenient when if the text of the messages is likely to change.

We can extend the previous reasoning and use an alternative technique to return one of three possible values.

Imagine a situation in which we need to carry of the following tests:

If 2 < 5, then output "LOW"

If 2 = 5, then output "OK"

If 2 > 5, then output "HIGH"

In the solution shown in Figure 3, I have established three definitions, called LOW, OK and HIGH which use processing similar to that of the previous example to return null or a string according to the relative values of attributes 2 and 5. A third definition, LEVEL3, then concatenates the results of the other three definitions. Since two of these will always return null, only the required string will be output.

LOW OK
001 A A
002 2 2
003 LOW OK
004
005
006
007
008 A"LOW"["1",(2<5)*"3"] A"OK"["1",(2=5)*"2"]
009 L L
010 1 1

HIGH LEVEL3
001 A A
002 2 2
003 HIGH LEVEL
004
005
006
007
008 A"HIGH"["1",(2>5)*"4"] AN(LOW):N(OK):N(HIGH)
009 L L
010 1 1

Figure 3

LIST STOCK DESCRIPTION QTY MIN LOW OK HIGH LEVEL3

STOCK. Description............. Qty Min Low OK High Level

1000 DESK, GREEN-BLUE, ASH 8 30 LOW LOW
2000 SETTEE, YELLOW, OAK 18 30 LOW LOW
3200 SIDEBOARD, NEUTRAL, ASH 15 15 OK OK
4200 SETTEE, GOLD, ASH 55 30 HIGH HIGH
8763 SIDEBOARD, MAROON, ASH 10 15 LOW LOW
9000 SIDEBOARD, YELLOW, ASH 99 15 HIGH HIGH

The same result could have been produced by the longer, but possibly clearer form:

A("HIGH"["1",(2>5)*"4"]): ("OK"["1",(2=5)*"2"]): ("LOW"["1",(2<5)*"3"])

or even:

A("STOCK LEVEL HIGH"["1",(2>5)*"99"]): ("STOCK LEVEL AT EVENS POSITION"["1",(2=5)*"99"]): ("!! STOCK LEVEL BELOW MINIMUM !!"["1",(2<5)*"99"])

When any condition is false (and returns a value of 0), we take a substring of length 0 (that is, a null value); when a condition is true (and returns a value of 1), we take a substring of length other than 0. These three substrings are then concatenated to produce the output result.

Like all processing codes, these must, of course, all be written on one attribute of the definition.

There may be a situation where output data is held on any of several possible files. For example, we may have a file of invoic es of which the customer code may identify a USA customer on the USA.CUST file or a British customer on the UK.CUST file, or a customer on the OSEAS.CUST file. We can pick up the customer name from the appropriate file by any of the codes:

A((11(TUSA.CUST;C;;1))(TUK.CUST;C;;1))(TOSEAS.CUST;C;;1) or: TUSA.CUST;C;;1]TUK.CUST;C;;1]TOSEAS.CUST;C;;1 or: F;11;(TUSA.CUST;C;;1);(TUK.CUST;C;;1);(TOSEAS.CUST;C;;1)

Each of these codes will pick up the name in attribute 2 of the USA.CUST file; if there is no such item on USA.CUST then it will go on to pick up the name from UK.CUST; if there is no such item on UK.CUST then we use OSEAS.CUST, and so on.

The action of these codes is fairly self-explanatory: we first translate the original code against USA.CUST and return either the customer's name (if it is on the USA.CUST file) or the original code (if it is not on the USA.FILE, since we use the C subcode in the Tfile code). This result is then used as input data to trans late against UK.CUST; this will return either the input data (the original code or the name from USA.CUST) or the translated name from UK.CUST, and so on. This process can be repeated indefinitely for a number of such files.

The solution assumes that the data returned from any of the files (a customer name, in this particular situation) is not a valid key to any of the later files in the sequence.

Since the A code does not provide a facility for the logical AND and OR operators, this must be achieved by some other means. A little mathematical thought will reveal how we might transform the two values 1 and 1 into 1 (to simulate AND) and the two values 1 and 0 into 1 (to simulate OR). For example, we may use an element such as:

(2<3)*(4>5)

to simulate the condition when attribute 2 is less than attribute 3 AND attribute 4 is greater than attribute 5. This will return a value 1 only when both conditions obtain, otherwise it will return a value of 0. This value can then be used in the manner described above. The element:

((2<3)+(4>5))>"0"

can be used to simulate the condition when attribute 2 is less than attribute 3 OR attribute 4 is greater than attribute 5. This will return a value 1 when either condition is true, otherwise it will return a value of 0.

Constructions of this form are illustrated in Figure 4. The defi nition AND.TEST which uses a A code such as:

A(2>"90")*(5="15")

returns a value of 1 or 0, according to whether or not attribute 2 is greater than 90 and attribute 5 is equal to 15. The definition OR.TEST which uses an A code such as:

A((2>"90")+(5="15"))>"0"

returns a value of 1 or 0, according to whether or not attribute 2 is greater than 90 or attribute 5 is equal to 15.

Figure 4

SORT STOCK *A2 *A5 AND.TEST OR.TEST

STOCK..... *A2....... *A5....... AND OR
TEST TEST

1000 8 30 0 0
2000 18 30 0 0
3200 15 15 0 1
4200 55 30 0 0
8763 10 15 0 1
9000 99 15 1 1

We could then apply these forms in any of the contexts which we introduced earlier. For example:

A("LEVEL < 0"["1",(2<"0")*"99"]): ("LEVEL IN RANGE 0-10"["1",(2>="0")*(2<="10")*"99"]): ("LEVEL IN RANGE 10-20"["1",(2>="10")*(2<="20")*"99"]): ("LEVEL IN RANGE 20-30"["1",(2>="20")*(2<="30")*"99"]): ("LEVEL > 30"["1",(2>="30")*"99"])

I hope that the variety of techniques - and especially the alternative methods - which I have presented in this article might inspire you to pursue A codes and to get Access to do more and more work for you and your organization. Access is always capable of doing just that bit more than you think.
Syntax
Options
Example
Purpose
Related