LAST PAGE  BACK TO INDEX  NEXT PAGE

[18.0] Some Worked Examples (2)

[18.0] Some Worked Examples (2)

v5.0 / 1 of 23 / 01 sep 99 / gvg

* More interesting VEE examples!


[18.1] HANDLING DATA INPUTS TO VEE
[18.2] A LOG-IN PROGRAM IN VEE

 BACK TO INDEX

[18.1] HANDLING DATA INPUTS TO VEE

* An HP factory person contacted me concerning a VEE problem he had, and it fascinated me enough to want to pursue the solution in detail.

He had an instrument that he wanted to control under VEE. The user had to input four different settings for the instrument using VEE, and some combinations of these inputs were illegal. He wanted to create a "pop-up" that would allow the user to select the four inputs, but would immediately flag an invalid input, and would refuse to exit until the error was corrected.

The four inputs are integers in the ranges:


   DC:  0:9
   DS:  0:11
   TC:  0:9
   TS:  0:11
 
The rules of interaction are:

   IF(( DS == TS )
   OR ( DS == 0  AND TS == 11 )
   OR ( DS == 11 AND TS == 0  )
   OR (( DC != TC ) AND (abs( TS - DS ) == 1 )))
   {
      combination = INVALID;     
   }
   ELSE
   {
      combination = VALID;
   }
 
* Moving on to implementation (see xdatapms.vee for the source). The Pop-Up display contains four Enum objects, plus a display giving the current settings, and a button allow you to exit when the selections are made:

   +----------------------------------------------------------+
   |                       UserObject                         |
   +----------------------------------------------------------+
   |                                                          |
   |               Parameter Setting Test Program             |
   |                                                          |
   |  +----------+  +----------+  +----------+  +----------+  |
   |  |    DC    |  |    DS    |  |    TC    |  |    TS    |  |
   |  +----------+  +----------+  +----------+  +----------+  |
   |  | <*> 0    |  | <*> 0    |  | <*> 0    |  | < > 0    |  |
   |  | < > 1    |  | < > 1    |  | < > 1    |  | < > 1    |  |
   |  | < > 2    |  | < > 2    |  | < > 2    |  | <*> 2    |  |
   |  | < > 3    |  | < > 3    |  | < > 3    |  | < > 3    |  |
   |  | < > 4    |  | < > 4    |  | < > 4    |  | < > 4    |  |
   |  | < > 5    |  | < > 5    |  | < > 5    |  | < > 5    |  |
   |  | < > 6    |  | < > 6    |  | < > 6    |  | < > 6    |  |
   |  | < > 7    |  | < > 7    |  | < > 7    |  | < > 7    |  |
   |  | < > 8    |  | < > 8    |  | < > 8    |  | < > 8    |  |
   |  | < > 9    |  | < > 9    |  | < > 9    |  | < > 9    |  |
   |  +----------+  | < > 10   |  +----------+  | < > 10   |  |
   |                | < > 11   |                | < > 11   |  |
   |                +----------+                +----------+  |
   |                       +---------+                        |
   |                       | 0 0 0 2 |                        |
   |                       +---------+                        |
   |                        +------+                          |
   |                        | QUIT |                          |
   |                        +------+                          |
   +----------------------------------------------------------+
 
If there's a bogus input, a Message Box appears; you'll also get the same message box if you try to exit without fixing the bogus input. (Note that a bogus input will not be changed by VEE, you have to change it back yourself.)

* The UserObject itself accepts parameters to initialize the Enum object and provides outputs to return their values:


          +------------+
   DC --->|            +---> DC
   DS --->| UserObject +---> DS
   TC --->|            +---> TC
   TS --->|            +---> TS
          +------------+
 
Internally, the UserObject can be divided into three parts: an entry block to get the values, a test block to check the values, and an exit block to allow exiting the UserObject:

          +----------------------------------------------+
          |                  UserObject                  |
          +----------------------------------------------+
          |              +---------+                     |
   DC --->|------------->|         +---------------------|---> DC
   DS --->|------------->|  entry  +---------------------|---> DS
   TC --->|------------->|  block  +---------------------|---> TC
   TS --->|------------->|         +---------------------|---> TS
          |              +-+-+-+-+-+                     |
          |                | | | |                       |
          |   +--------+   | | | |         +---------+   |
          |   |  exit  |   | | | +-- dc -->|         |   |
          |   |  block |   | | +---- ds -->|  test   |   |
          |   +--------+   | +------ tc -->|  block  |   |
          |                +-------- ts -->|         |   |
          |                                +---------+   |
          +----------------------------------------------+
 
The entry block is simple but it has a few subtleties:

       +-------+
       | Until |
       | Break +---------+              entry block
       +-------+         |
                     +---+---+     +-------------+
   DC -------------->|  DC   +---->| From String +-----------+--------> DC
                     +---+---+     +-------------+           |
                    enum |                                   |
                         |                                   |
                     +---+---+     +-------------+           |
   DS -------------->|  DS   +---->| From String +--------+--+--------> DS
                     +---+---+     +-------------+        |  |
                    enum |                                |  |
                         |                                |  |
                     +---+---+     +-------------+        |  |
   TC -------------->|  TC   +---->| From String +-----+--|--|--------> TC
                     +---+---+     +-------------+     |  |  |
                    enum |                             |  |  |
                         |                             |  |  |
                     +---+---+     +-------------+     |  |  |
   TS -------------->|  TS   +---->| From String +--+--|--|--|--------> TS
                     +-------+     +-------------+  |  |  |  |
                    enum                            |  |  |  |
                                                    |  |  |  +--> dc
                                                    |  |  +-----> ds
                                                    |  +--------> tc
                                                    +-----------> ts
 
The four Enum objects are "clocked" continously by an Until Break object. This allows the UserObject to check their status on a continuous basis. The Enum objects are not set to "Auto-Execute" (which, as this exercise confirmed, is a nearly useless VEE feature for anything but the most idiotic VEE programs and should be avoided) nor to "Wait For Input" (which is more useful but in this case violates the requirement that the inputs be continually checked).

As the Enum objects return an Enum value, which is effectively a string, you can't do Boolean operations on them, so each is run through a From String object containing the object's default action: READ TEXT X INT -- to convert them to a number. There are other ways of doing this, but this works as well as any.

* The test block is more complicated:


                      +--------+
   dc ---+----------->|        |     +----------+   +---------+ A!=B
   ds ---|--+-------->|   To   +--+->|  Shift   +-->| If-Then +----+
   tc ---|--|--+----->| String |  |  | Register +-->|  -Else  |    |
   ts ---|--|--|--+-->|        |  |  +----------+   +----+----+    |
         |  |  |  |   +--------+  |                      |         |
         |  |  |  |               |                  +---+---+     |
         |  |  |  |               |                  | Next  |     |
         |  |  |  |     test      |                  +-------+     |
         |  |  |  |     block     |                                |
         |  |  |  |               |                        +-------+-------+
         |  |  |  |               +----------------------->| AlphaNumeric  |
         |  |  |  |                                        +-------+-------+
         |  |  |  |                                                |
         |  |  |  |        +---------------------------------------+
         |  |  |  |        |
         |  |  |  |   +----+----+
         |  |  |  +-->|         |      +--------------+ A==1
         |  |  +----->| formula +--+-->| If-Then-Else +---------+
         |  +-------->|         |  |   +--------------+         |
         +----------->|         |  |                            |
                      +---------+  |   +------------+     +-----+-----+
                                   +-->| Set Global |     |  Message  |
                                       |  [ tmp  ]  |     |    Box    |
                                       +------------+     +-----------+
 
The four inputs to this block are converted to a nice string display format using a To String box containing the transactions:

   WRITE TEXT A," " INT
   WRITE TEXT B," " INT
   WRITE TEXT C," " INT
   WRITE TEXT D INT EOL
 
This is shoved into an AlphaNumeric object for display. However, since the inputs from the entry block are being clocked continuously, just driving this output into the AlphaNumeric would cause the display to flicker annoyingly. It should only be updated when its value actually changes.

To this end, the output is shoved into a Shift Register object. An If-Then-Else block checks the two Shift Register outputs and generates a "strobe" signal when they are different, which then activates the AlphaNumeric. (Note how a Next object is used to ensure that the Until-Break in the entry block keeps on clocking.)

At the same time, the four inputs from the entry block are being tested by a Formula box for validity:


   ((( DS == TS ) OR ( DS == 0  AND TS == 11 ) 
                  OR ( DS == 11 AND TS == 0  ) 
                  OR (( DC != TC ) AND (abs( TS - DS ) == 1 ))))
 
The Formula box is also hooked up to the "strobe" signal. When activated, it checks the inputs and returns a "1" if they are bad. This in turn activates a Message Box and is also stored in a Global variable named "parmerr". Note that if the Formula box wasn't being strobed, once you had an error you'd never get rid of the Message box -- tell it to go away and it would just pop back up at you again.

* Saving the results of the test in a Global allows the exit block to know about the results of the tests. The exit block is implemented as a separate thread, with an Until Break driving an OK button:


   +-------+
   | Until |
   | Break +---+
   +-------+   |
               |
           +---+---+
           | QUIT  +------+         exit block
           +-------+      |
           OK             |
             +------------+------------+
             |       If/Then/Else      |
             +---------------+---------+
             |[(parmerr==0)] | Then    +--------------------------+
             |[(parmerr==1)] | Else If +-----------+              |
             | Else          |         |           |              |
             +---------------+---------+     +-----+-----+  +-----+------+
                                             |  Message  |  |    Exit    |
                                             |    Box    |  | UserObject |
                                             +-----+-----+  +------------+
                                                   |
                                               +---+---+
                                               | Next  |  
                                               +-------+
 
If the QUIT button is activated, the If/Then/Else checks the value of the Global variable "parmerr". If there's been an error, you get a message box, if not, the UserObject is exited.

Using a Global here has a drawback: it better not have the same name as any other Global in the program (now I see why people ask for Local variables). I tried to implement the same function with a Sample-and-Hold, but I got lost in "strange VEE loop propagation problems" and decided I had to swallow that inelegancy.

* In any case, the UserObject operated as specified. Case closed.

 TOP OF PAGE

[18.2] A LOG-IN PROGRAM IN VEE

* A user in Israel had a program that was performing a login check with VEE. That is, the program would query the user for a name and password and then see if there was a match.

The user had a bug in the program. After some fruitless searching for it, I decided it would be wiser to simply write a program that does the function properly.

* The example I designed (see xlogin.vee for the source) uses an array of records stored in a Global named UserData to provide UserName, FullName, and PassWord for each of the users. It then allows the user to enter their name and password (with up to three tries for each), and either acknowledges a successful login or that the login failed. The user can cancel out of the login process at any time.

The overall program appears as follows:


   +------------------------------+
   |            Record            |
   +------------------------------+
   | +--------------------------+ |
   | |  [  0 ]    in: [0 .. 5 ] | |     +-------------+
   | +--------------------------+ |     | Set Global: |
   |  Field name   Value          +---->|  UserData   |
   | ---------------------------- |     +------+------+
   | [ UserName ] [ bugs        ] |            |
   | [ FullName ] [ Bugs Bunny  ] |   +--------+--------+
   | [ PassWord ] [ whatsupdoc  ] |   | Call Function:  |
   | ---------------------------- |   |      Login      +--+
   | [First][Prev ][Next ][Last ] |   +-----------------+  |
   +------------------------------+                        |
                                                           |
       +---------------------------------------------------+
       |
       |   +-----------------+
       |   |  If/Then/Else   |
       |   +---+------+------+
       +-->| A | A==1 | Then +-------------------------+
           |   |      | Else +--+                      |
           +---+------+------+  |                      |
                                |                      |
                       +--------+--------+  +----------+----------+
                       |  Message Box:   |  |    Message Box:     |
                       | "Login Failed." |  | "Login Succeeded."  |
                       +-----------------+  +---------------------+
 
The program is built around a UserFunction named "Login" that returns a 1 for a successful login and a 0 for an unsuccessful login. It has the architecture:

   +--------------------------------------------------------------------+
   |                       UserFunction: Login                          |
   +----------------------------------------------------------------+---+
   |                                                                |   |
   |        +-----+ Integer                    return value here    |   |
   |        | 0   +-------------------------------------->+-----+   |   |
   |        +--+--+                                       | JCT +-->| X |
   |           |                                      +-->+-----+   |   |
   |  +--------+--------+ query user for name         |             |   |
   |  | Call Function:  |                             |             |   |
   |  |   CheckName     +--+                          |             |   |
   |  +-----------------+  |                          +---------+   |   |
   |                       |                                    |   |   |
   |  +--------------------+----+                               |   |   |
   |  |                         |                               |   |   |
   |  |  +----------------+     |                               |   |   |
   |  |  |  If/Then/Else  |     |                               |   |   |
   |  |  +---------+------+     |                               |   |   |
   |  +->| A != -1 | Then +-----|-------------+                 |   |   |
   |     |         | Else +--+  |             |                 |   |   |
   |     +---------+------+  |  |  +----------+-----------+     |   |   |
   |                         |  +->| UserData[A].PassWord +--+  |   |   |
   |             +-----------+     +----------------------+  |  |   |   |
   |             |                  Formula                  |  |   |   |
   |    +--------+--------+              +-------------------+  |   |   |
   |    | Exit UserObject |              |                      |   |   |
   |    +-----------------+              |  +----------------+  |   |   |
   |                                     +->| Call Function: +--+   |   |
   |                                        | CheckPassWord  |      |   |
   |                                        +----------------+      |   |
   |                                                                |   |
   +----------------------------------------------------------------+---+
 
This UserFunction relies on two lower-level UserFunctions, "CheckName" and "CheckPassWord", to perform the checks on the UserName and the PassWord respectively.

The first thing "Login" does is write a 0 to the output pin. If the UserFunction completes without changing this value, that is what it will return. It then calls "CheckName" to query the user for a UserName. If the user cancels, or fails to enter a valid name, "CheckName" returns a -1, and "Login" exits immediately.

Otherwise, "CheckName" returns an index to the matching UserData record, which is used to obtain the corresponding password from the record using a Formula box, password is then passed on the UserFunction "CheckPassWord" to query the user for a matching password.

If the user cancels or fails to enter a matching password, "CheckPassWord" returns a 0; otherwise it returns a 1. The output value of "CheckPassWord" is passed to the output of "Login" to overwrite the 0 written at the beginning of the UserFunction.

* Examining the UserFunction "CheckName" in more detail gives:


   +--------------------------------------------------------------------+
   |                     UserFunction: CheckName                        |
   +----------------------------------------------------------------+---+
   |                                                                |   |
   |        +-----+ Integer                                         |   |
   |        | -1  +-------------------------------------->+-----+   |   |
   |        +--+--+                                       | JCT +-->| X |
   |           |           +---------------+          +-->+-----+   |   |
   |           |           | If/Then/Else  |          |             |   |
   |   +-------+-------+   +--------+------+          |             |   |
   |   | For Count: 3  +-->| A >= 1 | Then +--+       |             |   |
   |   +-------+-------+   |        | Else |  |       +---------+   |   |
   |           |           +-------++------+  |                 |   |   |
   |           |                   |          |                 |   |   |
   |  +--------+--------+          |  +-------+-------+         |   |   |
   |  | Message Box:    |          |  | Message Box:  |         |   |   |
   |  | "Access Denied" |          |  | "Try Again"   |         |   |   |
   |  +--------+--------+          |  +---------------+         |   |   |
   |           |                   |                            |   |   |
   |  +--------+--------+          |                            |   |   |
   |  | Exit UserObject |          |                            |   |   |
   |  +-----------------+          |                            |   |   |
   |                               |                            |   |   |
   |           +-------------------+                            |   |   |
   |           |                                                |   |   |
   |           |              +----------------+                |   |   |
   |   +-------+-------+      | Call Function: |                |   |   |
   |   |  Text Input:  +----->|    ScanList    +--+             |   |   |
   |   | "Enter Name"  +--+   +----------------+  |             |   |   |
   |   +---------------+  |                       |             |   |   |  
   |               Cancel |                       |             |   |   |
   |             +--------+--------+      +-------+             |   |   |
   |             | Exit UserObject |      |                     |   |   |
   |             +-----------------+      |                     |   |   |
   |                                      |                     |   |   |
   |  +-----------------------------------+                     |   |   |
   |  |                                   |                     |   |   |
   |  |   +----------------+              |                     |   |   |
   |  |   |  If/Then/Else  |              |                     |   |   |
   |  |   +---------+------+              |                     |   |   |
   |  +-->| A != -1 | Then +--------------|-------------+       |   |   |
   |      |         | Else +--+           |             |       |   |   |
   |      +---------+------+  |           | index   +---+---+   |   |   |
   |                          |           +-------->| Gate  +---+   |   |
   |                      +---+---+                 +---+---+       |   |
   |                      | Next  |                     |           |   |
   |                      +-------+            +--------+--------+  |   |
   |                                           | Exit UserObject |  |   |
   |                                           +-----------------+  |   |
   |                                                                |   |
   +----------------------------------------------------------------+---+
 
"CheckName" begins by writing a 0 to the output as the default value, and then uses a Counter to query the user three times. If the user fails to enter a valid UserName after three tries, he or she gets an "Access Denied" message, and the UserFunction exits. Note how "CheckName" uses a message box to tell the user "bogus UserName, please try again" before the second and third tries.

A Text Input dialogue is used to get the UserName. If the user cancels out, the UserFunction exits. Otherwise it calls the UserFunction "ScanList" to check for a UserName match.

"Scanlist" returns a -1 if there is no match, and the index of the matching record if there is a match. If there is no match, the UserFunction goes to the next iteration of the three queries; if there is a match, it gates the index of the matching record to the output and then exits.

* The architecture of "ScanList" is a little simpler:


   +---------------------------------------------------------------------+
   |                      UserFunction: ScanList                         |
   +------+----------------------------------------------------------+---+
   |      |                                                          |   |
   |      |         +-----+ Integer                                  |   |
   |      |         | -1  +------------------------------->+-----+   |   |
   |      |         +--+--+                                | JCT +-->| X |
   |      |            |                               +-->+-----+   |   |
   |      |  +---------+---------+   +-----------+     |             |   |
   |      |  | totsize(UserData) +-->| For Count +--+  |             |   |
   |      |  +-------------------+   +-----------+  |  |             |   |
   |      |                                         |  +---------+   |   |
   |      |  +--------------------------------------+            |   |   |
   |      |  |                                                   |   |   |
   |      |  |   +--------------------------------------+        |   |   |
   |      |  |   |             If/Then/Else             |        |   |   |
   |      |  |   +---+---------------------------+------+        |   |   |
   |      |  +-->| A | UserData[A].UserName == B | Then +-----+  |   |   |
   | Name +--|-->| B |                           | Else +--+  |  |   |   |
   |      |  |   +---+---------------------------+------+  |  |  |   |   |
   |      |  |                                             |  |  |   |   |
   |      |  |                             +---------------+  |  |   |   |
   |      |  |                             |                  |  |   |   |
   |      |  |                         +---+---+      +-------+  |   |   |
   |      |  |                         | Next  |      |          |   |   |
   |      |  |                         +-------+      |          |   |   |
   |      |  |                                    +---+---+      |   |   |
   |      |  +----------------------------------->| Gate  +------+   |   |
   |      |                                       +---+---+          |   |
   |      |                                           |              |   |
   |      |                                  +--------+--------+     |   |
   |      |                                  | Exit UserObject |     |   |
   |      |                                  +-----------------+     |   |
   |      |                                                          |   |
   +------+----------------------------------------------------------+---+
 
This UserFunction begins by writing a -1 to the output as the default value, and then gets the "totsize()" of the UserData array. This is used as a Counter value to allow checking each record of the UserData array.

The Counter drives a Formula box that checks the UserData array element given by the count against the username provide. If there isn't a match, it fires off the next iteration of the loop, if there is a match, it gates the current index count to the output of the UserFunction and then exits.

* The "CheckPassWord" UserFunction is somewhat similar to the "CheckName" UserFunction:


   +----------------------------------------------------------------------+
   |                      UserFunction: CheckName                         |
   +------+-----------------------------------------------------------+---+
   |      |                                                           |   |
   |      |       +-----+ Integer                                     |   |
   |      |       | 0   +---------------------------------->+-----+   |   |
   |      |       +--+--+                                   | JCT +-->| X |
   |      |          |           +---------------+      +-->+-----+   |   |
   |      |          |           | If/Then/Else  |      |             |   |
   |      |  +-------+-------+   +--------+------+      |             |   |
   |      |  | For Count: 3  +-->| A >= 1 | Then +--+   |             |   |
   |      |  +-------+-------+   |        | Else |  |   +---------+   |   |
   |      |          |           +-------++------+  |             |   |   |
   |      |          |                   |          |             |   |   |
   |      | +--------+--------+          |  +-------+-------+     |   |   |
   |      | | Message Box:    |          |  | Message Box:  |     |   |   |
   |      | | "Access Denied" |          |  | "Try Again"   |     |   |   |
   |      | +--------+--------+          |  +---------------+     |   |   |
   |      |          |                   |                        |   |   |
   |      | +--------+--------+          |                        |   |   |
   |      | | Exit UserObject |          |                        |   |   |
   |      | +-----------------+          |                        |   |   |
   |      |                              |                        |   |   |
   |      |          +-------------------+                        |   |   |
   |      |          |                                            |   |   |
   |      | +--------+--------+                                   |   |   |
   |      | |  Text Input:    +---------------+                   |   |   |
   |      | |"Enter Password" +--+            |                   |   |   |
   |      | +-----------------+  |            |                   |   |   |  
   |      |               Cancel |            |                   |   |   |
   |      |             +--------+--------+   |                   |   |   |
   |      |             | Exit UserObject |   |                   |   |   |
   |      |             +-----------------+   |                   |   |   |
   |      |                                   |                   |   |   |
   |      |  +--------------------------------+                   |   |   |
   |      |  |                                                    |   |   |
   |      |  |   +--------------------+                           |   |   |
   |      |  |   |    If/Then/Else    |                           |   |   |
   |      |  |   +---+---------+------+                           |   |   |
   |      |  +-->| A | A == B  | Then +-------------------+       |   |   |
   | Pass +----->| B |         | Else +--+                |       |   |   |
   |      |      +---+---------+------+  |            +---+---+   |   |   |
   |      |                              |    Integer | 1     +---+   |   |
   |      |                          +---+---+        +---+---+       |   |
   |      |                          | Next  |            |           |   |
   |      |                          +-------+   +--------+--------+  |   |
   |      |                                      | Exit UserObject |  |   |
   |      |                                      +-----------------+  |   |
   |      |                                                           |   |
   +------+-----------------------------------------------------------+---+
 
This UserFunction begins by writing a 0 to the output, then checks three times for input. If there is no valid input, it announces "Access Denied" and exits. (It announces the erroneous input for the second and third attempts.)

On each request for password input, it uses a Text Input dialogue to query the user. If the user cancels, the UserFunction exits. If the dialogue obtains a valid password, it compares it to the password passed to "CheckPassWord". If there is no match, it goes on to the next check in the loop, if there is a match, it sends a 1 to the output and exits.

One last comment: the Text Input box used to obtain the password from the user should be set to "Password Masking Enabled". This will substite a "*" for each keypress the user makes while entering the password, keeping it hidden.

* This example demonstrates structured and deterministic control-flow design in a VEE program. The example divides decision-making structures into simple individual UserFunctions, each one of which whose actions are well-defined, each one of which can be tested and validated in a straightforward fashion.

 TOP OF PAGE


 LAST PAGE  BACK TO INDEX  NEXT PAGE