Compaq BASIC for OpenVMS
Alpha and VAX Systems
User Manual


Previous Contents Index

8.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 BASIC statements:

The MAP DYNAMIC statement does not affect the amount of storage allocated. The MAP DYNAMIC statement causes 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 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 Compaq BASIC for OpenVMS Alpha and VAX Systems Reference Manual.


Chapter 9
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.

9.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:

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, 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.

9.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 

9.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.

9.1.3 Accessing RECORD Components

To access a particular elementary component within a RECORD that contains other groups, you use the name of the declared RECORD instance, the group name (or group names, if groups are nested), and the elementary component name, each separated by double colons (::).

In the following example, the PRINT statement displays the Rig component in the Specifications group in the variable named My_yacht. The RECORD instance name qualifies the group name and the group name qualifies the elementary RECORD component. The elementary component name, qualified by all intermediate group names and by the RECORD instance name, is called a fully qualified component. The full qualification of a component is called a component path name.


DECLARE Yacht My_yacht 
 
   .
   .
   .
 
PRINT My_yacht::Specifications::Rig 

Because it is cumbersome to specify the entire component path name, BASIC allows elliptical references to RECORD components. GROUP names are optional in the component path name unless:

The rules for using elliptical references are as follows:

The following example shows that using the complete component path name is valid but not required. The assignment statement uses the fully qualified component name; the PRINT statement uses an elliptical reference to the same component, omitting Extended_family and Nuclear_family GROUP names. Note that the Children GROUP name is required because the GROUP is an array; the elliptical reference to this component must include the desired array element, in this case the second element of the Children array.


! RECORD templates: 
 
RECORD Family 
 
  GROUP Extended_family 
 
    STRING Grandfather(1) = 30    ! Two-element fixed-length string 
    STRING Grandmother(1) = 30    ! arrays for the names of maternal 
                                  ! and paternal grandparents. 
    GROUP Nuclear_family 
 
      STRING Father = 30          ! Fixed-length strings for the names 
      STRING Mother = 30          ! of parents. 
        GROUP Children (10)       ! An 11-element array for the names and 
                                  ! gender of children. 
          STRING Kid = 10 
          STRING Gender = 1 
 
        END GROUP Children 
 
    END GROUP Nuclear_family 
 
  END GROUP Extended_family 
 
END RECORD 
! Declarations 
 
DECLARE Family My_family 
! Program logic starts here. 
 
My_family::Extended_family::Nuclear_family::Children(1)::Kid = "Johnny" 
 
PRINT My_family::Children(1)::Kid 
 
END 

Output


Johnny 


! RECORD Templates. 
 
RECORD Test 
 
  INTEGER Test_integers(2)     ! 3-element array of integers. 
 
  GROUP Group_1                ! Single GROUP containing: 
 
    REAL   My_number           ! a real number and 
    STRING Group_1_string      ! a 16-character (default) string 
 
  END GROUP 
  GROUP Group_2(5)             ! A 6-element GROUP, each element containing: 
 
    INTEGER My_number          ! an integer and 
    DECIMAL Group_2_decimal    ! a DECIMAL number. 
 
  END GROUP 
 
END RECORD 
! Declarations 
 
DECLARE Test Array_of_test(10)  ! Create an 11-element array of type Test... 
DECLARE Test Single_test        ! ...and a separate single instance of type 
                                ! Test. 

The minimal reference to the string Group_1_string in RECORD instance Array_of_test is as follows:


Array_of_test(i)::Group_1_string 

In this case, i is the subscript for array Array_of_test. Because the RECORD instance is itself an array, the reference must include a specific array element.

Because Single_test is not an array, the minimal reference to string Group_1_string in RECORD instance Single_test is as follows:


Single_test::Group_1_string 

The minimal reference for the REAL variable My_number in GROUP Group_1 in RECORD instance Array_of_test is as follows:


Array_of_test(i)::Group_1::My_number 

Here, i is the subscript for array Array_of_test. The minimal reference to the REAL variable My_number in RECORD instance Single_test is as follows:


Single_test::Group_1::My_number 

Because there is a variable named My_number in groups Group_1 and Group_2, you must specify either Group_1::My_number or Group_2(i)::My_number. In this case, extra component names are required to resolve an otherwise ambiguous reference.

The minimal reference to the DECIMAL variable Group_2_decimal in RECORD instances Array_of_test and Single_test are the fully qualified references. In the following examples, i is the subscript for array Array_of_test and j is an index into the group array Group_2. Even though Group_2_decimal is a unique component name within RECORD instance Single_test, the element of array Group_2 must be specified. In this case, the extra components must be specified because each element of GROUP Group_2 contains a component named Group_2_decimal.


Array_of_test(i)::Group_2(j)::Group_2_decimal 
 
Single_test::Group_2(j)::Group_2_decimal 

You can assign all the values from one RECORD instance to another RECORD instance, as long as the RECORD instances are identical except for names.

In the following example, RECORD instances First_test1, Second_test1, and the individual elements of array Array_of_test1 have the same form: an array of four groups, each of which contains a 10-byte string variable, followed by a REAL variable, followed by an INTEGER variable. Any of these RECORD instances can be assigned to one another.


!RECORD Templates 
 
RECORD Test1 
 
  GROUP Group_1(4) 
 
    STRING   My_string_1 = 10 
    REAL     My_real_1 
    INTEGER  My_integer_1 
 
  END GROUP 
 
END RECORD 
 
RECORD Test2 
 
  GROUP Group_2 
 
    STRING  My_string_2 = 10 
    REAL    My_real_2 
    INTEGER My_integer_2 
 
  END GROUP 
 
END RECORD 
RECORD Test3 
 
  STRING   My_string_3 = 10 
  REAL     My_real_3 
  INTEGER  My_integer_3 
 
END RECORD 
!Declarations 
 
DECLARE Test1 First_test1,         & 
              Second_test1,        & 
              Array_of_test1(3) 
 
DECLARE Test2 First_test2 
 
DECLARE Test3 First_test3,         & 
              Array_of_test3(10) 
!Program logic starts here 
 
! A single RECORD instance is assigned to another single instance 
 
First_test1 = Second_test1 
 
! An array element is assigned to a single instance 
 
Second_test1 = Array_of_test1(2) 
 
! And vice versa 
 
Array_of_test1(2) = Second_test1 

Further, you can assign values from single RECORD instances to groups contained in other instances.

In the following example, Array_of_test1 and First_test1 do not have the same form because Array_of_test1 is an array of RECORD Test1 and First_test1 is a single instance of RECORD Test1. Therefore, First_test1 and Array_of_test1 cannot be assigned to one another.


! A single instance is assigned to one group 
 
Array_of_test1(3)::Group_1(2) = First_test1 
 
! An array element is assigned a value from 
! a group contained in another array instance 
Array_of_test3(5) = Array_of_test1(3)::Group_1(3) 

The examples shown in this chapter explain the mechanics of using data structures. See Chapter 13 for more information about using data structures as parameters. See Chapter 14 for more information about using data structures for file input and output.


Previous Next Contents Index