980 likes | 1.18k Views
Chapter 5. Names, Bindings, Type Checking, and Scopes. Type Equivalence. Why Type Equivalence Is Needed?. The type compatibility rules are simple and rigid for the predefined scalar types . However, in the cases of structured types such as arrays and records and
E N D
Chapter 5 Names, Bindings, Type Checking, and Scopes
Why Type Equivalence Is Needed? • The type compatibility rules are simple and rigid for the predefined scalar types. • However, in the cases of • structured types • such as arrays and records and • user-defined types, the rules are more complex. • Coercion of these types is rare, so the issue is not type compatibility, but type equivalence.
Categories of Type Equivalence • There are two approaches to define type equivalence: • Name type equivalence • Structure type equivalence • There are some variations of these two approaches, and many languages use combinations of them.
Name Type Equivalence • Name type equivalence means that two variables have equivalent types if they are defined • in the same declaration or • in declarations that use the same type name.
Structure Type Equivalence • Structure type equivalence means that two variables have equivalent types if their types have identical structures.
Name Type Equivalence Is Highly Restrictive • Name type equivalence is easy to implement but is more restrictive. • e.g. Under a strict interpretation, a variable whose type is a subrange of the integers would not be equivalent to an integer type variable.
Example • Supposing Ada used strict name type equivalence, consider the following Ada code: type Indextype is 1..100; count : Ineger; index : Indextype; • The types of the variables count and index would not be equivalent. • count could not be assigned to index or vice versa.
A Problem of Name Type Equivalence • When a structured type is passed among subprograms through parameters, such a type must be defined only once, globally. • A subprogram cannot state the type of such formal parameters in local terms. • This is the case with the original version of Pascal.
Name Type Equivalence and Type Name • To use name type equivalence, all types must have names. • Most languages allow users to define types that are anonymous – they do not have names. • For a language to use name type equivalence, such types must implicitly be given internal names by the compiler.
Implementation Difficulty of Structure Type Equivalence • Structure type equivalence is more flexible than name type equivalence, but it is more difficult to implement. • Under name type equivalence, only the two type names must be compared to determine equivalence. • Under structure type equivalence, the entire structures of the two types must be compared.
A Weakness of Structure Type Equivalence • Structure type equivalence disallows differentiating between types with the same structure. • For example, consider the following declarations: type celsius = Float; fahrenheit = Float; • Variables of these two types are considered equivalent under structure type equivalence, allowing them to be mixed in expressions. • The above equivalence is undesirable, because in general, types with different names are likely to be abstractions of different categories of problem values and should not be considered equivalent.
Type Equivalence of Ada • Ada uses name type equivalence for most types, but provides two type constructs, subtypes and derived types, to create types from existing types.
Derived Types • An Adaderived type is a NEW type that is based on some previously defined type with which it is NOT equivalent, although it may have identical structure. • Derived types inherit all the properties of their parent types. • Derived types can also include range constraints on the parent type, while still inheriting all of the parent's operations.
Example of Derived Types type celsius is new FLOAT; type fahrenheit is new FLOAT; • Variables of these two derived types are not equivalent, although their structures are identical. • Furthermore, variables of both type are not type equivalent with any other floating-point type.
The Type of Literals in Ada • Literals are exempt from the rule imposed upon derived types. • A literal such as 3.0 has the type universal real and is type equivalent to any floating-point type.
Ada Subtypes • An Adasubtype is a possibly range-constrained version of an existing type. • A subtype IS type equivalent with its parent type.
Ada Subtype Example • For example, consider the following declaration: subtype Small_type is Integer range 0..99; The type Small_type is equivalent to the type Integer.
Array Types in Ada • Array types may be • constrained types or • unconstrained types.
Constrained Array Types in Ada • Constrained array types have a predefined size. • E.g. type Int_Buffer is array (1..10) of Integer;
Unconstrained Array Types in Ada • In Ada we can also declare unconstrained array types. • That means, when the array is declared, the index type is specified but there is no need to state limits for the index, instead the symbol <>is used. • When an object of an unconstrained array type is declared, the index constraints must be stated. type Vector is array (Integer range <>) of Character; Vector_1 : Vector (1 .. 10);
Example • For example, consider the following type declaration and two object declarations type Vector is array (Integer range <>) of Integer; Vector 1: Vector (1..10); Vector 2: Vector (11..20); • The types of these two objects are equivalent, even though they have different names and different subscript ranges, because for objects of unconstrained array types, structure type equivalence rather than name type equivalence is used. • P.S.: Both types have ten elements and the elements of both are of type Integer.
The Scope of a Program Variable • The scope of a program variable is the range of statements in which the variable is visible. • A variable is visible in a statement if it can be referenced in that statement.
The Scope Rules of a Language • determine how a particular occurrence of a name is associated with a variable. • determine how • references to variables declared outside the currently executing subprogram or block are associated with • their declarations and thus their attributes.
Local Variables vs. Non-local Variables • A variable is local in a program unit or block if it is declared there. • The nonlocal variables of a program unit or block are those that are visible within the program unit or block but are not declared there.
Static Scoping • Static scoping is thus named because the scope of a variable can be statically determined, that is, prior to execution.
Categories of Static-scoped Languages • Those in which subprograms cannot be nested. • Those in which subprograms can be nested, which creates nested static scopes. • Ada, JavaScript, and PHP allow nested subprograms, which can create a hierarchy of scopes in a program. • but the C-based languages do not.
Assumption • The discussion of static scoping in this chapter focuses on those languages that allow nested subprograms. • So, for now we assume: • that all scopes are associated with program units.
Determine the Attributes of a Referred Variable • In a static-scoped language, for a reference to a variable, the attributes of the variable can be determined by finding the statement in which it is declared.
Find the Declaration Statement of a Variable in Static Scoped Languages with Nested Subprograms • Suppose a reference is made to a variable x in subprogram Sub1. • The correct declaration is found by first searching the declarations of subprogram Sub1. • If no declaration is found for the variable there, the search continues in the declarations of the subprogram that declared subprogram Sub1, which is called its static parent. • If a declaration of x is not found there, the search continues to the next larger enclosing unit (the unit that declared Sub1's parent), and so forth, until • a declaration for x is found or • the largest unit's declarations have been searched without success. In that case, an undeclared variable error has been detected.
Static Ancestors of a Subprogram • The static parent of subprogram Sub1, and its static parent, and so forth up to and including the largest enclosing subprogram, are called the static ancestors of Sub1.
A Static Scoping Example procedure Big is X : Integer; procedure Sub1 is X : Integer; begin -- of Sub1 ... end; -- of Sub1 procedure Sub2 is begin -- of Sub2 ...X... end; -- of Sub2 begin -- of Big ... end; -- of Big • Under static scoping, the reference to the variableXinSub2is to theXdeclared in the procedureBig. • This is true because the search forXbegins in the procedure in which the reference occurs,Sub2, but no declaration forXis found there.The search thus continues in the static parent ofSub2,Big, where the declaration ofXis found.
Hide Variable Declarations • In some languages that use static scoping, regardless of whether nested subprograms are allowed, some variable declarations can be hidden from some other code segments.
Example • The reference to count in the while loop is to that loop’s local count. • In this case, thecountofsubis hidden from the code inside the while loop. • In general, a declaration for a variable effectively hides any declaration of a variable with the same name in a larger enclosing scope. void sub() { intcount; … while (…) { intcount; count ++; … } … }
Selective References • In Ada, hidden variables from ancestor scopes can be accessed with selective references, which include the ancestor scope's name. • For example, in the preceding procedure, Big, the X declared in Big can be accessed in Sub1 by the reference Big.X. P.S.: The textbook is wrong.
Global Variables • Although C and C++ do not allow subprograms to be nested inside other subprogram definitions, they do have global variables. • These variables are declared outside any subprogram definition. • Local variables can hide these globals, as in Ada. • In C++, such hidden globals can be accessed using the scope operator (::). • For example, if x is a global that is hidden in a subprogram by a local named x, the global could be referenced as ::x.
Blocks • Many languages allow NEW static scopes to be defined in the midst of executable code. • This powerful concept, introduced in ALGOL 60, allows a section of code to have its own local variables whose scope is minimized. • Such variables are typically stack dynamic, so they have their storage allocated when the section is entered and deallocated when the section is exited. • Such a section of code is called a block.
Specify Blocks in Ada In Ada, blocks are specified with declare clauses, as in ... declare TEMP : Integer; begin Temp := First; First := Second; Second := Temp; end; ...
In C-based Languages Blocks Could Be Created by Compound Statements • The C-based languages (such as C, C++, and Java) • allow any compound statement to have declarations and • thus define a new scope. • A compound statement is a statement sequence surrounded by matched braces. • Such compound statements are blocks. • For example, if list were an integer array, one could write if (list[i] < listf[j]) { int temp; temp = list[i]; list[i] = listf[j]; llst[j] = temp; }
Scopes Created by Blocks • The scopes created by blocks are treated exactly like those created by subprograms. • References to variables in a block that are not declared there are connected to declarations by searching enclosing scopes in order of increasing size.
Scope Rules for C++ and C • C++ allows variable definitions to appear anywhere in functions. • When a definition appears at a position other than at the beginning of a function, but not within a block, that variable's scope is from its definition statement to the end of the function. • Note that in C, all data declarations in a function but not in blocks within the function MUST appear at the beginning of the function.
Scope Rules for Variables Defined in for Statements in C++, Java, and C# • The for statements of C++, Java, and C# allow variable definitions in their initialization expressions. • In early versions of C++, the scope of such a variable was from its definition to the end of the smallest enclosing block. • P.S.: ( and ) do not create a block. • In the standard version, however, the scope is restricted to the forconstruct, as is the case with Java.
Example void fun() { ... for (int count = 0; count < 10; count++) { ... } ... } • In early version of C++, the scope of count would be from the for statement to the end of the method. • In later versions, as well as in Java and C#, the scope of count would be from the for statement to the end of its body.
Assumptions for the Analysis of the Drawbacks of Static Scoping • Static scoping provides a method of nonlocal access that works well in many situations. However, it is not without its problems. • Based on the program whose skeletal structure is shown in Figure 5.1, in the following slides, we will discuss its weakness. • For the program in Figure 5.1, assume that all scopes are created by the definitions of the main subprogram and the procedures.
The Structure of an Example Program • This program contains an overall scope formain, with two procedures that define scopes insidemain, Aand B. • Inside Aare scopes for the proceduresCand D. • Inside Bis the scope of procedureE. • We assume that the necessarydata and procedureaccessdetermined the structure of this program. • The required procedure access is as follows:maincan callA and B, Acan callCand D, andBcan callA and E.
The Tree Representation of the Scopes of the Previous Example Program • View the structure of the program as a tree in which each node represents a procedure and thus a scope. • A tree representation of the program of Figure 5.1 is shown in Figure 5.2.
The Potential Call Graph of the Previous Example Program • However, a graph of thepotential procedure callsof this system, shown in Figure 5.3, shows that a great deal of calling opportunity beyond that required is possible. • Backward references are not allowed. • This call graph has nothing to do with the nested scoping.