27) Semantics of Programming Languages ================================================================================ 0) What does the program below do? public class T { public static void main(String args[]) { int x = 1; x += (x = 2); System.out.println("x = " + x); } } 0.1) What about this program? #include int main() { int x = 1; x += (x = 2); printf("x = %d\n", x); } 1) How to describe a programming language? 2) Does anybody remember the grammar for simple integer expressions? ::= + | ::= * | ::= ( ) | 3) What is the associativity/precendence of the operators? 4) What is the parse tree for 1 + 2 * 3? What about AST? 5) How to define the meaning of this language? * The interpreter for the AST: ::= plus(, ) | times(, ) | const () val1(plus(X,Y),Value) :- val1(X,XValue), val1(Y,YValue), Value is XValue + YValue. val1(times(X,Y),Value) :- val1(X,XValue), val1(Y,YValue), Value is XValue * YValue. val1(const(X),X). 6) What is val1(const(1000000000), X)? 7) What is the problem of defining a language's semantics by an interpreter? * Defining the semantics of this language means defining a map from AST's to values, e.g: times(const(2), const(3)) -> 6 * Natural semantics will define the relation of AST's to values. E.g: E1 -> v1 E2 -> v2 E1 -> v1 E2 -> v2 ------------------------ -------------------------- plus(E1, E2) -> v1 + v2 times(E1, E2) -> v1 * v2 const(n) -> eval(n) 7.1) What is the meaning of this notation? The arrow and the fraction line? * Natural semantics is also called big-step semantics. There is also the small-step semantics: E1 -> E1' E1 -> E1' ----------------------------- --------------------------------- plus(E1, E2) -> plus(E1', E2) times(E1, E2) -> times(E1', E2) E2 -> E2' E2 -> E2' ----------------------------- --------------------------------- plus(E1, E2) -> plus(E1, E2') times(E1, E2) -> times(E1, E2') plus(v1, v2) -> v1 + v2 times(v1, v2) -> v1 * v2 const(v) -> eval(v) 8) Would it be possible to change these rules so that addition would be left associative? E1 -> E1' E2 -> E2' ----------------------------- ----------------------------- plus(E1, E2) -> plus(E1', E2) plus(v, E2) -> plus(v, E2') 9) How to define the semantics of if/then/else in SML? - There may be more than one evaluation for a rule: E1 -> true E2 -> v2 E1 -> false E3 -> v3 ------------------------- or ------------------------- if(E1, E2, E3) -> v2 if(E1, E2, E3) -> v3 * Let's add variables to our toy language: ::= + | ::= * | ::= let val = in end | ( ) | | 10) How does the new constructions change the grammar for AST's? ::= plus (, ) | times (, ) | const () | let (, , ) | var () 11) How to augment the interpreter so that it handles variables? val2(plus(X, Y), Context, Value) :- val2(X, Context, XValue), val2(Y, Context, YValue), Value is XValue + YValue. val2(times(X, Y), Context, Value) :- val2(X, Context, XValue), val2(Y, Context, YValue), Value is XValue * YValue. val2(const(X), _, X). val2(var(X), Context, Value) :- lookup(X, Context, Value). val2(let(X, Exp1, Exp2), Context, Value2) :- val2(Exp1, Context, Value1), val2(Exp2, [bind(X, Value1) | Context], Value2). 11.1) How to give the PL the block semantics? lookup(Variable, [bind(Variable, Value)|_], Value). lookup(VarX, [bind(VarY, _)|Rest], Value) :- VarX \= VarY, lookup(VarX, Rest, Value). * Example 1: val2(let(y, const(3), times(var(y), var(y))), nil, X). * Example 2: let val y = 3 in val2(let(y,const(3), let val x = y * y in let(x, times(var(y), var(y)), x * x times(var(x), var(x)))), end nil, X). end 12) Can you give me some other programs? 12.1) What is the meaning of let val y = 1 in let val y = 2 in y end end? E.g.: val2(let(y, const(1), let(y, const(2), var(y))), nil, X). 12.2) And what is the meaning of? let val x = 1 in let val x = 2 in x end + x end E.g.: val2(let(x, const(1), plus(let(x, const(2), var(x)), var(x))), nil, V). 13) How to rewrite this new interpreter using a natural semantics notation? (E1, C) -> v1 (E2, C) -> v2 (E1, C) -> v1 (E2, C) -> v2 ------------------------------ ------------------------------- (plus(E1, E2), C) -> v1 + v2 (times(E1, E2), C) -> v1 * v2 (var(v), C) -> lookup(C, v) (const(n), C) -> eval(n) (E1, C) -> v1 (E2, [bind(x, v1)|C]) -> v2 -------------------------------------------- (let(x, E1, E2), C) -> v2 14) What is the meaning of: "let val a = 1 in b end" ? E.g: val2(let(a,const(1),var(b)),nil,X). 14.1) What is wrong with the program above? 14.2) How a compiler would handle such codes? * Static semantics: program meaning known at compilation time. * Dynamic semantics: program meaning known at run time. 15) Which examples of errors of static semantics can you give me? - references must be in the scope of definitions. - variables must be used according to their declared type. 16) Which examples of errors of dynamic semantics can you give me? * Semantic equivalence 17) What does it mean to say that two programs P1 and P2 are equivalent? -> iff -> - or - -> stuck iff -> stuck 18) Prove that if be then c1 else c2 == if not(be) then c2 else c1 The proof is by induction on the rules used to evaluate the expression. 18.1) What are these rules? be -> true c1 -> v be -> true ---------------------- IfTrue ----------------- NotTrue if(be, c1, c2) -> v not(be) -> false be -> false c2 -> v be -> false ---------------------- IfFalse ----------------- NotFalse if(be, c1, c2) -> v not(be) -> true Case 1) be -> true c1 -> v ------------------------ if(be, c1, c2) -> v What do we know? We know that (i) "be -> true" and (ii) "c1 -> v" From (i) and rule NotTrue we know that "not(be) -> false", thus: (i) be -> true ---------------- not(be) -> false (ii) c1 -> v ----------------------------------- if(not(be), c2, c1) -> v Case 2) be -> false c2 -> v ------------------------ if(be, c1, c2) -> v What do we know? We know that (i) "be -> false" and (ii) "c2 -> v" From (i) plus NotFalse we know that "not(be) -> true", thus: (i) be -> false --------------- not(be) -> true (ii) c2 -> v -------------------------------- if(not(be), c2, c1) -> v 19) How to add functions to our grammar? ::= fn => | ::= + | ::= * | ::= | ::= let val = in end | ( ) | | 20) Can anyone give me examples of programs in this language? (fn x => x * x) 3 20.1) What is the parse tree of this program above? 21) How many parameters can a function have? 22) How to model this new AST? - apply(Function, Actual) - fn(Formal, Body) val3(plus(X, Y), Context, Value) :- val3(X, Context, XValue), val3(Y, Context, YValue), Value is XValue + YValue. val3(times(X, Y), Context, Value) :- val3(X, Context, XValue), val3(Y, Context, YValue), Value is XValue * YValue. val3(const(X), _, X). val3(var(X), Context, Value) :- lookup(X, Context, Value). val3(let(X, Exp1, Exp2), Context, Value2) :- val3(Exp1, Context, Value1), val3(Exp2, [bind(X, Value1) | Context], Value2). val3(fn(Formal, Body), _, fval(Formal, Body)). val3(apply(Function, Actual), Context, Value) :- val3(Function, Context, fval(Formal, Body)), val3(Actual, Context, ParamValue), val3(Body, [bind(Formal, ParamValue)|Context], Value). lookup(Variable, [bind(Variable, Value)|_], Value). lookup(VarX, [bind(VarY, _)|Rest], Value) :- VarX \= VarY, lookup(VarX, Rest, Value). 22.1) Can you give me examples of programs in this language? E.g.: val3(apply(fn(x,times(var(x), var(x))), const(3)), nil, X). 23) What is the value of: let val x = 1 in let val f = fn n => n + x in let val x = 2 in f 0 end end end E.g.: val3(let(x, const(1), let(f, fn(n, plus(var(n), var(x))), let(x, const(2), apply(var(f), const(0))))), nil, X). 23.1) Represent the context graphically. 23.2) Is this static or dynamic scoping? 24) How to add static scoping? - Add to the description of functions a copy of the context where that function is defined. val4(plus(X, Y), Context, Value) :- val4(X, Context, XValue), val4(Y, Context, YValue), Value is XValue + YValue. val4(times(X, Y), Context, Value) :- val4(X, Context, XValue), val4(Y, Context, YValue), Value is XValue * YValue. val4(const(X), _, X). val4(var(X), Context, Value) :- lookup(X, Context, Value). val4(let(X, Exp1, Exp2), Context, Value2) :- val4(Exp1, Context, Value1), val4(Exp2, [bind(X, Value1) | Context], Value2). val4(fn(Formal, Body), Context, fval(Formal, Body, Context)). val4(apply(Function, Actual), Context, Value) :- val4(Function, Context, fval(Formal, Body, Nesting)), val4(Actual, Context, ParamValue), val4(Body, [bind(Formal, ParamValue)|Nesting], Value). lookup(Variable, [bind(Variable, Value)|_], Value). lookup(VarX, [bind(VarY, _)|Rest], Value) :- VarX \= VarY, lookup(VarX, Rest, Value). 24.1) What is a closure in this interpreter? * Example: let val f = fn x => let val g = fn y => y+x in g end in f 1 2 end E.g.: val4(let(f,fn(x,let(g,fn(y,plus(var(y),var(x))), var(g))), apply(apply(var(f),const(1)),const(2))),nil, X). 24.2) How do we simulate the fact that the activation record of this function cannot be deallocated after the function returns? 25) How to write the semantic rules using the natural notation? Use dynamic scoping: (E1, C) -> v1 (E2, C) -> v2 (E1, C) -> v1 (E2, C) -> v2 ------------------------------ ------------------------------- (plus(E1, E2), C) -> v1 + v2 (times(E1, E2), C) -> v1 * v2 (var(v), C) -> lookup(C, v) (const(n), C) -> eval(n) (fn(x, E), C) -> (x, E) (E1, C) -> v1 (E2, [bind(x, v1)|C]) -> v2 -------------------------------------------- (let(x, E1, E2), C) -> v2 (E1, C) -> (x, E3) (E2, C) -> v1 (E3, [bind(x,v1)|C]) -> v2 ------------------------------------------------------------------- (apply(E1, E2), C) -> v2 26) How is recursion implemented, e.g: let val f = fn x => f x in f 1 end ? 26.1) What is the meaning of the recursive program below? val4(let( f, fn(x, apply(var(f), var(x))), apply(var(f), const(1))), nil, X). 26.1) Why is it tricky to have recursion under static scoping? 27) What is the role of the evaluation order in this interpreter? 27.1) What is the Java semantics of: x = 1; x += (x = 2)? x = 2; oldx = x; oldx = x; oldx = x; x = 2; x = oldx + 2; x = oldx + 2 x = oldx + 2; x = 2;