Thursday, 27 August 2020

Beware of the dollar sign (continued)

I have found a much simpler method of obtaining a naked dollar sign to be inserted within a string, using ENTMESSAGE. One defines a message number whose text is simply $. Here is the code which (to me, at least) is self explanatory.
:PAR1 = 'TEST@TEST.COM'; SELECT ENTMESSAGE ('$', 'P', 10) INTO :PAR2 FROM DUMMY; /* $ */ :PAR3 = STRCAT (STRPIECE (:PAR1, '@', 1, 1), :PAR2, STRPIECE (:PAR1, '@', 2, 1)); WRNMSG 99;
As a result of these statements, PAR1 will be TEST@TEST.COM, PAR2 will be $, and PAR3 will be TEST$TEST.COM. Using ENTMESSAGE is a better method as it relies only on itself - it doesn't rely on a specific value in the CURRENCIES table (which might not exist) nor does it rely on an entry in a special constants table.

Note that the ENTMESSAGE statement uses '$' to denote 'the current procedure'.

Monday, 24 August 2020

Beware of the dollar sign!

At a company for which I am doing piecework programming, I was asked to fix a procedure that was giving an error message about being unable to create a file. I looked at the procedure (one which prints invoices); the problematic code was trying to create a string whose contents - basically the invoice number and customer email - would be displayed as a barcode in the final document.

Why is this problematic? For those who don't know, the character set that can be displayed in a barcode is severely limited to upper case characters, digits and a few characters such as * and $, but not @. Email addresses have the '@' character, so the original programmer had to find a way of replacing this with the '$' character.

In order to achieve this, the original programmer used a baroque set up of creating the initial string (with '@'), writing it to a file, then running the FILTER program three times (!) in order to get the file contents (i.e. the string) into the required format; one run replaced @ with $, one run turned the entire string into upper case; I don't know (nor care) what the third run did. This file was then loaded via an interface into a simple table from which the string was extracted and eventually displayed. Breathtaking in its ingenuity but totally misguided. To be fair, the procedure might have been written for a much earlier version of Priority in which certain functionality might be missing.

The actual problem that the client faced was that the intermediate files were being written to the root of disk C: which is of course a no-no. I simply changed the file directory and the procedure started working. But the sheer complication of this procedure irked me and I was sure that I could find a simpler way of creating the required string.

Here is my first attempt:
SELECT TOUPPER (CUSTOMERSA.EMAIL) INTO :EMAIL FROM CUSTOMERSA, INVOICES WHERE CUSTOMERSA.CUST = INVOICES.CUST AND INVOICES.IV = :$.IV; SELECT STRCAT ('*E', IVNUM, STRPIECE (:EMAIL, '@', 1, 1), '$', STRPIECE (:EMAIL, '@', 2, 1), '*') INTO :$.BC FROM INVOICES WHERE IV = :$.IV;
If the invoice number were IV200001 and the email test@microsoft.com, the resulting string would be expected to be '*EIV200001TEST$MICROSOFT.COM*'. Unfortunately this would not be the result. Using the naked dollar sign ('$') causes the preprocessor in the parser to replace this with the name of the procedure, resulting in something like 
'*EIV200001TESTTEST_WWWSHOWCIV2MICROSOFT.COM*'! This is normally a good thing as it enables one to pass the current procedure name to the ENTMESSAGE function as $, without denoting the name - as a result, the same code can be copy/pasted between procedures without problem.

A method of obtaining the naked dollar sign without using the naked dollar sign is required! After some lateral thinking, I came up with the following, but unfortunately it too inserts the first letter of the procedure's name instead of the dollar sign.
SELECT TOUPPER (CUSTOMERSA.EMAIL) INTO :EMAIL FROM CUSTOMERSA, INVOICES WHERE CUSTOMERSA.CUST = INVOICES.CUST AND INVOICES.IV = :$.IV; :DOLLAR = '1$1'; SELECT STRCAT ('*E', IVNUM, STRPIECE (:EMAIL, '@', 1, 1), STRIND (:DOLLAR, 2, 1), STRPIECE (:EMAIL, '@', 2, 1), '*') INTO :$.BC FROM INVOICES WHERE IV = :$.IV;
Incidentally, I don't see the above as being programming in Priority; it's got nothing to do with working with a database engine. Instead, it's more general programming in the context of the Priority programming language, which isn't something that can be easily taught. One has to remember that in a procedure, '$' is going to be expanded into the procedure's name.

I finally figured out how to solve the problem - use the code of currency -2, which is ... $ (at least, in companies where the default currency is NIS; for companies where the default currency is dollars, the number is -1). The below works in a test procedure on my server, but I will have to implement it fully on the client's server in order to be sure.
SELECT TOUPPER (CUSTOMERSA.EMAIL) INTO :EMAIL FROM CUSTOMERSA, INVOICES WHERE CUSTOMERSA.CUST = INVOICES.CUST AND INVOICES.IV = :$.IV; SELECT STRCAT ('*E', INVOICES.IVNUM, STRPIECE (:EMAIL, '@', 1, 1), CURRENCIES.CODE, STRPIECE (:EMAIL, '@', 2, 1), '*') INTO :$.BC FROM INVOICES, CURRENCIES WHERE INVOICES.IV = :$.IV AND CURRENCIES.CURRENCY = -2;
The above code did not work on the client's server as they have defined the code of currency -2 to be USD. Tired of knocking my head against a brick wall, I swiftly defined a personal table of constants, defined a constant with the value '$' then inserted the appropriate code into the procedure. This finally works!!

Tuesday, 4 August 2020

Don't be a miser with brackets

In reports, there are three very useful 'group functions': T, S and B, that can be used on columns containing numerical data. T causes the total of the column to be presented at the end of the report, S causes sub-totals to be presented and B causes both sub-totals and totals to be presented. Unless one is displaying oranges and apples in the same column, B is normally the preferred option.

One can also use these functions on calculated columns; for example, I have been working on a report which displays in one column expected costs, in another column the actual costs and in the third column the ratio of actual to expected costs. 

Let's say that the report looks like this

Expense expected actual % ratio
Gas 160 170 106.25
Electricity 188 13571.81
Water 207 197 95.17
Totals 555 502 90.45

In order to achieve this, the definition of '% ratio' is (in words) 100.0 times the actual cost divided by the expected cost. Assuming that 'expected' has a column number #100 and 'actual' has a column number #110, the definition of column 120 will be 100.0 * #110 / #100. The first two columns should have the group function B (or T) in order to display their total at the end, and the ratio should have the group function b (or t). 

Sometimes this works, sometimes it doesn't.

This simple version might well have worked, but I had to change it in order to prevent division by zero; let's say that there are actual costs that were not budgeted. In such cases, Priority displays 100 * #110, which is completely wrong! There should be a condition that if expected = 0, then the result is zero, else it's what the formula calculated. On in Priority-speak, #100 = 0 ? 0 : 100.0 * #110 / #100.

This formula certainly handles division by zero properly and also displays the correct ratio in each row, but now the total percentage ratio is no longer 90.45 but 273.23 - the sum of the percentages in each row, and not the percentage of the total!

I know that group function b works because I have a report that displays the total percentage correctly. So why doesn't it work here? After a great deal of unsuccessful magic incantations, I was forced to call in someone else. She too did not know the reason off-hand, but she compared the formula here to the successful formula in another report, and noticed that here I was using the ternary comparison expression (taken from C; read it as 'if #100 is zero then return 0 else return 100.0 * #110 / #100). 

It turns out that the parser in Priority is very temperamental when it comes to the order of evaluation of expressions and especially the ternary expression; the parser requires a great deal of help which one supplies by means of brackets. When these are applied liberally, the 'b' group function is finally honoured in the totals line: (#100 = 0 ? 0 : (100.0 * ( #110 / #100)).

The use of brackets is somewhat inconsistent in Priority and reminds me of the UNLINK command: one can often get away without using it, but sometimes it is essential and so one should always use it. Apparently the same as with brackets: don't be a miser with them!

Also it seems that one has to use the row numbers (#100, etc) instead of actual row fields (e.g. STACK4.REALDATA).