Table Driven Logic - A Simple Case


Requirement - A Sudoku solver, using logic (emulating human techniques) to solve a puzzle. There are (in this example) five different kinds of transformations that can performed upon the data set. We want to keep repeating the first transformation until there is no change; upon no change, we want to proceed to the second transformation. If the second transformation results in a change, we want to go back and start with the first transformation; otherwise we proceed onto the third transformation, and so on.


Do Until ( ¬ changed )
   changed = test001()
   If ( ¬ changed )
      changed = test002()
      If ( ¬ changed )
         changed = test003()
         If ( ¬ changed )
            changed = test004()
            If ( ¬ changed )
               changed = test005()
            EndIf
         EndIf
      EndIf
   EndIf
EndDo

That's a lot of Ifs and EndIfs, and what would happen if we had two dozen transformations, or a thousand?

This whole top down structure can be implemented bottom up, through the use of Table Driven logic.

First, we create an array of Procedure Pointers:

                                                                        
d                 ds                                                    
d                                 *   ProcPtr Inz(%PAddr(test001))      
d                                 *   ProcPtr Inz(%PAddr(test002))      
d                                 *   ProcPtr Inz(%PAddr(test003))      
d                                 *   ProcPtr Inz(%PAddr(test004))      
d                                 *   ProcPtr Inz(%PAddr(test005))      
d                                 *   ProcPtr Inz(*Null)                
d @test@                  1     96*   ProcPtr Dim(6)                    

Note that there is one more entry in the array than there are transformations to execute.

Now, we define a pointer based procedure:


 d test            pr             1n   ExtProc(test@)     

and its basing pointer:

d test@           s               *   ProcPtr

and initialize tstCnt:

d tstCnt          s              3s 0 Inz(%Elem(@test@))

Now, we can implement the previous top down structure thusly;


DoU ( $I = tstCnt );                                     
   $I = 0;                                               
   DoU ( $I = tstCnt Or test() );                        
                                                         
      $I = $I + 1;                                       
      test@ = @test@($I);                                
                                                         
   EndDo;                                                
EndDo;                                                   

If we process all five transformation without change, then on the last iteration, $I goes from five to six (5=>6), test@ is loaded with a *NULL, and we fall out of our loop.



I have, in production code, implemented a Sarbanes-Oxley compliance report with a similar table driven structure. The process involves (roughly) fifteen different tests, and we want to fall out upon the first failure. The actual production mainline looks like:


$I = 0;                                             
DoU ( msgID <> *Blanks Or $I = s100Cnt );           
   $I = $I + 1;                                     
   procProxy@ = @procProxy@(@s100($I));             
   procProxy();                                     
EndDo;                                              
                                                     
cmtHndlr();

msgID is a global variable containing any error messages.

s100Cnt is the number of elements in @s100

@s100 contains indexes to procedure pointers in @procProxy@; this allows us to change logic at run-time (see note) - if hard coding data is a bad thing, then it seems to reason hard coding logic is as well. This technique allow us to soft code logic.

Note: @s100 is actually pointer based, allowing us to determine which of multiple arrays to use:

d @s100           s              3s 0 Based(@s100@) Dim(15)

Select;                                              
When ( system = SYSW And Not pmTESTMODE );           
   @s100@  = %Addr(@S100W);                          
   s100Cnt = %Elem(@S100W);                          
When ( system = SYSW And     pmTESTMODE );           
   @s100@  = %Addr(@S100WT);                         
   s100Cnt = %Elem(@S100WT);                         
When ( system = SYSO And Not pmTESTMODE );           
   @s100@  = %Addr(@S100O);                          
   s100Cnt = %Elem(@S100O);                          
When ( system = SYSO And     pmTESTMODE );           
   @s100@  = %Addr(@S100OT);                         
   s100Cnt = %Elem(@S100OT);                         
EndSl;                                                   


Valid HTML 3.2! Creative Commons License

BrilligWare/ chris@pando.org / revised February 2008