Type ::= int | bool | ^Type | Type * Type | (Type)where "int", "bool", "^", "*", "(" and ")" are terminal symbols.
Exp ::= Num | Ide | Exp Op Exp | let Ide = Num in Exp end | (Exp) Op ::= + | * | - | / Num ::= ... // sequence of digits representing a natural number Ide ::= ... // sequence of lettersand the interpreter eval for such language
int eval(tree* t, environment* r){
node* n = t->get_root();
string ty = n->get_type();
switch (ty) {
case "num":
return n->get_value();
case "ide":
return r->lookup(n->get_ide());
case "op" : {
int k1 = eval(t->get_left(), r);
int k2 = eval(t->get_right(), r);
switch (n->get_op()) {
case "+":
return k1 + k2;
case "*":
return k1 * k2;
case "-":
return k1 - k2;
case "/":
return k1 / k2;
}
}
case "dec": {
string x = n->get_ide();
int k = t->get_left()->get_root()->get_value();
environment* r1 = r->add(x,k);
return eval(t->get_right(), r1);
}
}
}
Assume that we want to modify the language by adding negative expressions.
More precisely, we want to add a unary operator "~", whose meaning is
to change the sign of the value of the argument. For instance, if the
value of x is 5, then the value of ~x should be -5. If the value of y is 2 and the
value of z is 7, then the value of 5 * (~(~y + z)) should be -25.
Show how the grammar and the eval function should be modified so to cope with
such an extension.
Note: Such extension is in a sense redundant, because the value of ~e can be expressed already by writing 0 - e. But anyway, the exercise is realistic because most programming languages allow the unary minus operator.
int f(int <parameter passing-method> x){
x = x+1;
y = y+2;
return x + y;
}
where y is a global variable of type integer.
Say what is the final value of y after the following fragment of code:
y = 0;
y = f(y);
under each of the following assumptions:
Exp ::= Exp implies Expand with a non-strict semantics of the following form:
the result of e1 implies e2 is - true if e1 is false - true if e1 is true and e2 is true - false if e1 is true and e2 is false
x > 0 implies (x = 1 or x > 1 or 1/x > 1)
procedure p(x:integer);
var y:integer; /* declaration of a variable y local to p */
procedure q; /* declaration of a procedure q local to p */
procedure r; /* declaration of a procedure r local to q */
var x:integer; /* declaration of a variable x local to r */
begin /* begin body of r */
x := 0;
q
end; /* end body of r */
begin /* begin body of q */
if x = y
then begin y := y - 1; r end
else write(y)
end; /* end body of q */
begin /* begin body of p */
y := x;
q
end; /* end body of p */
Assume that at a certain point, the main program
contains a call of the form
p(1);
class list{
int info;
list* next;
public:
list(){ info = 0; next = NULL; }
list(int x, list* l){ info = x; next = l; }
~list(){ delete next; }
int length(){ //returns the length of the list
int n = 0;
list* l = this;
while(l->next!=NULL){ l = l->next; n++; }
return n;
}
};
list* makelist(){ //reads a sequence of integers and returns them in a list
int x;
list* l = new list();
cin >> x;
while(x!=0){ l = new list(x,l); cin >> x; }
return l;
}
For each of the following declarations of p,
say whether a call to p leaves memory leaks
and/or dangling references.
(Note: "a call to p leaves ml/dr" means "after a call to p returns,
we have ml/dr".)
void p(){
cout << makelist()->length();
}
void p(){
list* l = makelist();
cout << l->length();
l = NULL;
}
void p(){
list* l = makelist();
cout << l->length();
delete(l);
}
void p(){
list l = list(42,makelist());
cout << l.length();
}
void p(){
list l = list(42,makelist());
m = new list(53, &l); // m is a global variable of type list*
cout << m->length();
}