# Compaq BASIC for OpenVMS
Alpha and VAX Systems
User Manual

Previous | Contents | Index

# 13.4 Invoking Subprograms

The following sections describe how to:

- Invoke subprograms
- Pass parameters to subprograms

## 13.4.1 Invoking SUB Subprograms

The CALL statement transfers control to a subprogram, and optionally passes arguments to it. The parameters in the CALL statement specify variables, constants, expressions, array elements, or entire arrays to be passed to the subprogram. You can also specify a function in the argument list. BASIC passes the value returned by the function to the subprogram. If possible, BASIC converts the actual arguments to the data type specified in the EXTERNAL statement. BASIC signals an error when the conversion is not possible.

The following example shows a BASIC main program calling a BASIC subprogram. The main program prompts for three integers: A, B, and C. It then passes these variables as parameters to the SUB subprogram. The subprogram prints the sum of these variables and returns control to the calling program.

    PROGRAM get_input
      OPTION TYPE = EXPLICIT
      EXTERNAL SUB SUB01(LONG, LONG, LONG)
      DECLARE LONG A, B, C
      INPUT "Please type three integers"; A, B, C
      CALL SUB01 (A, B, C)
    END PROGRAM

    SUB SUB01 (LONG X, LONG Y, LONG Z)
      PRINT "The sum is"; X + Y + Z
    END SUB

## 13.4.2 Invoking FUNCTION Subprograms

The following example performs the same task as the SUB program; however, this example uses a FUNCTION subprogram that returns the value to the main program and the main program prints the result:

    PROGRAM invoke_funct
      EXTERNAL LONG FUNCTION FUN01(LONG, LONG, LONG)
      DECLARE LONG A, B, C
      INPUT "Please type three integers"; A, B, C
      PRINT "The sum is"; FUN01(A, B, C)
    END PROGRAM

    FUNCTION LONG FUN01 (LONG X, LONG Y, LONG Z)
      FUN01 =  X + Y + Z
    END FUNCTION

If you do not assign a value to the function name and you do not specify a return value on an EXIT FUNCTION or END FUNCTION statement, the function returns zero or the null string.

Note that when writing FUNCTION subprograms, you must specify a data type for the function in both the main program EXTERNAL statement and the subprogram FUNCTION statement. This data type keyword specifies the data type of the value returned by the function subprogram. You should ensure that the data type specified in an EXTERNAL FUNCTION statement matches the data type specified in the FUNCTION statement.

If you declare a FUNCTION subprogram with an EXTERNAL statement and use the CALL statement to invoke the function, it executes correctly but the function value is not available. Note that BASIC still performs parameter validation when you invoke a function with the CALL statement.

Note that you cannot use the CALL statement to invoke a string or packed decimal function.

# 13.5 Returning Program Status

A PROGRAM unit lets you return a status from a BASIC image by optionally including an integer expression with the END PROGRAM and EXIT PROGRAM statements. After executing a program, you can examine this status by checking the DCL symbol $STATUS. By default, BASIC returns a status of 1, indicating success. Success is signaled with an odd numbered status value, while an error is signaled with an even numbered value. $STATUS contains the same value as the integer expression for the exit status in the EXIT and END PROGRAM statements. Note that if a program is terminated with an EXIT PROGRAM statement, the expression on the EXIT PROGRAM statement overrides any expression on the END PROGRAM statement.

In the following example, exit_status contains the status value returned by the program. After program execution, $STATUS has the value of exit_status. You can examine the value of $STATUS and display the corresponding message text with the lexical function F$MESSAGE at DCL level, as shown in the following example:

    PROGRAM Venture
      DECLARE INTEGER exit_status,                     &
              REAL capital
      EXTERNAL LONG CONSTANT SS$_BADPARAM
      EXTERNAL SUB play_safe(INTEGER),                 &
               minor_risk(INTEGER),major_risk(INTEGER)
      Exit_status = 1%
      SET NO PROMPT
      How_much:
      INPUT "Enter the amount of your free capital $";capital
      SELECT capital
         CASE = 0
                 exit_status = SS$_BADPARAM
                 EXIT PROGRAM exit_status
         CASE < 5000
                 CALL play_safe(capital)
         CASE < 15000
                 CALL minor_risk(capital)
         CASE < 50000
                 CALL major_risk(capital)
         CASE ELSE
                 PRINT "I can't cope with that amount, try again."
      END SELECT
      GOTO How_much
       .
       .
       .
    END PROGRAM exit_status

After program execution, you can examine the status of the program at DCL level:

    $ SHOW SYMBOL $STATUS
    $ STATUS = "%X10"
    $ error_text = F$MESSAGE(%X10)
    $ SHOW SYMBOL error_text
    ERROR_TEXT = "SYSTEM-W-BADPARAM, bad parameter value"

The PROGRAM statement is always optional; EXIT PROGRAM and END PROGRAM are legal without a matching PROGRAM statement. Without a PROGRAM statement, these statements still exit the main compilation unit. The EXIT PROGRAM and END PROGRAM statements are not valid within SUB, FUNCTION, or PICTURE subprograms.

# Chapter 14
File Input and Output

This chapter explains BASIC file organizations and record operations that are implemented through OpenVMS VAX Record Management Services (RMS). For a more thorough understanding of file organization and file and record operations, see the OpenVMS Record Management Services Reference Manual.

RMS stores data in physical blocks. A block is the smallest number of bytes BASIC transfers in a read or write operation. On disk, a block is 512 bytes. On magnetic tape, it is 18 to 8192 bytes.

RMS stores one or more data records in each block. A data record can also be divided into smaller units, called fields. A data record can be smaller than, equal to, or larger than a disk block.

# 14.1 Record Formats

The format of a record determines how RMS stores the record in a block. You specify the record format in an OPEN statement. The following are valid BASIC record formats:

- Fixed-length records
- Variable-length records
- Stream records

## 14.1.1 Fixed-Length Records

Fixed-length records are all the same length. RMS stores fixed-length records as they appear in the record buffer, including any spaces or null characters following the data; this process is called padding. Processing these records involves less overhead than other record formats; however, this format can use disk storage space less efficiently than variable-length or stream records.

## 14.1.2 Variable-Length Records

Variable-length records can have different lengths, but no record can exceed a maximum size set for the file. When the record is written to a file, RMS adds a record length header that contains the length of the record (excluding the header) in bytes. When your program retrieves a record, this header is not included in the record buffer. While variable-length records usually make more efficient use of storage space than fixed-length records, manipulation of the record length headers generates processor overhead.

## 14.1.3 Stream Records

BASIC interprets stream records as a continuous sequence, or stream, of bytes. Unlike the fixed- and variable-length formats, stream records do not contain control information such as record counts, segment flags, or other system-supplied boundaries. Stream records are delimited by special characters or character sequences called terminators. Note that stream record formats are valid only in sequential files.

RMS defines the following types of stream record formats:

- STREAM records can be delimited by any special character (usually a carriage return/line-feed pair).
- STREAM_LF records must be delimited by a line-feed character.
- STREAM_CR records must be delimited by a carriage return.

While you can access existing files of any one of these stream record formats, BASIC creates new stream files only in the STREAM format; you can create files of the other two stream record formats by modifying the RMS FAB control structure in a USEROPEN routine. For more information about USEROPEN routines, see Section 14.8.11.

# 14.2 File Organizations

BASIC provides the following file organizations:

- Terminal-format
- Sequential
- Relative
- Indexed
- Virtual

If you do not specify a file organization when creating a file, the default is a terminal-format file (a sequential file with variable-length records). The following sections describe each type of file organization.

## 14.2.1 Terminal-Format Files

A terminal-format file is a sequential file of variable-length records. Terminal-format files are the default; that is, you create a terminal-format file when you do not specify a file organization when you open a file. You can then use the PRINT, INPUT, INPUT LINE, and LINPUT statements to access a terminal-format file. See Chapter 6 and Chapter 7 for more information about terminal-format files.

## 14.2.2 Sequential Files

A sequential file contains records that are stored in the order they are written. Sequential files can contain records of any valid BASIC record format: fixed-length, variable-length, or stream. You usually read a sequential file from the beginning; therefore, a sequential file is most useful when you access the data sequentially each time you use it. You can also access sequential fixed-length records randomly by specifying a record number if the file resides on disk. In either case, sequential files can reside on both disk and magnetic tape devices, and those stored on disk support shared access.

## 14.2.3 Relative Files

A relative file contains a series of cells that are numbered consecutively from 1 to n, where n represents the relative record number. Each cell can contain only a single record. For fixed-length records, the length of each cell equals the record length plus 1 byte. For variable-length records, the length of the cell equals the maximum record size plus 3 bytes.

You can access records in a relative file either sequentially or randomly. The relative record number is the key value in random access mode; that is, to access a record in a relative file in random access mode, you must know the relative record number of that record. You can add records to a relative file either at the end of the file or into any empty cell.

Relative files are most useful when randomly accessed and when the record can be identified by its cell number (for example, when inventory numbers correspond to cell numbers). Relative files support shared access. You can delete records from relative files, but not sequential files.

## 14.2.4 Indexed Files

An indexed file contains data records that are sorted in ascending or descending order according to a primary index key value. The index key is a record field (or set of fields) that determines the order in which the records are logically accessed. Keys must be variables declared in a MAP statement. Keys can be any one of the following:

- Strings
- WORD integers
- LONG integers
- Quadword integers
- Packed decimal numbers

String keys can also be segmented; the key can be composed of up to eight string variables in a map. Quadword keys must be referenced using a record or group exactly 8 bytes long.

Along with the primary index key value, you can also specify up to 254 alternate keys; RMS creates one index for each key you specify. For each of these keys you can also specify either an ascending or descending collating sequence. Each index is stored as part of the file, and each entry in the index contains a pointer to a record. Therefore, each key you specify corresponds to a sorted list of record pointers.

An indexed file of library books, for example, might be ordered by book title; that is, the title of the book is the primary key for the file. The keys for alternate indexes might include the author's name and the book's Library of Congress number. Neither of these alternate indexes contains the actual records; instead, they contain sorted pointers to the appropriate records.

Indexed files are most useful when randomly accessed or when you want to access the records in more than one way.

## 14.2.5 Virtual Files

A virtual file is a random access file that stores one or more data records or virtual array elements in each physical 512-byte disk block. You create a virtual file by specifying ORGANIZATION VIRTUAL as part of the OPEN statement. Apart from virtual arrays and compatibility with BASIC and BASIC-PLUS-2, you should use sequential fixed-length instead of virtual files, as they provide the same capabilities. See Section 14.5 for more information about accessing the individual records in a disk block.

# 14.3 Record Access and Record Context

Record access modes determine the order in which your program retrieves or stores records in a file. They determine the record context: the current record and the next record to be processed. When your program successfully executes any record operation, the current record and next record pointers can change. If a record operation is unsuccessful, these pointers do not change.

The record access modes valid for RMS are as follows:

- Sequential access---valid on any file organization
- Random-by-record number access---valid on sequential fixed and all relative files
- Random-by-key access---valid on indexed files
- Random-by-RFA (Record File Address) access---valid on any RMS file located on disk

With sequential access, the next record is the next logical record in the file. In the case of relative files, the next logical record is the next existing record (deleted or never-written records are skipped). In the case of indexed files, the next logical record is the record with the next ascending or descending value in the current key of reference depending on that key's collating sequence. You can therefore access relative or indexed files sequentially by not specifying a relative record number or key value.

You can also access sequential fixed-length and relative files randomly by record number; that is, you can specify the record number of the record to be retrieved. For relative files, this record number corresponds to the cell number of the desired record.

You can access indexed files randomly by key. The key specification includes a primary or alternate key and its value. BASIC retrieves the record corresponding to that value in the particular key chosen.

You can access disk files of any organization by Record File Address (RFA); this means that you specify an RFA variable whose value uniquely identifies a particular record. The RFA requires six bytes of information. For more information about RFAs, see Section 14.6.10.

# 14.4 I/O and Record Buffers

An I/O buffer is a storage area in your program that RMS uses to store data for I/O operations. You do not have direct access to I/O buffers; they are controlled entirely by RMS. The I/O buffer holds blocks of data transferred from the device, and its size is always greater than or equal to that of the record buffer. For more information about the amount of storage allocated for I/O buffers, see the OpenVMS Record Management Services Reference Manual.

A record buffer is another storage area in your program. You have direct access to and control of the record buffer. When your program reads a record from a file, the information is transferred from the file to the I/O buffer in one large chunk of data, and then the requested record is transferred to the record buffer. When your program writes a record, data is transferred from the record buffer to the I/O buffer, and then to the file either when the I/O buffer is full or when other blocks need to be read in.

You can use MAP statements to create static record buffers and associate program variables with areas (fields) of the buffer. Static record buffers are buffers whose size does not change during program execution and whose program variables are always associated with the same fields in the buffer.

You can create dynamic record buffers with either a MAP DYNAMIC or a REMAP statement. These statements, when used after a MAP statement, associate or reassociate a particular program variable with a different area (field) of the record buffer; however, the total size of a record buffer does not change during program execution.

Note If you do not specify a map, you must use MOVE TO and MOVE FROM statements to transfer data back and forth from the record buffer to program variables; however, MOVE statements do not transfer data to or from a file.

# 14.5 Accessing the Contents of a Record

BASIC provides the following methods for accessing the contents of a record:

- MAP statement
- MAP DYNAMIC and REMAP statements (dynamic mapping)

- MOVE statements
- FIELD statements

The FIELD statement is a declining feature and is not recommended for new program development. It is recommended that you use either MAP statements, dynamic mapping, or MOVE statements to access record contents.

## 14.5.1 MAP Statement

Normally, a record is divided into predetermined fields, the sizes of which are known at compile time. The MAP statement creates the storage area for this record and determines its total size. The following examples show how the MAP statement creates the record storage area:

Example 1

    RECORD name_addr
      STRING last_name = 15,    &
             street_name = 30,  &
      INTEGER house_num
    END RECORD
    MAP (student_buffer) name_addr student_info

Example 2

    MAP (Emp_rec)
        STRING Emp_name = 25,                    &
        LONG Badge,                              &
        STRING Address = 25,                     &
        STRING Department = 4

## 14.5.2 MAP DYNAMIC and REMAP Statements

There are situations where predetermined fields are not applicable or possible. In these situations, you must perform record defielding in your program at run time. Using the MAP DYNAMIC statement, you can specify the variables in the map whose positions can change at run time. The REMAP statement then specifies the new positions of the variables named in the MAP DYNAMIC statement.

The following example shows how you can use MAP, MAP DYNAMIC, and REMAP to deblock your record fields. The MAP statement allocates a storage area of 2048 bytes and names it Emp_rec. The MAP DYNAMIC statement specifies that the variables Emp_name, Badge, Address, and Department are all located in Emp_rec, and that their positions can be changed at run time with the REMAP statement. The REMAP statement then redefines these variables to their appropriate sizes.

    MAP (Emp_rec) FILL$ = 2048

    MAP DYNAMIC (Emp_rec)                                    &
        STRING Emp_name,                                     &
        LONG Badge,                                          &
        STRING Address,                                      &
        STRING Department

    REMAP (Emp_rec) FILL$ = Record_offset,                   &
        Emp_name = 25,                                       &
        Badge,                                               &
        Address = 25,                                        &
        Department = 4

Note that when accessing virtual or sequential files, you can specify a RECORD clause for the GET statement. The following example opens a virtual file with each block containing 512 bytes. However, each block contains 4 logical records that are 128 bytes long. Each of these logical records consists of a 20-character first name field, a 44-character last name field, and a 64-character company name field.

    DECLARE WORD Record_number
    MAP (Virt) STRING FILL = 512
    MAP DYNAMIC (Virt) STRING First_name,        &
                      Last_name,                 &
                      Company
    OPEN "VIRT.DAT" FOR INPUT AS FILE #5,        &
                 VIRTUAL, MAP Virt
    Record_number = 1%
    WHEN ERROR IN
      WHILE -1%
         GET #5, RECORD Record_number
         FOR I% = 0% TO 3%
            REMAP (Virt) STRING FILL = (I% * 128%),  &
                  First_name = 20,                   &
                  Last_name = 44,                    &
                  Company = 64
            PRINT First_name, Last_name, Company
         NEXT I%
         Record_number = Record_number + 1%
      NEXT
    USE
       IF ERR = 11%
             THEN
                PRINT "Finished"
                CONTINUE 32767
             ELSE EXIT HANDLER
             END IF
    END WHEN
    END

After the first 512-byte block is brought into memory, the FOR...NEXT loop deblocks the data into 128-byte logical records. At each iteration of the FOR...NEXT loop, the REMAP statement uses the loop variable to mask off 128-byte sections of the block.

For more information about the MAP DYNAMIC and REMAP statements, see Chapter 8 and the Compaq BASIC for OpenVMS Alpha and VAX Systems Reference Manual.

Previous | Next | Contents | Index