**Note: Using angle brackets causes trouble in html. So I am using braces
to enclose non-terminal symbols in the discussion below.**

1. There is no relation. Data abstraction is concerned with the idea 
that clients of a class should not know about or worry about the internal
details of how objects that are instances of that class are stored.
Instead, the class should provide a suitable set of operations to manipulate
these objects and the clients should use just these operations to work
with the objects.

Abstract parse trees, on the other hand, have to do with the fact that a
lot of the information in a (concrete) parse tree can be deduced from the
BNF grammar; so there is no need to store that information in the tree 
corresponding to each legal program of the language. Leaving out all this
unnecessary info gives us an abstract parse tree.

There is no relation between data abstraction and recursive descent.
We can implement recursive descent (execution, printing, or parsing etc.)
if we have access to the details of how the (abstract) parse tree is
represented (as we have been doing until recently in our discussion). 
And we can also do the same if these details are not available but the 
ParseTree class provides a suitable set of operations (such as for 
moving from one node to another, obtaining information about the non-
terminal at the current node etc.). 

2. Yes. Example (set of unsigned numbers): First grammar:
  {no} ::= {digit} | {digit} {no}
Second grammar:
  {no} ::= {digit} | {no} {no}
In both cases:
  {digit} ::= 0 | 1 | ... | 9


3. {cond exp} ::= if {cond} then {exp} else {exp}
EvalCondExp() will be a function that returns the value of the conditional
expression. Using the concrete view of the parse tree (i.e., directly in
terms of the array elements):

  int EvalCondExp(int n) {
    // check if PT[n,1] has the value corresponding to {cond exp}
    bool b = EvalCond(PT[n,3]); // gets the value of the {cond}
    if (b) { return EvalExp(PT[n,4]); } 
    else  { return EvalExp(PT[n,5]); } // return value of appropriate {exp}
  }


4. This is a run-time condition because one or more of these changes (i.e.,
assignments or input statements) may be inside conditionals and we cannot
know, until the program is executed, whether they are actually executed.


5. It means there is at least one string that is legal in the language for 
which the grammar allows us to generate two or more distinct parse trees.
It doesn't cause any problems for printing because, given *any* of the 
parse trees, the print procedures will print the original program 
correctly. It can cause problems for execution - if the "wrong" tree (from
the semantic point of view) is given. This was a possibility in the 
{exp} example we saw in our discussion. There, given an expression like
X + Y * Z, with the first grammar we saw, we could get two different trees.
In the first one, this expression was treated as the sum of X and Y * Z;
that gives the right semantics. In the second, the expression was treated
as the product of X + Y and Z; that gives the wrong semantics.

But that doesn't mean that every ambiguous grammar will lead to semantic
problems. For example, we may have an ambiguous grammar that treats 
X + Y + Z as either the sum of X and Y + Z or as the sum of X + Y and Z.
If this is the only type of ambiguity in the grammar, there is no semantic
problem (because addition is associative). 

If the ambiguity *does* cause semantic problems, we can try to write the 
parse in such a way that it produces the correct tree each time. That will
make the parser more difficult to write. A better approach would be to 
rewrite the grammar to eliminate such ambiguities.


6. We haven't gotten to this item in our class discussion but you should
be able to understand the following. (It is the notes I distributed
in class on 10/24. We will talk about it next week.)
Basically the idea here is that rather than having a single monolithic object, pt, corresponding to the entire parse tree, we could have as many objects as there are nodes in the pt. Thus the object corresponding to a {stmt seq} node will be an instance of the StmtSeq class; that corresponding to an {if} node will be an instance of the If class. The *type safety* with this approach corresponds to the fact that, in the definition of, say, the If class, the children of an object of this type will be specified to be of type Cond, StmtSeq, and StmtSeq, respectively. This will ensure that it would be impossible to construct an If object whose first child is, for example, a DeclSeq object or some such silly thing. In the approach where we use a single pt object of type ParseTree, there is no such guarantee. That is why we are forced to check, at the start of each procedure (such as ExecIf, PrintIf etc.), that the node we are currently working on is the right kind -- by checking the value of PT[n,1] or by calling currNTNo{}. Note that this problem arises whether we take a concrete view of the monolithic parse tree object (where we directly check PT[n,1]) or follow the principles of data abstraction (where we use currNTNo{} to check).