HP OpenVMS Systems Documentation

Content starts here

HP BASIC for OpenVMS
User Manual


Previous Contents Index

7.7.2.2 Multiple Maps

When a program contains more than one map with the same name, the storage allocated by these MAP statements is overlaid. This technique is useful for manipulating strings. Figure 7-1 shows multiple maps and maps in use.

Figure 7-1 Multiple Maps


When you use more than one map to access a record buffer, HP BASIC uses the size of the largest map to determine the size of the record. (The RECORDSIZE clause of the OPEN statement can override this map-defined record size. For more information, see Chapter 13.)

You can also use multiple maps to interpret numeric data in more than one way. The following example creates a map area named barray. The first MAP statement allocates 26 bytes of storage in the form of an integer BYTE array. The second MAP statement defines this same storage as a 26-byte string named ABC. When the FOR...NEXT loop executes, it assigns values corresponding to the ASCII values for the uppercase letters A to Z.


MAP (barray) BYTE alphabet(25)
MAP (barray) STRING ABC = 26
FOR I% = 0% TO 25%
    alphabet(I%) = I% + 65%
NEXT I%
PRINT ABC
END

Output


ABCDEFGHIJKLMNOPQRSTUVWXYZ

7.7.3 FILL Items

FILL items reserve space in map and common blocks and in record buffers accessed by MOVE or REMAP statements. Thus, FILL items mask parts of the record buffer and let you skip over fields and reserve space in or between data elements.

FILL formats are available for all data types. Table 7-1 summarizes the FILL formats and their default allocations if no data type is specified.

Table 7-1 FILL Item Formats, Representations, and Default Allocations
FILL Format Representation Bytes Used
FILL Floating-point 4, 8, 16, or 32
FILL( n) n floating-point elements 4 n, 8 n, 16 n, or 32 n
FILL% Integer (BYTE, WORD, LONG, or QUAD) 1, 2, 4, or 8
FILL%( n) n integer elements 1 n, 2 n, 4 n, or 8 n
FILL$ String 16
FILL$( n) n string elements 16 n
FILL$ = m String m
FILL$( n) = m n string elements, m bytes each m * n

Note

In the applicable formats of FILL, n represents a repeat count, not an array subscript. FILL(n), for example, represents n real elements, not n+1.

You can also use data-type keywords with FILL and optional data type- suffixes. The data-type and storage requirements are those of the last data type specified. For example:


MAP (QED) STRING A, FILL$=24, LONG SSN, FILL%, REAL SAL, FILL(5)

This MAP statement uses data-type keywords to reserve space for:

  • A 16-character string variable A
  • 24 bytes of padding
  • One LONG variable, SSN
  • 4 bytes of padding
  • One REAL variable, SAL
  • Space for five floating-point numbers (10, 20, or 80 bytes of padding, depending on the default size for floating-point numbers)

You can specify user-defined data types (RECORD names) for FILL items. In the following example, the first line defines a RECORD of data type X. The MAP statement contains a fill item of this data type, thus reserving space in the buffer for one RECORD of type X.


RECORD X
   REAL Y1, Y2(10)
END RECORD X
MAP (QED) X FILL

See Chapter 8 for more information about the RECORD statement.

7.7.4 Using COMMON and MAP Statements in Subprograms

The COMMON and MAP statements create a block of storage called a PSECT. This common or map storage block is accessible to any subprogram. A HP BASIC main program and subprogram can share such an area by referencing the same common or map name.

The following example contains common blocks that define:

  • A 16-character string field called A by the main program and X by the subprogram
  • A 10-character string field called B by the main program and Z by the subprogram
  • A 4-byte integer field called C by the main program and Y by the subprogram


!In a main program
COMMON (A1) STRING A, B = 10, LONG C
   .
   .
   .
!In a subprogram
COMMON (A1) STRING X, Z = 10, LONG Y

If a subprogram defines a common or map area with the same name as a common or map area in the main program, it overlays the common or map defined in the main program.

Multiple COMMON statements with the same name behave differently depending on whether these statements are in the same program module. If they are in the same program module, then the storage for each common area is concatenated. However, if they are in different program units, then the common areas overlay the same storage. The following COMMON statements are in the same program module; therefore, they are concatenated in a single PSECT. The PSECT contains two 32-byte strings.


COMMON (XYZ) STRING A = 32
COMMON (XYZ) STRING B = 32

In contrast, the following COMMON statements are in different program modules, and thus overlay the same storage. Therefore, the PSECT contains one 32-byte string, called A in the main program and B in the subprogram.


!In the main program
COMMON (XYZ) STRING A = 32
   .
   .
   .
!In the subprogram
COMMON (XYZ) STRING B = 32

Although you can redefine the storage in a common section when you access it from a subprogram, you should generally not do so. Common areas should contain exactly the same variables in all program modules. To make sure of this, you should use the %INCLUDE directive, as shown in the following example:


COMMON (SHARE) WORD emp_num,               &
            DECIMAL (8,0) salary,          &
            STRING wage_class = 2
   .
   .
   .
!In the main program
%INCLUDE "COMMON.BAS"
   .
   .
   .
!In the subprogram
%INCLUDE "COMMON.BAS"

If you use the %INCLUDE directive, you can lessen the risk of a typographical error. For more information about using the %INCLUDE directive, see Chapter 16.

If you must redefine the variables in a PSECT, you should use the MAP statement or a record with variants for each overlay. When you use the MAP statement, use the %INCLUDE directive to create identical maps before redefining them, as shown in the following example. The map defined in MAP.BAS is included in both program modules as a 40-byte string. This map is redefined in the subprogram, allowing the subprogram to access parts of this string.


MAP (REDEF) STRING full_name = 40
   .
   .
   .
!In the main program
%INCLUDE "MAP.BAS"
   .
   .
   .
!In the subprogram
%INCLUDE "MAP.BAS"
MAP (REDEF) STRING first_name=15, MI=1, last_name=24

7.7.5 Dynamic Mapping

Dynamic mapping lets you redefine the position of variables in a static storage area. This storage area can be either a map name or a previously declared static string variable. Dynamic mapping requires the following HP BASIC statements:

  • A declarative statement, such as a MAP statement, allocating a fixed-length storage area
  • A MAP DYNAMIC statement, naming the variables whose positions can change at run time
  • A REMAP statement, specifying the new positions of the variables named in the MAP DYNAMIC statement

The MAP DYNAMIC statement does not affect the amount of storage allocated. The MAP DYNAMIC statement causes HP BASIC to create internal pointers to the variables and array elements. Until your program executes the REMAP statement, the storage for each variable and each array element named in the MAP DYNAMIC statement starts at the beginning of the map storage area.

The MAP DYNAMIC statement is nonexecutable. With this statement, you cannot specify a string length. All string items have a length of zero until the program executes a REMAP statement.

The REMAP statement specifies the new positions of variables named in the MAP DYNAMIC statement. That is, it causes HP BASIC to change the internal pointers to the data. Because the REMAP statement is executable, it can redefine the pointer for a variable or array element each time the REMAP statement is executed.

With the MAP DYNAMIC statement, you can specify either a map name or a previously declared static string variable. When you specify a map name, a MAP statement with the same map name must lexically precede the MAP DYNAMIC statement.

In the following example, the MAP statement creates a storage area named emp_buffer. The MAP DYNAMIC statement specifies that the positions of variables emp_name and emp_address within the map area can be dynamically defined with the REMAP statement.


DECLARE LONG CONSTANT emp_fixed_info = 4 + 9 + 2
MAP (emp_buffer) LONG badge,                     &
                 STRING social_sec_num = 9,      &
                 BYTE name_length,               &
                      address_length,            &
                      FILL (60)

MAP DYNAMIC (emp_buffer) STRING emp_name,        &
                                emp_address

WHILE 1%
GET #1
REMAP (emp_buffer) STRING FILL = emp_fixed_info,              &
                                 emp_name = name_length,      &
                                 emp_address = address_length

NEXT

At the start of program execution, the storage for badge is the first 4 bytes of emp_buffer, the storage for social_sec_num is equal to 9 bytes, and together name_length and address_length are equal to 2 bytes. The FILL keyword reserves 60 additional bytes of storage. The MAP DYNAMIC statement defines the variables emp_name and emp_address whose positions and lengths will change at run time. When executed, the REMAP statement defines the FILL area to be equal to emp_fixed_info and defines the positions and lengths of emp_name and emp_address.

When you specify a static string variable, it must be either a variable declared in a MAP or COMMON statement or a parameter declared in a SUB, FUNCTION, PICTURE, or DEF. The actual parameter passed to the procedure must be a static string variable defined in a COMMON, MAP, or RECORD statement.

The following example shows the use of a static string variable as a parameter declared in a SUB. The MAP DYNAMIC statement specifies the input parameter, input_rec, as the string to be dynamically defined with the REMAP statement. In addition, the MAP DYNAMIC statement specifies a string array A whose elements will point to positions in input_rec after the REMAP statement is executed. The REMAP statement defines the length and position of each element contained in array A. The FOR...NEXT loop then assigns each element contained in array A into array item, the target array.


SUB deblock (STRING input_rec, STRING item())
 MAP DYNAMIC (input_rec) STRING A(1 TO 3)
 REMAP (input_rec) &
     A(1) = 5, &
     A(2) = 3, &
     A(3) = 4
 FOR I = LBOUND(A) TO UBOUND(A)
   item(I) = A(I)
 NEXT I
END SUB

Note that dynamic map variables are local to the program module in which they reside; therefore, REMAP only affects how that module views the buffer.

For more information about using the MAP DYNAMIC and REMAP statements, see the HP BASIC for OpenVMS Reference Manual.


Chapter 8
Creating and Using Data Structures

A data structure is a collection of data items that can contain elements or components of different data types.

The RECORD statement lets you create your own data structures. You use the RECORD statement to create a pattern of a data structure, called the RECORD template. Once you have created a template, you use it to declare an instance of the RECORD, that is, a RECORD variable. You declare a RECORD variable just as you declare a variable of any other type: with the DECLARE statement or another declarative statement. A RECORD instance is a variable whose structure matches that of the RECORD template.

The RECORD statement does not create any variables. It only creates a template, or user-defined data type, that you can then use to create variables.

This chapter describes how to create and use data structures.

8.1 RECORD Statement

The RECORD statement names and defines a data structure. Once a data structure (or RECORD) has been named and defined, you can use that RECORD name anywhere that you can use a BASIC data type keyword. You build the data structure using:

  • Variables of any valid BASIC data type
  • RECORD variables of previously defined RECORD data types
  • Any combination of the two

The following example creates a RECORD called Employee. Employee is a data structure that contains one LONG integer, one 10-character string, one 20-character string, and one 11-character string.


RECORD Employee
  LONG Emp_number
  STRING First_name = 10
  STRING Last_name = 20
  STRING Soc_sec_number = 11
END RECORD Empolyee

To create instances of this data structure, you use declarative statements. In the following example, the first DECLARE statement creates a variable called Emp_rec of data type Employee. The second DECLARE statement creates a one-dimensional array called Emp_array that contains 1001 instances of the Employee data type.


DECLARE Employee Emp_rec
DECLARE Employee Emp_array (1000)

Any reference to a RECORD component must contain the name of the RECORD instance (that is, the name of the declared variable) and the name of the elementary RECORD component you are accessing, separated by two colons (::). For example, the following program assigns values to an instance of the Employee RECORD template:


! Record Template

RECORD Employee

  LONG   Emp_number
  STRING First_name = 10
  STRING Last_name  = 20
  STRING Soc_sec_number = 11

END RECORD Employee
! Declarations

DECLARE Employee Emp_rec

DECLARE STRING Social_security

! Program logic starts here.

INPUT 'Employee number'; Emp_rec::Emp_number
INPUT 'First name';      Emp_rec::First_name
INPUT 'Last name';       Emp_rec::Last_name
INPUT 'Social security'; Social_security
IF Social_security <> ""
THEN
   Emp_rec::Soc_sec_number = Social_security
END IF
PRINT
PRINT "Employee number is: "; Emp_rec::Emp_number
PRINT "First name is: ";      Emp_rec::First_name
PRINT "Last name is: ";       Emp_rec::Last_name
PRINT "Social security is: "; Emp_rec::Soc_sec_number
END

When you access an array of RECORD instances, the array subscript should immediately follow the name of the RECORD variable. The following example shows an array of RECORD instances:


! Record Template

RECORD Employee

  LONG   Emp_number
  STRING First_name = 10
  STRING Last_name  = 20
  STRING Soc_sec_number = 11

END RECORD
! Declarations

DECLARE Employee Emp_array ( 10 )

DECLARE INTEGER Index

DECLARE STRING Social_security

! Program logic starts here.

FOR Index = 0 TO 10

  PRINT
  INPUT 'Employee number'; Emp_array(Index)::Emp_number
  INPUT 'First name';      Emp_array(Index)::First_name
  INPUT 'Last name';       Emp_array(Index)::Last_name
  INPUT 'Social security'; Social_security
  IF Social_security <> ""
  THEN
     Emp_array(Index)::Soc_sec_number = Social_security
  END IF
NEXT Index

FOR Index = 0 TO 10

PRINT
PRINT "Employee number is: "; Emp_array(Index)::Emp_number
PRINT "First name is: ";      Emp_array(Index)::First_name
PRINT "Last name is: ";       Emp_array(Index)::Last_name
PRINT "Social security is: "; Emp_array(Index)::Soc_sec_number

NEXT Index

END

You can have a RECORD that contains an array. When you declare arrays, HP BASIC allows you to specify both lower and upper bounds.


RECORD Grade_record

  STRING     Student_name = 30
  INTEGER    Quiz_scores (1 TO 10)    ! Array to hold ten quiz grades.

END RECORD
! Declarations

DECLARE Grade_record Student_grades ( 5 )

!The Student_grades array holds information on six students
!(0 through 5), each of whom has ten quiz grades (1 through 10).

DECLARE INTEGER I,J
!Program logic starts here.

FOR I = 0 TO 5      !This loop executes once for each student.

  PRINT
  INPUT 'Student name'; Student_grades(I)::Student_name

    FOR J = 1 TO 10  !This loop executes ten times for each student.

      PRINT 'Score for quiz number'; J
      INPUT Student_grades(I)::Quiz_scores(J)

    NEXT J
NEXT I

FOR I = 0 TO 5

  PRINT
  PRINT 'Student name: '; Student_grades(I)::Student_name

    FOR J = 1 TO 10

      PRINT 'Score for quiz number'; J; ": ";
      PRINT Student_grades(I)::Quiz_scores(J)

    NEXT J

NEXT I

END

Because any reference to a component of a RECORD instance must begin with the name of the RECORD instance, RECORD component names need not be unique in your program. For example, you can have a RECORD component called First_name in any number of different RECORD statements. References to this component are unambiguous because every RECORD component reference must specify the record instance in which it resides.

8.1.1 Grouping RECORD Components

A RECORD component can consist of a named group of instances, identified with the keyword GROUP. You use GROUP to refer to a collection of RECORD components, or to create an array of components that have different data types. The GROUP name can be followed by a list of upper and lower bounds, which define an array of the GROUP components. GROUP is valid only within a RECORD block.

The declarations between the GROUP statement and the END GROUP statement are called a GROUP block.

The following example declares a RECORD template of data type Yacht. Yacht is made up of two groups: Type_of_yacht and Specifications. Each of these groups is composed of elementary RECORD components. BASIC also allows groups within other groups.


RECORD Yacht

  GROUP Type_of_yacht
    STRING Manufacturer = 10
    STRING Model = 10
  END GROUP Type_of_yacht
  GROUP Specifications
    STRING Rig = 6
    STRING Length_over_all = 3
    DECIMAL(5,0) Displacement
    DECIMAL(2,0) Beam
    DECIMAL(7,2) Price
  END GROUP Specifications

END RECORD Yacht

8.1.2 RECORD Variants

Sometimes it is useful to have different record components overlay the same record field, in much the same way that multiple maps can overlay the same storage. Such an overlay is called a RECORD variant. You use the keywords VARIANT and CASE to set up RECORD variants.

The following example creates a RECORD template for any three kinds of boats:


RECORD Boat

  STRING Make  = 10
  STRING Model = 10
  STRING Type_of_boat = 1    ! This field contains the value S, P, or C.
                             ! Value S causes the record instance to be
                             ! interpreted as describing a sailboat, value
                             ! P as describing a powerboat, and value C as
                             ! describing a canoe.
  VARIANT

  CASE     ! Sailboats

    STRING Rig   = 20
  CASE     ! Powerboats

    WORD   Horsepower

  CASE     ! Canoes

    WORD   Length
    WORD   Weight

  END VARIANT

END RECORD

The SELECT...CASE statement allows you to access one of several possible RECORD variants in a particular RECORD instance. A RECORD component outside the overlaid fields usually determines which RECORD variant is being used in a particular reference; in this case, the determining RECORD component is Type_of_boat. You can use this component in the SELECT expression.


! Declarations

DECLARE Boat My_boat

! Main program logic starts here

   .
   .
   .
Input_boat_information:

  INPUT 'Make of boat'; My_boat::Make
  INPUT 'Model';        My_boat::Model
  PRINT 'Type of boat (S = Sailboat, P = Powerboat, C = Canoe)';
  INPUT My_boat::Type_of_boat
  SELECT My_boat::Type_of_boat

  CASE "S"

    INPUT 'Sail rig'; My_boat::Rig

  CASE "P"

    INPUT 'Horsepower'; My_boat::Horsepower
  CASE "C"

     INPUT 'Length'; My_boat::Length
     INPUT 'Weight'; My_boat::Weight

  CASE ELSE

     PRINT "Invalid type of boat, please try again."

  END SELECT

The value of the Type_of_boat component determines the format of the variant part of the record.

The following example is a more complex version of the same type of procedure. This program prompts for the RECORD instance components in each variant. When the user responds to the "Wage Class" prompt, the program branches to one of three CASE blocks depending on the value of Wage_class.


!Record templates

RECORD Emp_wage_class

  STRING Emp_name = 30         ! Employee name string.

  STRING Street = 15           !
  STRING City = 20             ! These components make up the
  STRING State = 2             ! employee address field.
  DECIMAL(5,0) Zip             !

  STRING Wage_class = 1
  VARIANT

    CASE

      GROUP Hourly                     ! Hourly workers.

        DECIMAL(4,2) Hourly_wage       ! Hourly wage rate.
        SINGLE Regular_pay_ytd         ! Regular pay year-to-date.
        SINGLE Overtime_pay_ytd        ! Overtime pay year-to-date.

      END GROUP Hourly
    CASE

      GROUP Salaried                   ! Salaried workers.

        DECIMAL(7,2) Yearly_salary     ! Yearly salary.
        SINGLE Pay_ytd                 ! Pay year-to-date.

      END GROUP Salaried
    CASE

      GROUP Executive                  ! Executives.

        DECIMAL(8,2) Yearly_salary     ! Yearly salary.
        SINGLE Pay_ytd                 ! Pay year-to-date.
        SINGLE Expenses_ytd            ! Expenses year-to-date.

      END GROUP Executive

  END VARIANT

END RECORD
! Declarations:

  DECLARE Emp_wage_class Emp

! Main Program logic starts here.


LINPUT "Name"; Emp::Emp_name          ! Use LINPUT statements for
LINPUT "Street"; Emp::Street          ! string fields so the entire
                                      ! string is assigned to the
LINPUT "State"; Emp::State            ! variable.
INPUT  "Zip Code"; Emp::Zip
LINPUT "Wage Class"; Emp::Wage_class
SELECT Emp::Wage_class

CASE "A"
  INPUT 'Rate';Emp::Hourly_wage
  INPUT 'Regular pay';Emp::Regular_pay_ytd
  INPUT 'Overtime pay';Emp::Overtime_pay_ytd

CASE "B"
  INPUT 'Salary';Emp::Salaried::yearly_salary
  INPUT 'Pay YTD';Emp::Salaried::pay_ytd

CASE "C"
  INPUT 'Salary';Emp::Executive::yearly_salary
  INPUT 'Pay YTD';Emp::Executive::pay_ytd
  INPUT 'Expenses';Emp::Expenses_ytd

END SELECT

Variant fields can appear anywhere within the RECORD instance. When you use RECORD variants, you imply that any RECORD instance can contain any one of the listed variants. Therefore, if each variant requires a different amount of space, BASIC uses the case that requires the most storage to determine the space allocated for each RECORD instance.


Previous Next Contents Index