📄 chapter 9 designing classes.htm
字号:
<H4>9.1.4.1 Parsing Lightweight Classes</H4><!-------------------------------------------------------------------------------->This
is the rule that parses class declarations. As we can see by figure {RULE13}
that there are three parts that we need to be concerned with. The first is a
call to RuleExtends. This rule will be ignored for now. After parsing all of the
base classes, we enter a loop to process a series of variable declarations and
procedure/function declarations.
<MENU><IMG src="Chapter 9 Designing Classes.files/RULE13.gif">
<P><FONT face=arial size=-1><B>Figure {RULE13}</B> </FONT></P></MENU>We will not
cover the implementation of SAL streams. For more information on this subject,
the reader is referred to the SAL tutorial. At this stage this rule bears some
resemblance to rule RecordType.
<P><TT>RuleDeclareType()</TT> needs to be modified slightly, in order to detect
the start of a class declaration. The modifications are in grey in figure
{RULE07}.
<MENU><IMG src="Chapter 9 Designing Classes.files/RULE07.gif">
<P><FONT face=arial size=-1><B>Figure {RULE07}</B>
</FONT></P></MENU><TT>RuleMemberField()</TT> needs to be modified so that it can
parse a procedure/function call. This is shown in grey in figure {RULE20}. This
rule also covers some of the details that need to be performed for scope
resolution. This is included for grammatical clarity, but you will not need to
implement scope resolution as it is covered in one of the utility functions.
This will also be discussed more later on in this chapter.
<MENU><IMG src="Chapter 9 Designing Classes.files/RULE20.gif">
<P><FONT face=arial size=-1><B>Figure {RULE20}</B> </FONT></P></MENU>
<H4>9.1.4.2 Symbol Tables for Lightweight Classes</H4><!-------------------------------------------------------------------------------->Although
figure {RULE13S} seems simple, there is a little more involved at each step
shown.
<MENU><IMG src="Chapter 9 Designing Classes.files/RULE13S.gif">
<P><FONT face=arial size=-1><B>Figure {RULE13S}</B> </FONT></P></MENU>The first
thing we need to do is to add a new classblock to the symbol table. We do this
by calling <TT>Table::appendClassBlock()</TT>. Likewise, at the end of the rule,
there is a correspond to <TT>Table::leaveBlock()</TT>.
<P>The steps in between involve adding members to the class. The call to
<TT>RuleVarIdentList()</TT> will add one of more identifiers to the current
block (the class). This function should also return an integer count of
variables that were added for this declaration. For this declaration, <PRE> i, j, k: int;
</PRE><TT>RuleVarIdentList()</TT> should return a count of three.
<P>The next thing to do is to get the type. This might be a pre-declared type,
as in the previous tiny example, or it can be an anonymous type. Here is an
example of an anonymous type declaration: <PRE> i, j, k: record
a: int;
b: char;
end record;
</PRE>In either case, we call <TT>RuleDeclareType()</TT>. This rule takes a
reference to a dummy ident, which it uses to keep track of the type of whatever
is about to be declared. Once this rule returns, we can initialize each
identifier in the list in order to set all the types. We can do this using the
<TT>Ident::init()</TT> function.
<P>Once </TT>RuleVarIdentList()</TT> has been called, adding one or more
uninitialized variables to the current block; and <TT>RuleDeclareType()</TT> has
been called, retrieving the type of these new variables, we can proceed to set
some information for these new variables. We need the value current offset, and
the total size of all the new variables declared on this line. The current
offset is basically the total size of the class up to the new variables. This
size can be used as an offset for the position of each data member in the class
as each one is declared. The total size is found by multiplying the size of the
type returned from <TT>RuleDeclareType()</TT> by the count returned by
<TT>RuleVarIdentList()</TT>.
<P><PRE> RuleVarIdentList(follow+local, colonsy, count);
Accept(colonsy, ...);
RuleDeclareType(follow+local, semicolonsy, &info);
size:= info.getSize();
totalsize:= totalsize + count*size;
curoffs:= totalsize;
field =
table.getLast(); whilecount>0 do
dec(count);
curoffs:= curoffs
-size;
field->init(&info);field->setOffset(curoffs);
field = table.goToPrev();
loop;
</PRE>We then loop backwards through the table for each identifier that was
declared, as shown above. This will effectively initialize each identifier with
the proper offset and type information. The final thing that we do is if the
type is an anonymous array, class, or record, we need to set the parent field
for the type's block so that it references the <I>first</I> identifier on that
line. In other words, the last example above declared three instaces of an
anonymous record. The block describing that record has a parent field that
should point to the first variable, in other words, <TT>i</TT>. This step is
critical for classes, since the symbol table's search algorithm relies on this
pointer to the parent in order to backtrack through each level of scope.
<P>This code is exactly identical to a portion of the code for declaring record
fields. Again, there are many similarities between classes and records.
<P>The next rule that we have to modify requires very little work. We can see it
in figure {RULE07S}.
<MENU><IMG src="Chapter 9 Designing Classes.files/RULE07S.gif">
<P><FONT face=arial size=-1><B>Figure {RULE07S}</B> </FONT></P></MENU>This sets
one more field for the type information that is not set deeper in the parse
tree.
<P>The next thing we need to do is to fix the condition in rule MemberField when
it calls member functions so that the proper information is passed into
<TT>RuleProcFuncCall()</TT>. It needs a pointer to the identifier of the
function being called. We have already retrieved that by successfully searching
through the list of members. Finally, it needs a pointer to the type information
for <TT>self</TT>. This information should be the type information for the class
that was just searched. The code should resemble this: <PRE> func RuleMemberField ( follow: Set; last: Symbol; block: Blockvar; rType: Type, var procFuncCall: boolean ): ^Block;
var
ident: ^Ident;
nextblock : ^Block
... // Local vars
begin
//*** Find the identifier in the list of fields/members
ident:= rType->getExtra()->toBlock()->Elements.to_front();
while ident <> null and not Found do
if <I>Token.data.id = ident->name()</I> then
Found= true;
endif;
ident:=rType->getExtra()->toBlock()->Elements.next();
loop;
if Found then
rType.Init(ident); nextblock := ident->getParent();
if ident->getObj() == varobj then
if ident->getOffset() <> 0 then
// Emit Offset;
endif;
if(temp->getType()->getType()== recordtyp) then
nextblock :=temp->getExtra()->toBlock();
end if;
else if ident->getObj() == procobj or ident->getObj() == funcobj then
ProcFuncCall ( follow, last, ident, rType, rType); // the last two parameters are for type and self
procFuncCall := true; // see note about RuleDesignator below
end if;
else
SemanticErrorMsg("Identifier %s is not a member of %s.....
end if;
return nextblock;
end proc;
</PRE>One final thing that we do is to make sure that the rType is set as
the return type for <TT>RuleMemberField(). </TT> We can do this by
setting the rtype within rule ProcFuncCall (this is what is done in the code
above assumes). We do not initialize the return type using the type in
<TT>ident</TT>, since that would make it a pointer to a function. This works
fine for data members and record fields, however.
<P></P>
<P>Rule Designator will also need to be changed. Normally, RuleDesignator will
dereference the address left on the stack by RuleMemberField. A function call
will have already put the value, not an address on the stack. So rule designator
must know from RuleMemberField wether a field was accessed or a method call. We
can do this with the code below: <PRE> proc RuleDesignator(Set follow, Symbol last, Ident* ident, Type* rval, bool makerval)
var
procFuncCall: boolean;
... // other local variables
begin
procFuncCall:= false;
...
if TestToken(FirstMemberField) then
block := RuleMemberField(follow+local, badsy, block, rval,<B>procFuncCall</B>);
else
...
if makerval && !procFuncCall
then EmitLoad(_LS_,GET_TYPE_BYTE_SIZE(rval->subtype) , 0);
endif;
end proc;
</PRE><BR>Here, procFuncCall is passed by reference, and is set accordingly in
RuleMemberField.
<P>The last thing that needs to be done is a small modification to rule
ProcFuncCall(). Somehow it needs to know that it is processing a method instead
of a standard function or procedure. The difference is that with a method call,
the first parameter (the reference to <TT>self</TT>) will already be on the EES,
and will not have to be generated. If <TT>RuleProcFuncCall()</TT> is processing
a method, then it needs to skip over and not process the self parameter.
<P>
<MENU><IMG src="Chapter 9 Designing Classes.files/RULE21S.gif">
<P><FONT face=arial size=-1><B>Figure {RULE21S}</B> </FONT></P></MENU>We need to
make a similar change in <TT>RuleProcHeading()</TT> and
<TT>RuleFuncHeading()</TT>. In each of these rules, we have an implicit self
parameter when a procedure or function is a method of a class. To do this, we
pass in a pointer to a <TT>ClassBlock</TT>. If the procedure/function is not a
class method, then the pointer will be null. Otherwise, the pointer will point
to a block that describes the class of which the procedure/function is a method.
Then we just add a new parameter
<MENU><IMG src="Chapter 9 Designing Classes.files/RULE56S.gif">
<P><FONT face=arial size=-1><B>Figure {RULE56S}</B> </FONT></P></MENU>
<MENU><IMG src="Chapter 9 Designing Classes.files/RULE57S.gif">
<P><FONT face=arial size=-1><B>Figure {RULE57S}</B> </FONT></P></MENU>Another
solution would be to pass in a string containing the name of the class. If the
string were not null, we could push a <PRE> self: theClass;
</PRE>where <TT>theClass</TT> is the string. Of course, the presence of the
semicolon would depend upon the existance of additional formal parameters.
<P>
<H4>9.1.4.3 Code Generation for Lightweight Classes</H4><!-------------------------------------------------------------------------------->So
far all that we have done has been to manipulate syntax. We have done relatively
little to the language, itself. Really all that we have done is put existing
functionality to some new uses. for this reason, no extra code generation is
required.
<P><!-------------------------------------------------------------------------------->
<H2>9.2 Inheritance</H2><!-------------------------------------------------------------------------------->Inheritance
mechanisms are unique for every language. For most languages, classes and class
inheritance behaves like that of C++. For other languages like Ada or Modula 3,
inheritance can be quite different. SAL's inheritance functions like that of
C++, for the most part. In this section, we will discuss the issues that are
involved with most aspacts of class inheritance, including multiple inheritance.
<P>Let us review some terminology. We can represent the inheritance using a
graph. If class B inherits from class A, a graph representing the hierarchy
for a single instance would look like this:
<MENU><IMG src="Chapter 9 Designing Classes.files/9-2-a.gif">
<P><FONT face=arial size=-1><B>Figure {9.2a}</B> </FONT></P></MENU>Notice that
the representation is such that the base classes are on the top, and the
deriving classes are on the bottom. In this hierarchy, A is said to be the
superclass, and B is the subclass. If B inherits from A, we can say that B is
derived from A, or that B subclasses A. We can also referr to A as the topmost
class, and B is the most-derived class. This graph would be representative of
the following code:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -