Expression evaluation Order of evaluation For the abstract syntax tree + / \ / \ + 5 / \ / \ + + / \ / \ x 3 2 4 the equivalent expression is (x + 3) + (2 + 4) + 5 Correct way to evaluate this expression from the abstract syntax tree is to evaluate the tree bottom-up, left-to-right. But this constrains optimization that uses mathematical properties of operators such as commutativity and associativity. e.g., it may be preferable to evaluate of e1 + (e2 + e3) instead of (e1 + e2) + e3 (x+0) + (y + 3) + (z + 4) => x + y + z + 0 + 3 + 4 => x + y + z + 7 The compiler can evaluate 0+3+4 at compile time, so that at runtime, we have two fewer addition operations. Many languages leave order of evaluation unspecified. - even the order of evaluation of procedure parameters are not specified. It is a bad programming practice to use expressions where different orders of evaluation can lead to different results: e.g., /* Assume x = 5 */ (x++) + x gives value 11 when left-to-right evaluation is used, but gives value 10 when right-to-left evaluation is used. Note that it takes some amount of effort to understand that the value could be different, when different orders of evaluation are used. Order of evaluation matters when the evaluation of subexpressions has the side-effect of changing the values of certain variables (eg evaluation of x++ changes value of x). Using expressions with such side-effects is thus a bad idea in programming. Left-to-right evaluation with short-circuit semantics is appropriate for boolean expressions. e1 && e2 => e2 is evaluated only if e1 evaluates to true e1 || e2 => e2 is evaluated only if e1 evaluates to false This is useful in expressions like if ((i < n) && a[i] != 0) With short-circuit code a[i] is never accessed if i >= n Another example: if ((p != NULL) && p->value > 0) But the disadvantage is that, in an expression like if ((a == b) || (c = d)) where the second expression has a statement (assignment), the value of c may or may not be the value of d depending on if a == b is true or not. Bottom-up No order specified among unrelated subexpressions. Short-circuit evaluation of boolean expressions. Delayed evaluation Delay evaluation of an expressions until its value is absolutely needed. This is generalization of short-circuit evaluation Control Structures If-then-else. It is in two forms: if cond then s1 else s2 if cond then s1 evaluate condition. If and only if evaluates to true, then evaluate s1 otherwise evaluate s2 Dangling else problem if c1 then if c2 then s1 else s2 may be intrepreted as if c1 then if c2 then s1 else s2 or if c1 then if c2 then s1 else s2 where the indentation represents the intended interpretation. This ambiguity can be avoided by bracketing syntax: if cond then s1 fi if cond then s1 else s2 fi Using this syntax, the above intended statements can be written as if c1 then if c2 then s1 else s2 fi fi or if c1 then if c2 then s1 fi else s2 fi Another way to avoid ambiguity is to use disambiguating rule: associate else with closest "if" that doesn't have "else". This is used in most programming languages (C, C++ etc) Case Statement switch () { case : case : ... default : } Evaluate to get value v. Evaluate the case that corresponds to v. Restriction: has to be value (and not an expression) of an orinal type e.g., int, enum Implementation of case statement Naive algorithm: Sequential comparison of value v with case labels. This is simple, but inefficient. It involves O(N) comparisons. switch (e) { case 0 : s0 ; case 1 : s1 ; case 2 : s2 ; case 3 : s3 ; } can be translated as v = e ; if (v == 0) s0 ; else if (v == 1) s1 ; else if (v == 2) s2 ; else if (v == 3) s3 ; Binary search: O(log N) comparisons, hence a drastic improvement over sequential search for large N. Using this, the above case statement can be translated as v = e ; if (v <= 1) if (v == 0) s0 ; else if (v == 1) s1 ; else (if v >= 2) if (v == 2) s2 ; else if (v == 3) s3 ; Another technique is to use hash tables. This maps the value v to the case label that corresponds to the value v. This takes constant O(1) time.