📄 writing_a_be
字号:
names and AST nodes, and UTL_Stack manages scope nesting and entry and exit
from definition scopes as the parse is proceeding. UTL_Scope is defined in
include/utl_scope.hh and implemented in util/utl_scope.cc. UTL_Stack is
defined in include/utl_stack.hh and implemented in util/utl_stack.cc.
During initialization, the CFE creates an instance of UTL_Stack and saves
it. During parsing, as definition scopes are entered and exited, AST nodes
are pushed onto, or popped from, the stack represented by the saved
instances. Nodes on the stack are stored as instances of UTL_Scope. Section
THE NARROWING MECHANISM explains how to obtain the real type of a node
retrieved from the stack.
All definition scopes are linked in a tree rooted in the distinguished AST
root node. This linkage is implemented by UTL_Scope and AST_Decl. The
linkage is a permanent record of the scope nesting while the stack is a
dynamic record which at each instant represents the current state of the
parse.
The nesting information is used to do name lookup. IDL uses scoped names
which are concatenations of definition scope names ending with individual
construct names. For example, in
interface a {
struct b {
long c;
};
const long k = 23;
struct s {
long ar[k];
};
};
the name a::b::c represents the long field in the struct b inside the
interface a.
Lookup is performed by searching down the linkage chain for the first component
of the name, then, when found, recursively resolving the remaining
components in the scope defined by the first component. Lookup is relative
to the scope of use; in the above example, k could also have been referred to
as a::k within the struct s.
Nodes are stored in a definition scope as instances of AST_Decl. Thus, name
lookup returns instances of AST_Decl. The next section, THE NARROWING
MECHANISM, explains how to obtain the real type of a node retrieved from a
definition scope.
THE NARROWING MECHANISM
-----------------------
Here we give only a cursory explanation of how narrowing works. We
concentrate on defining the problem and showing how to use our narrowing
mechanism. The narrowing mechanism is defined in include/idl_narrow.hh.
As explained above, nodes are stored on the scope stack as instances of
UTL_Scope, and inside definition scopes as instances of AST_Decl. Also,
nodes are linked in a nesting tree as instances of AST_Decl. Given a node
retrieved from the stack or a definition scope, one is faced with the task
of obtaining its real class. C++ does not currently provide an implicit
mechanism for narrowing to a derived class, so the CFE defines its own
mechanism. This mechanism requires some work on your part as BE implementor
and requires some explicit code to be written when it is to be used.
The class AST_Decl defines an enum whose members encode specific AST node
classes. AST_Decl provides an accessor function, node_type(), which
retrieves a member of the enum representing the AST type of the node. Thus,
if an instance of AST_Decl really is an instance of AST_Module, the
node_type() accessor returns AST_Decl::NT_module.
The class UTL_Scope also provides an accessor function, scope_node_type(),
which returns a member of the enum encoding the actual type of the node.
Thus, given an UTL_Scope instance which is really an instance of
AST_Operation, scope_node_type() would return AST_Decl::NT_op.
Perusing the header files for classes provided by the AST, you will note
the use of some macros defined in include/idl_narrow.hh. These macros
define the explicit narrowing mechanism:
DEF_NARROW_METHODSx(<class name>,<parent_x>) for x equal to 0,1,2 or 3,
defines a narrowing method for the specified class which has 0,1,2 or 3
immediate base classes from which it inherits. For example, ast_module.hh
which defines AST_Module contains the following line:
DEF_NARROW_METHODS2(AST_Module, AST_Decl, UTL_Scope)
This is because AST_Module inherits directly from AST_Decl and UTL_Scope.
DEF_NARROW_FROM_DECL(<class name>) appears in class definitions for classes
which are derived from AST_Decl and which can be stored in a definition
scope. This macro declares a static operation narrow_from_decl(AST_Decl *)
on the class in which it appears. The operation returns the provided
instance as an instance of <class name> if it can be narrowed, or NULL.
DEF_NARROW_FROM_SCOPE(<class name>) appears in class definitions of classes
which are derived from UTL_Scope and which can be stored on the scope
stack. This macro declares a static operation narrow_from_scope(UTL_Scope *)
on the class in which it appears. The operation returns the provided
instance as an instance of <class name> if it can be narrowed, or NULL.
Now look in the files implementing these classes. You will note occurrences
of the following macros:
IMPL_NARROW_METHODSx(<class name>,<parent_x>) for x equal to 0,1,2 or 3,
implements a narrowing method for the specified class which has 0,1,2 or 3
immediate base classes from which it inherits. For example, ast_module.cc
which implements AST_Module contains the following line:
IMPL_NARROW_METHODS2(AST_Module, AST_Decl, UTL_Scope)
IMPL_NARROW_FROM_DECL(<class name>) implements a method to narrow from an
instance of AST_Decl to an instance of <class name> as defined above.
IMPL_NARROW_FROM_SCOPE(<class name>) implements a method to narrow from an
instance of UTL_Scope to an instance of <class name> as defined above.
To put it all together: In the file ast_module.hh, you will find:
// Narrowing
DEF_NARROW_METHODS2(AST_Module, AST_Decl, UTL_Scope);
DEF_NARROW_FROM_DECL(AST_Module);
DEF_NARROW_FROM_SCOPE(AST_Module);
In the file ast_module.cc, you will see:
/*
* Narrowing methods
*/
IMPL_NARROW_METHODS2(AST_Module, AST_Decl, UTL_Scope)
IMPL_NARROW_FROM_DECL(AST_Module)
IMPL_NARROW_FROM_SCOPE(AST_Module)
The CFE uses narrowing internally to obtain the correct type of nodes in
the AST. The CFE contains many code fragments such as the following:
AST_Decl *d = get_an_AST_Decl_from_somewhere();
AST_Module *m;
...
if (d->node_type() == AST_Decl::NT_module) {
m = AST_Module::narrow(d);
if (m == NULL) { // Narrow failed
...
} else { // Success, do normal processing
...
}
}
...
Similar code implements narrowing instances of UTL_Scope to their actual
types.
In your BE classes which derive from UTL_Scope you must include a line
defining how to narrow from a scope, so:
DEF_NARROW_FROM_SCOPE(<your BE class>)
and similarly for your BE classes which derive from AST_Decl.
The narrowing mechanism is defined only for narrowing from AST_Decl and
UTL_Scope. If your BE class inherits directly from one or more classes
which themselves are derived from AST_Decl and/or UTL_Scope, you must
include a line
DEF_NARROW_METHODSx(<your class name>,<parent 1>,<parent 2>)
To make this concrete, here is what you'd write in a definition of BE_union
which inherits from AST_Union:
DEF_NARROW_METHODS1(BE_Union, AST_Union);
DEF_NARROW_FROM_DECL(BE_Union);
DEF_NARROW_FROM_SCOPE(BE_Union);
and in the implementation file of BE_Union:
/*
* Narrowing methods:
*/
IMPL_NARROW_METHODS1(BE_Union, AST_Union)
IMPL_NARROW_FROM_DECL(BE_Union)
IMPL_NARROW_FROM_SCOPE(BE_Union)
Then, in BE code which expects to see an instance of your derived BE_Union
class, you will write:
AST_Decl *d = get_an_AST_Decl_from_somewhere();
BE_Union *u;
...
if (d->node_type() == AST_Decl::NT_union) {
u = BE_Union::narrow_from_decl(d);
if (u == NULL) { // Narrow failed
...
} else { // Success, do normal processing
...
}
}
...
SCOPE MANAGEMENT
----------------
Instances of classes which are derived from UTL_Scope implement definition
scopes. A definition scope can contain any kind of AST node as long as it
is derived from AST_Decl. However, specific kinds of definition scopes such
as interfaces and unions can contain only a restricted subset of all AST
node types.
UTL_Scope provides operations to add instances of each AST provided class
to a definition scope. The names of these operations are constructed by
prepending the string "add_" to the name of the IDL construct. So, to add
an interface to a definition scope, invoke the operation add_interface.
The operations are all defined virtual and are intended to be overridden in
classes derived from UTL_Scope.
If the node was successfully added to the definition scope, the node is
returned as the result. Otherwise the node is not added to the definition
scope and NULL is returned.
All add operation implementations in UTL_Scope return NULL. Thus,
only the operations which implement legal additions to a specific kind of
definition scope must be overridden in the implementation of that
definition scope. For example, in AST_Module the add_interface operation is
overridden to add the provided instance of AST_Interface to the scope and
to return the provided instance if the addition was successful. Operations
which were not overridden return NULL to indicate that the addition is
illegal in this context. For example, in AST_Operation the definition of
add_interface is not overridden since it is illegal to store an interface
inside an operation definition scope.
The add operations are invoked in the actions in the Yacc grammar. The
following fragment is a representative example of code using the add
operations:
AST_Constant *d = construct_a_new_constant();
...
if (current_scope->add_constant(d) == NULL) { // Failed
...
} else { // Succeeded
...
}
BE INTERACTION DURING THE PARSING PROCESS
-----------------------------------------
The add operations can be overridden in BE derived classes to let the BE
perform additional house-keeping work during the process of constructing
the AST. For example, a BE could keep separate lists of interfaces as they
are being added to a module.
If you override an add operation in your BE, you must invoke the overridden
operation in the superclass of your derived class to allow the CFE to
perform its own house-keeping tasks. A good rule is to invoke the operation
on the superclass before you do your own processing; then, if the
superclass operation returns NULL, this indicates that the addition failed
and your own code should immediately return NULL. An example explains this:
AST_Interface *
BE_Module::add_interface(AST_Interface *i)
{
if (AST_Module::add_interface(i) == NULL) // Failed, bail out!
return NULL;
... // Do your own work here
return i; // Return success indication
}
We strongly advise you to only define add operations that override add
operations provided by the AST classes. Add operations which
do not override equivalent operations in the AST in effect
extend the semantics of the language accepted by the compiler. For
example, the CFE does not have an add_interface operation on
AST_Operation. If you were to define one in your BE_Operation class,
the resulting compiler would allow an interface to be
stored in an operation definition scope. The current CORBA specification
does not allow this.
AST INHERITANCE SCHEME
----------------------
The AST classes all use public virtual inheritance to construct the
inheritance tree. This ensures that a class may appear several times in the
inheritance tree through different paths and the derived class's instances
will have only one copy of the inherited class's data.
The use of public virtual inheritance has several important effects on how
a BE is constructed. We explain those effects below.
First, you must define a default constructor for your BE class, since
your class may be used as a virtual base class of some other class. In this
case the compiler may want to call a default constructor for your class. It
is a good idea to have a default constructor anyway, even if you do not
plan to subclass your BE class, since for most C++ compilers this causes
the code to be smaller. Your default constructor should initialize all
constant data members. Additionally, it may initialize any non-constant
data member whose value must be set before the first time the instance is
used.
Second, the constructor of your BE derived class must explicitly call all
constructors of virtual base classes which perform useful work. For
example, if a class in the AST from which your BE class inherits has an
initializer for a data member, you must call that constructor. This rule is
discussed in detail in the C++ ARM. An example may help here.
Suppose you define a class BE_attribute which inherits from AST_Attribute.
Its constructor should be as follows:
BE_Attribute::BE_Attribute(boolean ro,
AST_Type *ft,
UTL_ScopedName *n,
UTL_StrList *p)
: AST_Attribute(ro, ft, n, p),
AST_Field(ft, n, p),
AST_Decl(AST_Decl::NT_attr, n, p)
{
}
The calls to the constructors of AST_Attribute, AST_Field and AST_Decl are
needed because these constructors do useful initializations on their
classes.
Note that there is some redundancy in the data passed to these
constructors. We chose to preserve this redundancy since it should be
possible to create BEs which subclass only some of the classes supplied by
the AST. This means that the constructors on each class provided by the AST
should take arguments which are sufficient to construct the instance if
the AST class is the most derived one.
The code supplied with this release contains a demonstration BE which
subclasses all the AST provided classes. The constructors for each class
provided by the BE are found in the file be/be_classes.cc.
INITIALIZATION
--------------
The following steps take place at initialization:
- The global data instance is created, stored in idl_global and filled with
default values (in driver/drv_init.cc).
- The command line arguments are parsed (in driver/drv_args.cc).
- For each IDL input file, a copy of the compiler process is forked (in
driver/drv_fork.cc).
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -