MMCC MASTHEAD




MMCC's PL/B notes
  •    Notes home
  •    Intro
  •    History
  •    Article index
  •    MMCC Standards

    v1.10
  • Mid-Michigan Computer Consultants, Inc.
    509 Center
    Bay City, Michigan

    Sales (989) 892-9242             Support (989) 686-8860


    ANSI Standard PL/B Language
    MMCC Programming Standards
    NOTE TO OUR READERS
    This web resource is written for our own use. But we feel strongly that the PL/B language should be shared with the software community. So feel free to use this at will! BUT PLEASE... if you find errors or omissions or have a better way to do something. TELL US! Dialog helps us all. Send e-mail to: support@mmcctech.com


    FILE SYSTEM CONCEPTS

    MMCC I/O and FILEs STANDARDS

    MMCC software is written in a systematic way. There are several foundation concepts which, when understood, set the stage for all development. Central to everything is the "file system".

    The MMCC file system is built on four code modules which are included into all programs. These are: (Where xxxx is the prefix of the particular system. i.e. ADRW, SHOP, FM-, GHCW, ODA- etc.)

    These modules contain the necessary code for every file. The common characteristics of the modules are:


    The file EQUATE list

    Every program contains a list of EQU statements identifying all of the files in the system. The equated value for each file indicates how the file is to be used by the program. The greater the value, the more that can be done.

    Besides controlling the compile proccess, this list provides quick documentation about how the program works. By simple inspection you know which files are used, which are just read, which are potentially updated.

    The Equate values are: Equates for every file shoule be included in each program. If an equate is left out the compiler will give a warning and the equate will be assumed to be zero.

    When a new file is added to the system it is not necessary to fix every program. Those which don't need the file will have the equivalent of a zero equate which says to ignore the file anyway.

    An example of the equate list would be:
    ASIO     EQU       0
    CAIO     EQU        1     ;just need FD
    GXIO     EQU       0
    HCIO     EQU          3   ;need full I/O
    MAIO     EQU       0
    ... etc ...
    RPTIO    EQU       0
    USERIO   EQU         2    ;just need READs
    WDIO     EQU          3   ;need full I/O
    WHIO     EQU       0
    XCIO     EQU          3   ;need full I/O
    .
             INCLUDE     xxx-IOFD
    


    Back to
    Standards Index


    xxx-IOFD.PLS - The File Descriptions

    The "IOFD" include unit contains the "file descriptions". Every file is described in this one module. This module defines data fields and is typically included after the other variables and before the processing.

    Each file's code is encapsulated in the %IF compiler control as follows:
      %IF WHIO > 0
           work order header file description
      %ENDIF
    
    The FD contents have several consistent components.

    Back to
    Standards Index


    xxx-OPEN.PLS - The File Opening routines

    The "OPEN" unit contains the file opening and prepping code. This module is to be included IN-LINE near the beginning of the program. It is not a CALLED routine. The program just flows into the INCLUDE and out the other side.
    EXCEPTION:

    For "loadmods" (separately compiled modules), the xxx-OPEN routine might be put in a module to be called. The idea is that the mainline is entered oncce and does the opens during initialization. The "loadmod" may be called multiple times in the same session. The first call to the "loadmod" loads that program into memory. Once loaded it stays there and it's context is preserved. If files are opened on the first entry they don't have to be opened on subsequent entries. It's just a waste of time.

    To make the most of this fact, we typically do use a CALL in the loadmod's initialization code. The routine that is called includes the xxx-OPEN routine and sets a flag to see if it needs to be processed:
    #FILES_ARE_OPEN  INIT  "N"
    OPEN_THE_FILES
           IF  (#FILES_ARE_OPEN = YES)
               RETURN
           ENDIF
           MOVE      YES, #FILES_ARE_OPEN
           INCLUDE   xxx-OPEN
           RETURN
    
    Every file is coded in this one OPEN module. Each file's code is encapsulated in the %IF compiler control as follows: The code for a typical open follows. Note that DISPLAYs are included but will only appear if the WINSHOW operation is done to turn on the main window. That's controlled by the PRIVDIAG flag which is set universally from the main menu.

    The FILE PREP is part of the open routine. It is only available for files with read/write access. If a read-only file is not found the program errors out.

    File's should NOT have to be prepped after the first time they're used. Most files are permanent. The PREP is included in this module because it's a logical, central place to have it. (Some older systems do not have the PREP code.)

    Note that this is a nested %IF structure. The xxIO equate must be 2 or greater to get the module included at all. The first level sets the common characteristics, like the file name and displays the filename.

    Note that the TITLE_LINE universal work area is packed up with each of the opens. The PACK instruction appends TITLE_LINE to itself, includes a "return" (HEX_7F), then the file name. On return to the mainline program, TITLE_LINE can be ignored or can be shown in an edit text or anywhere else.
      %IF WHIO > 1
             TRAP      NOFILE IF IO        
             PACK      FILENAME,   FILE_PATH,SYSTEMID,"-FWH"
             PACK      TITLE_LINE,TITLE_LINE,HEX_7F:
                       "WH_FILE: Work order header file:",FILENAME
             DISPLAY   "WH_FILE: Work order header file":
                       *H=40,*HON,*LL,FILENAME,*PL,*HOFF,*H=70;
        %IF WHIO < 3
             DISPLAY   "READ"
             OPEN      WH_FILE, FILENAME,READ
             ADD       ONE,OPEN$R
        %ELSE IF
             MOVE      NO,FILENIF
             TRAP      FILEERR  IF IO   ;IN I/O... just sets FILENIF
             DISPLAY   "SHARE"
             OPEN      WH_FILE,FILENAME
             IF (FILENIF=NO)
                 ADD   ONE,OPEN$S
               ELSE
                 DISPLAY "WH file not found.  Err:",S$ERROR$:
                         " Name:",*LL,*HON,FILENAME,*HOFF,*PL
                 ALERT PLAIN,"OK TO PREP WH FILE",ALERT_RESULT,"WHFILE"
                 IF (ALERT_RESULT != 1)
                     DISPLAY  "Run aborting"
                     STOP
                 ENDIF
                 PREP   WH_FILE,FILENAME,FILENAME,"1-11","625",SHARE
                 MOVE   #ALL_ZEROS,WHRECKEY
                 UNPACK NUL,       WHRECORD
                 CALL  WHIOWRT
             ENDIF
         %ENDIF
      %ENDIF
      
    The OPEN$R and the OPEN$S variables are simple counters for the number of files opened in read and the number opened in share mode. This was important in the DOS days when we had a limited number of file handles based on the FILES=nn value in CONFIG.SYS. It's not as important now, but can be interesting.

    The TRAP NOFILE at the beginning of the routine is the general trap for a missing file. It's primarily used for read only files. If the trap is taken, the routine essentially aborts the program with an error message.

    For read/write access files we use TRAP FILEERR IF IO. That routine is found in the xx-IO module. All it does it MOVE "X", FILENIF.

    (FILENIF is a one byte field defined in the xx-IOFD module. It's used anywhere we need a temporary flag for testing file errors, like the open.)

    If the file is not found on a read/write level open, we offer the user the oppertunity to prep the files. All of our files are permanent. We should get the prep offer only when new files are added to the system.
    Most of our systems include a program which opens ALL files. After opening it shows the list of all the files. You can click on any file and get a display of the contents of the first record.

    When a new file is added to the system, the 8804 program is compiled and run. It offers the PREP which we do. After that we should never see the PREP again.
    Note that the PREP uses the form which describes the KEY position as "1-nn". That is significant. By doing that the system will later allow the SUNIDXNT utility with the "r" parameter to "reindex" the file. You could also prep with the key designated as "nn". The effect is the same as "1-nn" but you can't do the "r" utility index then.



    SPECIAL CASE FOR PREPPING FILELIST and MULTIPLE INDEXES

    Filelists are structures which put several indexes into a control group. If you update the primary file, the runtime will update all of the associated indexes.

    Filelist present some challenges when prepping the files. If the text file and the several indexes already exist, you open the "primary" file first then open all of the secondary index files. If the files do not exist, each of the indexes must be created outside of the filelist group before the group can be opened.

    Our solution to this problem is to put the primary file OPEN in a LOOP structure. If the primary file is found, the loop is broken and we open the secondary indexes. If the primary is NOT found, we prep all of the secondary files using a dummy file, then loop back and open the primary again. It should be found the second time and break the loop.

    Note that each of the secondary index PREP's uses FILENAME to describe the TXT file but defines it's own #ISINAME for the ISI file name. The last prep is for the primary file and it uses the same FILENAME in BOTH of the name slots.

    Once all of the files have been prepped, and the primary file has been successfully opened, we fall into the regular open for the secondary indexes. These trap a special NO_ISI_FILE routine in case the secondary index fails. We just want to be sure we know the type of file that caused such errors.

    The code is as follows:
      %IF ALIO > 1
             TRAP      NOFILE IF IO        
             PACK      FILENAME,   FILE_PATH,SYSTEMID,"-AL00"
             PACK      TITLE_LINE,TITLE_LINE,HEX_7F:
                       "ALLIST: Master address list file:",FILENAME
             DISPLAY   "ALLIST: Master address list file":
                       *H=40,*HON,*LL,FILENAME,*PL,*HOFF,*H=70;
        %IF ALIO < 3
             DISPLAY   "READ"
             OPEN      ALLIST, FILENAME,READ
             ADD       ONE,OPEN$R
        %ELSEIF ALIO > 2
           LOOP
             MOVE      NO,FILENIF
             TRAP      FILEERR IF IO 
             DISPLAY   "SHARE"
             OPEN      ALLIST,FILENAME
             IF (FILENIF=NO)
                 ADD   ONE,OPEN$S
                 BREAK
             ENDIF
    
             DISPLAY "AL file not found.  Err:",S$ERROR$:
                     " Name:",*LL,*HON,FILENAME,*HOFF,*PL
             ALERT PLAIN,"OK TO PREP WH FILE",ALERT_RESULT,"WHFILE"
             IF (ALERT_RESULT != 1)
                 DISPLAY  "Run aborting"
                 STOP
             ENDIF
    .
             PACK     #ISINAME, FILE_PATH,SYSTEMID,"-AA00"
             DISPLAY  "Prep ",*HON,*LL,FILENAME,*PL,*HOFF:
                      ", ",   *HON,*LL,#ISINAME,*PL,*HOFF
             PREP     #TEMPISI,FILENAME,#ISINAME:
                         "U1-2,66-85,3-9","650",SHARE
             CLOSE    #TEMPISI
    .
             PACK     #ISINAME, FILE_PATH,SYSTEMID,"-AC00"
             DISPLAY  "Prep ",*HON,*LL,FILENAME,*PL,*HOFF:
                      ", ",   *HON,*LL,#ISINAME,*PL,*HOFF
             PREP     #TEMPISI,FILENAME,#ISINAME:
                         "U66-85,1-9","650",SHARE
             CLOSE    #TEMPISI
    .
             DISPLAY  "Prep ",*HON,*LL,FILENAME,*PL,*HOFF:
                      ", ",   *HON,*LL,#ISINAME,*PL,*HOFF
             PREP     #TEMPISI,FILENAME,FILENAME:
                         "1-9","650",SHARE
             MOVE     #ALL_ZEROS,ALRECKEY
             UNPACK   ALRECKEY,  ALKEY
             UNPACK   NUL,       ALRECORD
             WRITE    #TEMPFILE, ALRECKEY;ALKEY,ALRECORD
             CLOSE    #TEMPISI
           REPEAT
         %ENDIF
      %ENDIF
    .
             TRAP      NO_ISI_FILE IF IO  
      %IF ALIO > 1
             PACK      FILENAME,   FILE_PATH,SYSTEMID,"-AA00"
             PACK      TITLE_LINE,TITLE_LINE,HEX_7F:
                       "AALIST: Master address list ALPHA IDX:",FILENAME
             DISPLAY   "ALLIST: Master address list ALPHA IDX:":
                       *H=40,*HON,*LL,FILENAME,*PL,*HOFF,*H=70;
        %IF ALIO < 3
             DISPLAY   "READ"
             OPEN      AALIST, FILENAME,READ
             ADD       ONE,OPEN$R
        %ELSEIF ALIO > 2
             DISPLAY   "SHARE"
             OPEN      AALIST, FILENAME
             ADD       ONE,OPEN$S
        %ENDIF
       %ENDIF
    .
             TRAP      NO_ISI_FILE IF IO  
      %IF ALIO > 1
             PACK      FILENAME,   FILE_PATH,SYSTEMID,"-AC00"
             PACK      TITLE_LINE,TITLE_LINE,HEX_7F:
                       "ACLIST: Master non-class IDX:",FILENAME
             DISPLAY   "ACLIST: Master non-class IDX:":
                       *H=40,*HON,*LL,FILENAME,*PL,*HOFF,*H=70;
        %IF ALIO < 3
             DISPLAY   "READ"
             OPEN      ACLIST, FILENAME,READ
             ADD       ONE,OPEN$R
        %ELSEIF ALIO > 2
             DISPLAY   "SHARE"
             OPEN      ACLIST, FILENAME
             ADD       ONE,OPEN$S
        %ENDIF
      %ENDIF
    


    Back to
    Standards Index



    xxx-IO.PLS - The I/O (read / write) Routines

    The "I/O" unit contains all "I/O" related code for the files. Each operation is designed as a called routine. This include module is usually included at the tail end of the mainline program.

    Each file's code is encapsulated in the %IF compiler control just as all other file components described in this article.

    This include unit may also contain a number of utility routines that all programs can take advantage of. These are not generally controlled by EQUATES. They're just in every program. Some are file specific and they may be included based on the EQUATES. Most of these utility routines appear at the beginning of the xxx-IO.PLS module.

    Another large block of utility routines is in the MMCCSERV routine, which is stored in the SUNDB folder. The xxx-IO include module normally includes the MMCCSERV module as the last thing. That makes those routines available to all programs.

    For the most part the I/O is done using the FILEIO verb. That means that a single, general format I/O statement can be written and it will be specialized at runtime based on a flag. We use the variable IOMETHOD, defined in COMMONWK to identify the method.

    You don't have to worry about the I/O method in your program. That part is handled by the I/O routine itself. To read a record you'd typically do something like this:
    	MOVE	SPACE to           WH_TYPE
    	MOVE	INPUT_WO_NUMBER to WH_WONUM
    	MOVE	SPACE to           WH_RECODE
    	PACKKEY	WHRECKEY from WHKEY
    	CALL	WHIOREAD
    	IF (WHNIF != NO)
    	   { process error }
    	ENDIF
    
    Note that that errors are reported back in the xxNIF variable. If there are no errors the flag will be set to "N". The PL/B runtime will store the full error code in the S$ERROR$ string. You can display that or do whatever is needed if an error is detected.

    All error conditions are trapped. The trap processing routines are T_xxxx which are common routines used by every I/O routine. The common trap routines put the flag into the TEMPNIF field which is then moved to the file's own xxNIF field after the operation.

    Note the WRITE in the following example. The first thing it does is to UNPACK the xxRECKEY into the xxKEY LIST. That's because the I/O routine just names the LIST, not the individual fields. We want to be certain that the packed up key fields are actually in the fields which are written. That's just a peace of mind move since the caller should have insured that the fields are properly loaded as well.

    The actual code in an xxx-IO.PLS module looks like this.

    NOTE: This example is for standard, single index files. The routine is different for FILELIST file groups and is described after this example.
      %IF WDIO > 1
    ...................................
    .
    wdioread MOVE      ONE,IOMETHOD
             GOTO      #WDIODOIT
    .
    wdiorks  MOVE      FOUR,IOMETHOD
             GOTO      #WDIODOIT
    .
    wdiorkp  MOVE      FIVE,IOMETHOD
             GOTO      #WDIODOIT
    .
      %ENDIF
      %IF WDIO > 2
    wdiowrt  MOVE      TWO, IOMETHOD
             UNPACK    WDRECKEY,WDKEY    
             MOVE      ".",     WD_DOT
             GOTO      #WDIODOIT
    .
    wdioupd  MOVE      SIX, IOMETHOD
             MOVE      ".",     WD_DOT
             GOTO      #WDIODOIT
    .
    wdiodel  MOVE      NO,TEMPNIF
             DELETE    WD_FILE
             CALL      T_OVER IF OVER
             MOVE      TEMPNIF,WDNIF
             RETURN
      %ENDIF
      %IF WDIO > 1
    #wdiodoit MOVE      NO,TEMPNIF
             TRAP      T_IOTRAP IF IO
             TRAP      T_FTRAP  IF FORMAT
             TRAP      T_RTRAP  IF RANGE
             FILEPI  1;WD_FILE
             FILEIO    WD_FILE,WDRECKEY,IOMETHOD;WDKEY,WDRECORD
             CALL      T_OVER IF OVER
             TRAPCLR   IO
             TRAPCLR   FORMAT
             TRAPCLR   RANGE
             MOVE      TEMPNIF,WDNIF
             IF (WDNIF=NO &:
                (IOMETHOD=4 | IOMETHOD=5))
                 PACKKEY  WDRECKEY,WDKEY
             ENDIF
             IF (WDNIF=NO &:
                (IOMETHOD=3 | IOMETHOD=4 | IOMETHOD=5))
                 GOTO   WHIOEDIT
             ENDIF
             RETURN
    .
    whioedit
    .... do any common editing work here ...
             RETURN
      %ENDIF
      


    SPECIAL NOTES for FILELISTs

    Where multiple indexes are needed the new PL/B FILELIST techniques are used. A file list is just a list of files with pre-defined keys. When a WRITE, UPDATE or DELETE is done to the file list, every file in the list is automatically updated. Before FILELIST was added, the programmer would have to treat each of the files separately and coordinate them to insure that the multiple indexes were updates.

    Note that FILELIST are for output instructions only. The reads are done to the individual files. The PL/B runtime recognizes the read to a file that's part of a list and insures that all files are updated in subsequent outputs to the list.

    Note also that the WRITE, UPDATE and DELETE operations must be done to the "primary" file. That file is the first one listed in the group and the first one opened.
      %IF ALIO > 2
    aliowrt  MOVE      TWO, IOMETHOD
             UNPACK    ALRECKEY,ALKEY
             MOVE      ".",     AL_DOT
             GOTO      #ALIO_FL_DOIT
    .
    alioupd  MOVE      SIX, IOMETHOD
             MOVE      ".",     AL_DOT
             GOTO      #ALIO_FL_DOT
    .
    aliodel  MOVE      NO,TEMPNIF
             DELETE    WD_FILE
             CALL      T_OVER IF OVER
             MOVE      TEMPNIF,WDNIF
             RETURN
    .
    #alio_fl_doit
             MOVE      NO, TEMPNIF
             MOVE      ".",AL_DOT
             TRAP      T_IOTRAP IF IO
             TRAP      T_FTRAP  IF FORMAT
             TRAP      T_RTRAP  IF RANGE
             IF (IOMETHOD = 2)
                 CLOCK TIMESTAMP,AL_ADDED
                 WRITE AL_FILELIST;ALKEY,ALRECORD
               ELSE
                 CLOCK TIMESTAMP,AL_CHANGED
                 UPDATE AL_FILELIST;ALKEY,ALRECORD
             ENDIF
             CALL      T_OVER IF OVER
             TRAPCLR   IO
             TRAPCLR   FORMAT
             TRAPCLR   RANGE
             MOVE      TEMPNIF,WDNIF
             RETURN
      %ENDIF
      


    Back to
    Standards Index


    Sequential Processing Loops

    A common requirement for processing indexed files is to establish a starting point in the file then read forward sequentially. For example, say you want to look up the Smiths in an alpha index. You'd position the file to "SMITH", then read forward from that point.

    In many cases you'll ask a user to give you a starting point. In the "SMITH" example they might enter "S" or "SMI" or "SMITH". You can set up the key and do a READ against the file. If the key is found the record will be read. If the key is not found, the file will be positioned to the closest record to the key and the next READKS (key sequential) will get the next record in line.

    Since you won't know if the key the user enters will be a valid one or just a starting point, you have to do some tests around the read to see what the results are. We do this by taking advantage of our standard I/O routines and the way the xxNIF flag is set.

    Here's how we do it:
           MOVE    USERS_KEY,  AARECKEY
           MOVE    ONE,        AANIF
           LOOP
             IF (AANIF = "1")
                 CALL  AAIOREAD
                 MATCH NO, AANIF
                 CONTINUE IF NOT EQUAL
               ELSE
                 CALL  AAIORKS
             ENDIF
             MATCH  NO,AANIF
             BREAK IF NOT EQUAL
    ......       proccess this record
           REPEAT
    
    The AANIF variable is a string. When we move ONE, a one byte numeric variable with the value 1, to AANIF we'll get a string "1". (We could also move a literal.)

    In the loop we test for the "1". That is a first time condition. If it's true we do the READ, which will reset AANIF to a "N" or some value other than "1". The second time through the loop AANIF will never be a "1" so we'll always do the READKS.

    On the off chance that the user enters a valid key, that first READ will find the record. In that case AANIF will be "N". We'll fall out of the IF statment and process that record. Looping back, AANIF will not be a "1" so we'll do the READKS.

    It's an elegant and simple routine.

    Back to
    Standards Index




    The 8802 general file indexing program

    Each of the MMCC systems includes an xxx-8802 program. These are all about the same. Every file in the system is listed in a table along with it's keys and, if required, any secondary indexes.

    At runtime, the 8802 program builds a series of check boxes and buttons on the screen; one for each file.

    The user can put a check in each file that needs to be indexed. There is also a button to "Check All" and one to "Clear All".

    Besides the check box, each file has a description on screen and a button out to the right for viewing the history of the last index. The button shows the two character file ID and the date that the index was last run. If you click that button you are shown the history file from that last index run.

    There's a button to run the indexes for those files which are checked. The program builds a command line and executes the SUNIDXNT utility to index the files.

    The SUNIDXNT command includes the "-l" argument telling it to save the screen output to a log file. After the index is run, that log file is read and stored in the "XC" file. (This was the original use of the XC file... it was called the "index control" file, thus XC.)

    Another button titled "Build BAT File" will build a standard batch (.BAT) file to run all of the indexes. We usually build one of these so that if a problem occurs and we can't run the system, we can go to the command prompt and run the bat file to index the files. That file also provides a quick place to find the key specs for the files.

    Back to
    Standards Index




    The 8804 general file testing program

    Most of the MMCC systems also include an "xxx-8804" program. This one is written to open every file using the standard xxx-OPEN include unit. If the file is missing you get the opportunity to prep it.

    When the files are open, the program shows a list of all file, their names, and descriptions. You can click on any file and get an analysis of the first record of the file. This is handy for testing a new file to be sure that the design record length agrees with the file description.

    When a new file is added to the system, the only change needed to the 8804 program is to add the one equate in the EQU list. When the program is compiled everything comes in via the file include units.

    This is our normal testing routine for new files. Add them to the 8804 and compile it. Run it to prep the file. Then do the first record analysis to insure that the file is OK. If there's a problem, delete the file, fix the FD, compile and test again.

    Note that the old DOS systems used an "8804" program for setting standard screen colors. We're not sure why we didn't catch that when we began to write Windows systems. There's no need for the color setting program in Windows so the name conflict was just missed.

    Back to
    Standards Index


    © 2001 mmcc All Rights Reserved.

    Report problems or suggestions to support@mmcctech.com