We have seen that a declaration made in an outer block is valid inside blocks which are enclosed by that block or which are prefixed by it, but not vice versa. This rule covers the use of identifiers declared in prefixing classes within subclasses.
Further we have seen that a declaration in an inner block will supercede that in an outer block where the names of the identifiers are the same. This is true even when the type of the declaration is the same in both outer and inner block. A different location is indicated for a variable, a different body for a procedure or a class.
This is a brief summary of the scope rules that we have used so far. They are very important and it is essential to grasp them fully.
In addition to these basic rules, we have seen that when and qua are provided and allow the scope rules for classes and subclasses to be bent. They allow a reference to a parent to be extended to access an object of a subclass or access to attributes of a class from a reference to a subclass in which their identifiers have been redeclared. There are three more features of SIMULA which concern themselves with relaxing the scope rules for classes. This chapter will examine these in some detail.
This reverses the normal rule about identifiers in inner blocks being inaccessible to outer blocks. As long as a virtual declaration is made in the prefixing class, any uses of that identifier in the body of the prefixing class will be assumed to refer to the declaration of that identifier within the body of the innermost class containing such a declaration.
This is rather simpler in practice than it sounds in theory. Let us use an example to show how it works in the simplest cases.
Example 18.1 shows a class Prefix containing two virtual declarations a procedure Banner and a label Dest. Of these, only Dest is matched by an actual label in the body of Prefix. Banner is unmatched.
Example 18.1: Virtual quantities.
begin class Prefix; virtual: procedure Banner; label Dest; begin Banner; ! Call a virtual procedure with no match here; goto Dest; Dest: ! Default match for goto, skips nothing; OutText("Dest in Prefix used"); OutImage end++of++Prefix; Prefix class Stars; begin procedure Banner; begin OutText("************************************"); OutImage end--of--Banner; OutText("This should not be printed"); Dest: ! This will replace the Dest in Prefix; OutText("Dest in Stars used"); OutImage end++of++Stars; Prefix class Dashes; begin procedure Banner; begin OutText("------------------------------------"); OutImage end--of--Banner; OutText("This should be printed"); ! No local match for Dest; OutImage; OutText("Dest in Prefix used"); OutImage end++of++Dashes; new Stars; new Dashes; end**of**programPrefix is used as a parent class to both Stars and Dashes. Each of these contains a procedure called Banner, providing a match for the virtual procedure in their parent. Only Stars contains a label called Dest to match the virtual declaration in Prefix.
Prefix contains a call on Banner. Since it has no match for this itself, the statement
new Prefixwould be reported as a runtime error.
new Stars or new Dashesare perfectly legal, however. The procedure declared in the subclass is called from the parent's body, via the parent's virtual declaration, which is matched in the subclass.
Prefix also contains a go to statement, which uses label Dest as its designational expression or destination. Since there is a matching label in the body of Prefix, this would cause no problems if new Prefix were used. For new Dashes, the only match would be the label in Prefix itself and so the goto would lead to this, with no effect from the virtual declaration. For new Stars the existence of a declaration of label Dest inside the body of the subclass would cause the goto to jump to this rather than to the label in the parent. The scope rules are reversed.
Consider the example carefully and try to work out what its output would be like. Then, of course, you should run it to check.
The parent class can be written as a template. Its actions can be calls on virtual procedures and jumps to virtual labels. These can be left as blanks, to be filled in by the subclass, or given default matches in the parent class, which can be overidden by a subclass.
Take a practical example. We want to provide a class for use as a prefix and we want this class to write messages at the start of its actions and at the end. It also contains some procedures for writing headings, and starting paragraphs. It is a simple component in a package.
These are all candidates for virtual procedures. They provide clearly defined facilities, for which meaningful defaults can be given. They are all likely to need to be tailored to fit some purposes.
The syntax is the keyword virtual, followed by a colon, followed by a list of type specifiers separated by semi-colons. These are of the same form as the type specifiers for parameters, but only the types label, switch, procedure and type procedure are allowed.
Each identifier specified as virtual is either matched by a normal declaration inside the class body which follows or unmatched. It is not an error to use an unmatched virtual identifier inside this class body, so long as this class is only used to prefix subclasses which contain a match.
When an object is created which has virtual quantities specified in one or more classes on its prefix chain, all these must be matched by declarations in those classes or in classes inner to them. Where matches exist at more than one equal or inner level, the innermost is used.
Example 18.2: Use of virtual label in a package.
class SafeMths; begin class SafeDivide(Dividend,Divisor); real Dividend, Divisor; virtual: label ZeroAction,Perform; begin real Result; if Divisor=0 then go to ZeroAction; go to Perform; ZeroAction: Divisor := 0.0001; Inner; Perform: Result := Dividend/Divisor; Complete: end++of++SafeDivide; class SafeAdd(Val1,Val2); real Val1,Val2; virtual: label SafeLab; begin ! Various actions; end++of++SafeAdd; ! Various other maths classes; end--of--SafeMaths;The class SafeMaths is a package of specially safeguarded arithmetic features. Most are shown only in outline, but SafeDivide will demonstrate the general idea and is given in full.
Class SafeDivide has two virtual labels ZeroAction and Perform defined. Before performing its division the divisor is checked. If it is zero, the division will result in a runtime error. (I am sorry to get mathematical again. I am sure we all remember that an attempt to divide by zero is illegal in normal arithmetic.) To allow the program to continue a goto is performed to ZeroAction when a zero divisor is found. If a non-zero divisor is found, a goto is carried out to the other virtual label, Perform, to avoid the zero actions. Here a normal real division is performed. This is followed by a non-virtual label at the end of the class, called Complete. Note that Complete is placed after the Inner statement, while ZeroActions and Perform are placed after it.
The instructions following ZeroAction provide a default sequence in the event of an attempt to divide by zero. The decision has been taken to replace zero with a suitably small decimal fraction and then divide. This removes the possibility of a runtime error, but may not give a suitable result. Example 18.3 shows a program using SafeMaths, where division is handled by a subclass of SafeDivide. The subclass has provided its own default action when a zero divisor is found.
Example 18.3: Use of package with virtual label, SafeMaths.
begin external class SafeMths; SafeMths begin SafeDivide class MyDivide; begin ZeroActions: ! Override the default ZeroActions; Result := 9999999999999.9999999; go to Complete; end++of++MyDivide; OutFix(new MyDivide(6,0).Result,4,20); OutImage end..of..prefixed..block end**of**programBy providing its own match for ZeroAction the subclass can choose to substitute a very large value for the result of the division. This would probably represent the largest legal value for a real on the particular system. The value in the example is chosen at random.
Example 18.4: Fully specified virtual procedure.
class Virtuous; virtual:procedure CharVal is character procedure CharVal(IntVal); integer IntVal;; begin ! Declarations and actions; end--of--Virtuous;Once such a virtual procedure is specified all matches must have exactly the same form.
18.2 What would be the effect of removing the inner statement in example 18.2?
18.3 Rewrite example 18.2 using a virtual procedure to replace the virtual label.
SIMULA allows two levels of protection of attributes. The first protects the attribute from use outside of the body of the class, any subclasses or blocks prefixed by the class or its subclasses. This is achieved by specifying that attribute as protected. Example 18.5 shows the use of a protected specifier.
Example 18.5: Use of protected specifier.
begin class Counter; protected Tally; begin integer Tally; procedure Increment; Tally := Tally + 1; procedure Decrement; Tally := Tally - 1; integer procedure Total; Total := Tally; end++of++Counter; Counter class DoubleCounter; begin procedure Increment; Tally := Tally + 2; procedure Decrement; Tally := Tally - 2; end++of++DoubleCounter; ref(Counter) Count1; ref(DoubleCounter) Count2; integer I; Count1 :- new Counter; Count2 :- new DoubleCounter; for I := 1 step 1 until 10 do begin Count1.Increment; Count2.Decrement end..of..for; OutInt(Count1.Total,8); OutInt(Count2.Total,8); OutImage end**of**programNotice that the form of a protected specifier is the same as a type specifier or a mode specifier, although it is only allowed for classes.
The example shows the integer Tally specified as protected. This means that it can be accessed directly inside class Counter and Counter class DoubleCounter, but only through calls on Increment, Decrement and Total from the main program. The central attribute is kept safe from outside manipulation.
This feature allows object oriented programming to become a much more powerful concept. A data structure can be defined which can only be manipulated in ways which are also defined in the same class declaration, as procedure attributes. Attempts at cheating by manipulating them directly will be prevented.
At the same time, the range of defined operations on the data can be extended, as can the data structure. This can be done by creating a subclass of the original definition.
The protected specification allows extensible but externally secure objects for use in programming. The second level of protection allows complete internal security as well.
An attribute must be specified as protected in the class where it is declared, not in an inner one.
To achieve this an attribute must be specified as hidden as well as protected. It is not necessary to make an attribute hidden until some subclass of that in which it is declared and specified protected.
The hidden specifier has exactly the same form as the protected specifier, except that the keyword hidden replaces protected.
If an attribute has been specified as virtual at an outer level, specifying it as hidden at the current level means that no matches will be made at levels inner to the current one.
Example 18.6 shows the use of hidden. 18.6a is a package used to prefix 18.6b. The integer Tally is not directly accessible outside the package, since it is specified hidden in classes Count and Double Count. The integer CheckValue is only accessible inside the class BasicCount, which prefixes both Count and DoubleCount.
Example 18.6: Use of hidden and protected.
a) Package using both. class TallyPack; begin class BasicCounter; hidden protected CheckValue; begin integer CheckValue; integer procedure CVal; CVal := CheckValue; CheckValue := 1; end++of++BasicCounter; BasicCounter class Counter; protected Tally; begin integer Tally; procedure Increment; Tally := Tally + 1; procedure Decrement; Tally := Tally - 1; integer procedure Total; if Total<CVal then Total := CVal else Total := Tally; end++of++Counter; Counter class DoubleCounter; begin procedure Increment; Tally := Tally + 2; procedure Decrement; Tally := Tally - 2; end++of++DoubleCounter; end--of--TallyPack; b) Program using 18.6a. begin external class TallyPack; TallyPack begin ref(Counter) Count1; ref(DoubleCounter) Count2; integer I; Count1 :- new Counter; Count2 :- new DoubleCounter; for I := 1 step 1 until 10 do begin Count1.Increment; Count2.Decrement end..of..for; OutInt(Count1.Total,8); OutInt(Count2.Total,8); OutImage end end**of**programYou will notice that it is possible to specify an attribute both hidden and protected at once. This is done by using both keywords, hidden and protected, separated by at least one space, in the specifier. They may be used in either order.
Procedures only use mode and type specifiers as appropriate. These were described in chapter 6. The mode specifiers always come before the type specifiers, when both are present.
Classes use type, protection and virtual specifiers. Type specifiers are the same as those for procedures. The others have been described in this chapter. The order, when present is type followed by protection followed by virtual specifiers.
The concept of virtual specification has been introduced, allowing procedures, switches and labels to be used at prefix levels outer to their declarations or to be redefined at levels inner to their use.
The concept of attribute protection has been dealt with. The two levels of protection specifications have been explained.
We have now covered all the features of the SIMULA language. Only certain system features remain.