 Welcome to the lecture on semantic analysis. We will be studying many topics such as attribute grammars, attributed translation grammars and how exactly semantic analysis is done with such attributed translation grammars. These are the main topics and as we go along we will see the subtopics as well. To put the semantic analysis module in the correct perspective, let us look at the overview diagram once again. So, we have completed lexical analyzer, syntax analyzer and studied their properties, different types of such analyzers and so on. So, one from the syntax analyzer, the syntax tree is input into the semantic analyzer module which outputs these annotated syntax tree after validating the semantics of the program. So, let us understand what exactly semantics of a program mean, how they are checked etcetera etcetera. So, basically semantic consistency that cannot be handled at the parsing stage is actually the one that is handled here. For example, parsers cannot handle context sensitive features of programming languages. So, there are two types of semantics that programming languages have. One is known as the static semantics and the other is known as the dynamic semantics. Static semantics as the name indicates you know they do not depend on the run time system and the execution of the program, but they are dependent only on the programming language definition whereas, the dynamic semantics again as the name indicates they are the properties of the programming language systems that occur at run time and we need to check such properties only during execution time of the program. For example, the static semantics can be checked by that can be checked by a semantic analyzer or you know there are many examples here. So, our variables declared before use if so everything is ok otherwise an error message has to be provided. Then do the types of the expression and the variable to which it is assigned match on the two sides of an assignment statement and do the parameter types and number of parameters match in both the declaration and use. So, these are few examples of static semantics of programming languages whereas, you know for dynamic semantics compilers can only generate code to check such a you know meaning of the program. For example, whether an overflow will occur during an arithmetic operation. So, whether array limits will be crossed during execution whether recursion will cross stack limits whether heap memory will be sufficient. So, really speaking we should say you know the properties are these and the checks will be done by the semantic analyzer and similarly you know there are properties of the numbers and if the variables you know if the number exceeds a particular range then an overflow is set to occur. Similarly, the array will be defined with say 0 to 10 or 0 to 20 and if the program really crosses these limits then the array limit violation is set to occur. So, that the violation should not happen is the semantics of the program whereas, the check will be performed only at the runtime. So, let us take some examples of the semantic checks static semantic checks that can be done by a compiler. So, consider this you know simple function and main program which calls it. So, this is a function to calculate the dot product of two arrays. So, int x and int y these are the two arrays of integers. So, the program is very simple you know it has a loop i equal to 0 i less than 10 i plus plus and then d accumulates the dot product d plus d equal to d plus x i star y i sorry turn d. So, this is the simple you know dot product function and in the main program we have two arrays a and b of size 10 each and we call dot product function with these two arrays as parameters the sum is actually taken in p. So, what are the possible static semantic checks that can be performed on this program? For the main program here is a simple you know list. So, types of p and return types of the dot product function they should match. So, for example, p is being assigned the value of value return by dot product and p's value is integer. So, the value of the return value of dot product is also of integer type. So, these two match and there is no question of error here. Do that number and type of the parameters of dot product the same in both its declaration and use yes in this case. So, here is the usage in the usage we have two parameters and in the declaration of dot product also we have two parameters. Secondly, the two parameters are arrays in the declaration and they are arrays in the usage as well. So, this particular you know this thing property is also satisfied then is p declared before use similarly is a declared before use is b declared before use etcetera etcetera. So, yes definitely p is declared and then used a and b are declared and then used. So, all these properties are satisfied for the main program. So, let us see how it is for the erroneous program. So, the same dot product let us not worry about the dot product function. So, I have just you know given you only the declaration of the header in the main program there are many errors here and the error messages given out by the GCC compiler are here. So, for example, we have a and b as before and then we are trying to call dot product with a single parameter. Here we are trying to call it with three parameters here we are calling it you know with two parameters properly, but unfortunately the p into which we are trying to accumulate the dot product is not a single integer, but is an array type. So, let us see and of course, print f says print an integer and p an array is provided as a parameter for the print f statement. Let us look at the errors which are given out. So, statement three two few arguments to function dot product. So, it requires two, but we have given only one. So, the semantic analyzer in the compiler has got the error and then statement four it says too many arguments to function dot product it requires two, but we have provided three again an error and statement five incompatible types in assignment. So, p equal to dot product p is an array whereas dot product returns an integer. So, the types do not match on both sides. So, that is the error which is being printed out here and this is a warning really for the fifth one for the print f statement. So, format percent d expects type int, but argument two has type int star. So, p is an array. So, it is considered as type int star and that is the reason why it says has type int star. So, these are the examples of errors in the main program. Continuing the same program in the function dot product again there are many semantic checks possible even though it is a repetition let us just go through it to drive home the point. So, r d and i declared before use. So, d is being initialized here sorry d is being declared here and assigned here. So, the declaration happens before usage i is declared here and then used here. So, its property of declaration before use is satisfied again the same is true for even you know the x and y because x and y happen to be appearing as parameters. So, it is in some sense a declaration of these two variables. Then type of d matches the return type of dot product. So, again d is being returned by the function. So, the return value is of type int for the function dot product and what is being returned is an integer again int. So, this property also is satisfied. Then type of d matches you know result type of star as well. So, for example, here we say d equal to d plus x i star y i. So, that means we are multiplying x i and y i adding it to d and then you know the result is again the value is stored in updated again and again in the for loop. So, star is operating on two integers and it produces an integer and then it is added to d. So, int plus int is again a valid combination. Therefore, this property also holds elements of arrays x and y are compatible with star. So, again this is a proper match because x i and y i are integers and star between two integers is valid. So, these are some examples of static semantic checks in the function dot product. So, let us you know consider some of the dynamic semantic checks in dot product. So, value of i does not exceed the declared range of arrays x and y both lower and upper. So, x and y in the main program have been declared with size 10 and for some reason if we say this is size 11 or 12 i less than 11 or i less than 12 then you know we would be accessing x 11 and y 11 x 12 and y 12 etcetera which are not within the range of the declaration. So, there would be a run time error at this point. There are no in this case of course, x you know i goes from 0 to 9 and that is only 10 iterations. So, everything is well within the you know declaration of the two arrays x x and y. There are no overflows during the operations of star and plus in d equal to d plus x i star y i. Again the integers that we are if the values of the variables a and b the arrays of a components of a and b happen to be extremely large values then the multiplication x i star y i could actually cause an overflow to occur. Similarly, again if these values if there are if the arrays are too large and too many components are being multiplied and added into d then d could again you know cross the limit and cause an overflow. But in this case assuming that a and b have small values such overflows will not occur. So, these are some examples of what exactly are dynamic semantic checks in programs that cannot be done by the compiler, but a compiler can definitely generate code to perform these. For example, what happens is to check the you know array going out of bounds at run time the index value of i is checked to be less than 10 if it is then the iteration is allowed to go further otherwise an error is caused and the program stops. So, the another type of dynamic semantic check that can take place is again demonstrated with the use of a recursive function in fact. So, this computes factorial of a number. So, if n equal to 0 return 1 otherwise return n star fact of n minus 1 this is a very well known program. So, there is no need to explain it further. So, p is an integer and p is equal to fact 10. So, we are trying to compute 10 factorial. So, does the program stack overflow due to recursion. So, here when we say fact of 10 it comes here 10 into fact of 9 then again 9 into fact of 8 etcetera. So, there are really 10 invocations of fact. So, therefore, will the stack permit 10 instances of the activation records which are required to execute the function fact that is the question. So, if it does not then there will be an overflow otherwise the program will go through 10 is a small number. So, there is no need to worry about overflow in this particular case there is no overflow due to star in n star fact n minus 1. So, again if n into fact of n minus 1 goes beyond the range of representable integers then there would be an overflow at this point. So, that is an introduction to what exactly are semantic checks. So, let us see what exactly semantic analysis. So, during semantic analysis the type information of the variables whether they are ints or arrays or pulleons or characters etcetera is stored in what is known as the symbol table or the syntax tree. So, it can be stored in the symbol table or it could be stored in the nodes of the syntax tree itself. Usually the symbol table option is better because syntax tree takes much more space than the symbol table usually we store it in a symbol table and then place a pointer to the appropriate entry in the symbol table in one of the fields in the node of the syntax tree. So, what is the information that is stored? Types of variables function parameters array dimensions of course, the variable name etcetera are all stored in the symbol table. So, these symbol tables are used not only for the semantic validation or semantic analysis, but they are also required and used for subsequent phases of the compilation. For example, during intermediate code we will definitely access the symbol table to check and you know access get the information regarding the size of the arrays, the dimensions number of dimensions of the array etcetera type of the elements of the array all these are required for code generation even intermediate code generation and that would be our purpose to access the symbol table during intermediate code generation. During machine code generation again we will need to know the types of the variables in order to allocate space for them etcetera etcetera. So, optimization again requires the information about variables for movement of code etcetera etcetera. So, all these are uses of the symbol table during other phases of compilation. Then if the declarations you know need not appear before use as in the case of the language C plus plus then semantic analysis requires more than one pass. The point is when you write a class declaration in C plus plus you put the member functions and then you know usually you write all the declarations of the variables that are used within the methods. So, in such a case when we try to pass the methods or member functions we will not know the type of the variables because the declarations appear later. There is the only way to handle it is to go through the program once create the symbol table and then go through the program a second time and perform semantic analysis. So, we will see more of this towards the end of this lecture. So, static semantics of programming languages can be specified using what are known as attribute grammars. So, we will see how to check the static semantics of programming languages and how to specify them using attribute grammars. Semantic analysis can be generated semi automatically from attribute grammars. So, we write the semantic you know specifications for the program for the programming language and then we could use a generator which generates them from such an attribute grammar. The reason we say semi automatically is that the semantics are usually specified in a programming language like notation it is not a pure side effect free notation. So, that is the reason why it is called semi automatic where programmer has to sequence the semantic checks appropriately and then submit that to the generator. In fact, Yacht also is a semantic analyzer generator if we supply the semantics checks appropriately in C. Attribute grammars are extensions of context free grammars. So, now let us move on and study attribute grammars in some detail. It so happens that attribute grammars require some terminology to be understood before we pick up an example and study it. So, let us go through some terminology understand it and then proceed with an example. So, let G equal to N T P S be a context free grammar as I said attribute grammars are nothing but extensions of context free grammars. So, the base is definitely a context free grammar and let the set of variables V of the grammar be N union T. For every symbol of for x of V we can associate a set of attributes denoted as x dot a x dot b etcetera etcetera. That is why the name attribute grammar there are two types of attributes inherited attributes which are denoted as a i of x. So, inherited attributes of x and synthesized attributes which are denoted by a s of x these are really sets of attributes. Each attribute takes values from a specified domain it could be a finite domain or it could be an infinite domain and we call the domain you know such a domain as its type. For example, even in programming languages we say int of x. So, x integer domain is the domain from which the value of x we provide values for x in our program. So, we could say int is the type integer is the type of the variable x the same notion is carried over to attributes as well. So, typical domains of attributes are integers, reals, characters, strings, booleans, structures etcetera. Now, given some basic domains such as integers, reals, characters and booleans we could actually construct new domains from the given domains using mathematical operations such as cross product, map etcetera. So, even union intersection all these are permitted on these domains. There are two examples which I provide here one is for an array the other is for a structure. So, you can think of a single dimensional array as a map from the domain of natural numbers to the domain of our objects. So, n to d. So, basically for every natural number we provide the object which is to be placed in the array location. So, if we say a is an array a of 1 would contain some object that we want a 2 will contain some other object we want and so on. Where n and d are domains of natural numbers and the given objects respectively. So, we could place restrictions on the number of elements in n define a finite subset of n here and then similarly this will also be a finite set. So, in that case we would have a finite map. If we want two dimensional arrays then instead of a single domain here you would probably have a cross product of domains i cross j cross k you know that would mean i comma j comma k that would be from n cross n cross n to d. So, you take two or three dimensions then appropriately the cross product is also provided here. If you take a structure then it can be obtained using a cross product operation just like an array was obtained using a map operation. So, the cross product is a 1 cross a 2 cross a etcetera a n where n is the number of fields in the structure and a i is the domain of the ith field. So, if you have a structure with two integers and one character then a 1 would be integer a 2 would be integer and a 3 would be care. So, these would again be finite objects or infinite you know finite domains or infinite domain as the example requires. Now, a production P has a set of attribute computation rules or functions. So, this is basically the rule which is provided to compute a value for appropriate attributes of the production. So, rules are provided for the computation of synthesized attributes of the left hand side non-terminal of P and inherited attributes of the right hand side non-terminals of P. So, for example, you cannot compute the inherited attributes of the LHS non-terminal. Similarly, you cannot compute the synthesized attributes of the RHS non-terminals of the production P. So, this will become clear as we go along. These rules can use attributes of symbols from the production P only. So, in other words they are strictly rules are strictly local to the production P there are no other side effects as well. So, you cannot access attributes of other symbols from different productions you can access the attributes of various symbols within the production. Restrictions on the rules really define different types of attribute grammars. For example, we are going to study the LA-tributed grammars we are also going to study SA-tribute grammars, but we will not worry about the other types. So, there are order attribute grammars, absolutely non-circular attribute grammars, circular attribute grammars etcetera. There must be more than half a dozen such varieties possible. So, now let us understand what exactly are synthesized attributes and inherited attributes. So, an attribute cannot be both synthesized and inherited, but a symbol can have both types of attributes. So, if x is a non-terminal it can have a as a synthesized attribute and b as a as a inherited attribute, but a single attribute cannot be of both types. Attributes of symbols are evaluated over a parse tree by making passes over the parse tree. This is a very important point to note. So, in general the parse tree is always assumed to be available from the parsing stage. Then the various nodes in the parse tree internal nodes are all non-terminals and the leaves are all terminal symbols. So, they will be associated with many attributes. So, these attributes must get appropriate values through computations. So, that means we must also provide an order in which these attributes will be evaluated over the parse tree. Synthesized attributes are computed in a bottom up fashion from the leaves upwards that is why they are called synthesized. So, always synthesized from the attribute values of the children of the node and leaf nodes which are terminal symbols have only synthesized attributes. They really cannot have inherited attributes and these you know synthesized attributes are initialized by the lexical analyzer and cannot be modified. So, for example, if you take a name the character string associated with that name is returned by the lexical analyzer and the token corresponding to the name say identifier would have an attribute synthesized attribute which is the character string for that particular name. Similarly, if you take an integer constant then the token const int const would have an attribute which is the value of that particular constant and again this value is returned by the lexical analyzer. An attribute grammar with only synthesized attributes is an S attributed grammar. This is a very important class of attribute grammars and it so happens that the Yacht tool permits specification of only S attributed grammars. It does not permit specification of inherited attributes at all. Inherited attributes on the other hand flow down from the parent or siblings to the node in question. So, let us study the synthesized attributes first and then move on to the inherited attributes. So, let us take an example to understand how these synthesized attributes and in general how an attribute grammar is specified. Take a context free grammar S going to A, B, C, A going to A, A or A, B going to B, B or B, C going to C, C or C. This generates the language a to the power m, b to the power n, c to the power p where m, n and p are greater than or equal to 1 and observe that there is no relationship between m, n and p. Each one of them varies depending on the string. Suppose we want to generate a to the power n, b to the power n, c to the power n that is the counts of A, B and C are equal and n greater than or equal to 1. Let us define this is obviously known to be a context sensitive language. It is definitely not context free. So, no matter what modifications we make to this context free grammar to get another context free grammar we will never succeed. So, the no context free grammar can never generate a, n, b and c, n, but it so happens that an attribute grammar based on this context free grammar can be defined so that the language a, n, b and c, n is generated. For this grammar the synthesized attributes are the only ones we require. So, the synthesized attributes of s just one the equal the attribute name is equal the arrow here indicates up arrow indicates that is a synthesized attribute. Similarly, down arrow would indicate that it would be an inherited attribute. The value the domain of values for this attribute is t comma f true or false a finite domain. The non-terminals a, b and c again have just one attribute count. So, a has an attribute count, b also has an attribute count, c also has an attribute count, c also has an attribute count. It does not mean that they share the attribute they are different attributes, but the name is the same that is perfectly any terminal or non-terminal all of them can have attributes with the same name, but it does not mean that they it is they are shared. Count is a synthesized attribute and the domain of values is that of the integers. So, any integer value is possible for count. So, typically when we generate the basic idea is this when we generate a's using the rule a going to little a or a we associate attribute computation rules such that the number of a's is counted the same is true for b and c. And when we come to the root we check whether the number of a's equal to number of b's equal to number of c's if so then the string is accepted otherwise the string is rejected. So, here is the attribute grammar this also shows how attribute computation rules are positioned within the grammar. So, we will come to this diagram after we understand the attribute grammar the rule says a's going to a, b, c the context of the grammar rule. So, now within these flower brackets we write the attribute computation rule. So, the remember that the synthesized attributes of the left hand side non-terminal need to be provided with attribute computation rules, but we do not have any inherited attributes on the right hand side we have only synthesized attributes. So, there is no need to provide any rule for the computation of attributes of a, b and c. So, just one rule here so s dot equal that is the notation we use to indicate the attribute of s the up arrow indicates that it is a synthesized attribute. So, this is computed as if a dot count equal to b dot count and b dot count equal to c dot count then true otherwise false. So, the rule is very clear if a, b and c a dot count b dot count c dot count are all equal then s dot equal becomes true otherwise it becomes false. So, if s dot equal is true then the string is in the language and if s dot true is false then the string is not in the language. For the production a going to a we say a dot count equal to 1 because this is generating exactly 1 a for the production a going to a, a we are differentiating the two occurrences of the capital A. We provide a rule for the computation of a 1 dot count we do not provide any rule for the computation of a 2 dot count because that is a synthesized attribute on the right hand side. So, a 1 dot count is whatever is the number of a is already generated by this a 2 plus 1. So, a 2 dot count plus 1 similarly the rule for the number of b is b 1 dot count equal to b 2 dot count plus 1 and for the c is it is c 1 dot count equal to c 2 dot count plus 1. Now, let us look at the parse tree and understand how the attributes are positioned on the nodes of the parse tree. So, the here are the leaves. So, there are two a's two b's and two c's. So, the sentence is indeed in the language. So, here this is a dot count because this is the non-terminal a and here this is again another non-terminal a. So, this has the attribute a dot count etcetera. So, similarly b dot count c dot count s dot equal etcetera to begin with the attribute instances do not have any value because we have not carried out any attribute evaluation. The attribute dependences are all shown in red. So, for example, the a dot count in this particular node requires the value of a dot count at its child node. So, this is very clear from the production that is used. So, the production used here is a going to a a. So, if you look at the production a going to a a the attribute computation rule says a 1 dot count equal to a 2 dot count plus 1. So, this a 2 is nothing but the child node one of the children of the particular node a. So, this has two children a and this a. So, this count is required to compute this count whereas, the production used at this point is just a going to a. So, which has just a a dot count equal to 1. So, there is no need to use any other attribute value to compute this a dot count. The same holds for this b dot count this b dot count this c dot count and this c dot count. Finally, s dot equal requires the computed values of the three counts here that is the three children a dot count b dot count and c dot count. So, there are three dependences shown for s dot equal. So, let us now see how to evaluate these attributes. So, to be obviously, because this attribute requires this attribute it is necessary to evaluate this attribute before we go to the evaluation of this attribute the dependence indicates that. So, this attribute can be evaluated very simply as a dot count equal to 1. Similarly, the b attribute can be evaluated as b dot count equal to 1 and the c this attribute can be evaluated as c dot count equal to 1. So, the three attributes 1 2 and 3 can be evaluated you know in three steps in any particular order, but they must be evaluated first before we go to the next level. So, this is what we mean by a bottom up evaluation in the step number two. So, this was already computed we compute these attributes. So, this a dot count is this a dot count plus 1. So, this becomes 2 similarly, this also becomes 2 and this becomes 2 as well. So, we can compute 4 5 and 6 these attributes in any particular order you can compute 4 first then 5 then 6 or you could compute 4 then 6 then 5 or you could do 5 6 4 it does not matter, but we must complete the evaluation of the children before we go to this particular level. Once we have completed the evaluation at this level now we are ready to compute the you know attribute value at the root. So, these have been completed the s dot equal rule says if a dot count b dot count and c dot count are equal then true else false in this case all these three are equal. So, it is true. So, what we must observe here is the local dependence of the attributes in the attribute evaluation rule. So, for example to compute this we require the counts only at the next immediate level we will never try to access the attributes at two levels lower. So, to compute this we require this attribute which is one level lower etcetera. So, this is what we mean by the locality of the attribute computation rule. So, let us move on and now we are going to look at the attribute computation in general. So, let t be a parse tree generated by the context free grammar of an attribute grammar g. So, now you know let us go back for one second. So, you I am sure you realize that the attributes can be located on the parse tree and only then can they really be evaluated. So, we are assuming that the parse tree is computed by the syntax analyzer and parsed on to the semantic analyzer and then onwards we take over and compute the attributes over the parse tree. So, let t be a parse tree generated by the c f g of any g g. Now, we must define the attribute dependence graph or dependence graph for short for t. So, we have shown it informally in the previous example. So, the d g t is a graph v comma e where v is the set of b such that b is an attribute instance of some tree node. So, all the attribute instances of the nodes happen to be you know the nodes of the vertices of this particular direct graph and the set of edges is b comma c where b and c are in v and b and c are attributes of the grammar symbols in the same production p of b and the value of b is required for the used computing of computing the value of c in an attribute computation rule associated with production p. So, if we go back to this example these red ones are all the dependence graph edges. So, 3 plus another 3 here. So, these 6 edges actually belong to the dependence graph. So, the attribute instances are all labeled with numbers here. So, those are the vertices. So, remember even though the parse tree has other terminals a b and c these are really not part of the attribute dependence graph because they are not required in the computation of the attributes. In case these terminal symbols carry attributes which are required for the computation of some attributes then they would also be those attributes of the terminal symbols would also be a part of the dependence graph. So, that is the dependence graph, but what about it an AGG is called as a non-circular attribute grammar if and only if for all trees T derived from the grammar G the dependence graph T of T is the dependence graph T of T is a cyclic. So, this is quite you know easy to perceive because if the dependence graph is cyclic then you know we may not be able to find an order in which the attributes have to be computed. So, you know I will just assume that s dot is equal s dot equal is required for the computation of a dot count as well at this level. So, then there would be a net here in the downward direction and we would have a cycle. So, that means the attributes on the cycle cannot be evaluated that is the reason why we place this property of circularity. So, non-circular attribute grammars are the only ones which can be used in practice, but still even checking for non-circularity is a very expensive algorithm. So, exponential in the size of the grammar therefore, our interest will be in the sub classes of attribute grammars whose non-circularity can be determined very efficiently. So, this will become as we clear as we go along assigning consistent values to the attribute instances in the dependence graph of T is called as attribute evaluation. We have informally seen what is attribute evaluation. So, let us look at attribute evaluation algorithm in general. The process is simple construct the parse tree, then construct the dependence graph perform topological sort on the dependence graph and obtain an evaluation order. Evaluate attributes according to this order using the corresponding attribute evaluation rules attached to the respective productions. Multiple attributes at a node in the parse tree may result in that node of the parse tree to be visited multiple number of times, but each visit resulting in an evaluation of at least one attribute of that particular node. So, here is a formal algorithm which tells you the same thing. So, input is a parse tree T with un-evaluated attribute instances and the output is T with consistent attribute values. So, let V comma E is equal to D G of T. So, we have constructed the dependence graph. Now, initialize a Q W or a work list W with instances of attributes B such that B is in V and the in degree of B is 0. That means you do not require any other attribute to compute B. So, these are the ones which can be computed first. So, these are the lowest level in the parse tree as we saw before. So, while W not equal to phi do this is a very general algorithm and it holds for any non-circular attribute grammar. Remove some attribute B from the Q W. The value of B is value defined by the appropriate attribute computation rule. This will become clear in the next example. Now, that we have computed the value of B. So, for all B comma C in E that means B is required for computation of C. So, you reduce the in degree of C by 1 and if in degree has become 0 then add C to the Q. So, now C can also be C is eligible for you know to compute. So, it gets its chance in the Q sometime. The order in which we compute the attributes you know once they are in the Q is not very important, any order will do. So, let us take the same example and understand the evaluation possible. You know in a better way. So, there are many attribute evaluation orders possible here. So, 1, 2, 3, 4, 5, 6, 7. So, this is 1, 2, 3, 6, 5, 1, 4, 7. So, 2, 3, 6, 5, 1, 4, 7. So, we never violate the dependences and then 1, 4, 2, 5, 3, 6, 7 is another 1, 4, 2, 5, 3, 6 and then 7. So, this is an order which can be used with L R parsing. Really speaking we do not have to construct the parse tree explicitly in this case because the parsing order is actually the order in which the attributes are also evaluated and we will never visit that part of the tree again. So, if we use the order 1, 4, 2, 5, 3, 6 and then 7 we can do it using the L R parsing strategy that is because the parser you know goes in this way. So, it shifts A onto the stack then shifts this A onto the stack reduces to this A then these two together are reduced to this A and then it does the same with these B's and gets this capital B. Similarly, these two C's and then it gets this capital C. So, now it reduces to S and winds up. So, as we go along we are really traversing bottom up upwards and we will never go down again. So, it is possible to do the attribute computation as we go along and store the attribute value in the record of the non-terminal on the stack itself. So, here is the rightmost derivation and the reverse of this is used by the L R parser. So, let us understand how the attribute computations happen in this case. So, node number 1 which is here. So, A dot count equal to 1. So, that is the evaluation the A dot A to A is the production and A dot count equal to 1 is the rule. So, similarly for this particular node the value of A dot count is 2 and that is obtained because of this production and its associated computation rule A 1 dot count equal to A 2 dot count plus 1. So, this is the order in which we go ahead. So, here this is the quite clear and you can go through this example in detail later. Here is another example very interesting one attribute grammar for the evaluation of a real number from its bit string representation. So, for example, if you have a binary string here 1 1 0 dot 1 0 1. So, here is the binary point corresponding to a decimal point. So, 1 1 0 is 6 that is very clear and this 1 0 1 the weights actually go in this direction. So, the first one has minus 1 weight, second one has minus 2, third one has minus 3. So, 2 to the power minus 1 is 0.5 and 2 to the power minus 3 is 0.125. So, the value is 0.625. So, 6.625 is the value of this particular string. So, if you consider the context free grammar which generates bit strings of this kind at the highest level you would have n going to l dot r, l generates the left part r generates the fraction. So, l goes to b l r b. So, just like the count of the a generator a counts here it generates bits. So, any number of b is here and each b can go to either 0 or 1. Similarly, r also generates any number of b is here with each b going to either 0 or 1. So, now we have a context free grammar which generates strings on this side and strings on this side and joins them with a dot here. So, given this context free grammar here is an attribute grammar which evaluates the string and gets you this particular value. Again this attribute grammar requires only synthesized attributes. So, a's of the non-terminal n is the same as the synthesized attributes of r and that is in turn same as the synthesized attributes of b. So, all these three have the same name attribute called value which is from the domain of real numbers. Whereas, the non-terminal l has two attributes one is length which is an integer and another called value which is a real number. So, the basic idea is as we generate these b's. So, this l would have accumulated certain value and a b is placed to the left. So, therefore, the b should get a value which is equal to the length of the bit string here which is the length of l. So, that is why we require both length and value for this non-terminal l. So, and similarly r generates b r. So, this b is generated fresh r would have some value of its own, but since this is on the right hand side of the dot it implies that the value of r is now reduced by 2 and the value new value is assigned as the sum of the values of b and r. So, let us go through it and see. So, n going to l dot r the value of n is nothing but l dot value plus r dot value that is very simple. If l generates a single bit b l dot value is b dot value and l dot length is 1. So, that is very simple again because whatever b is either 0 or 1 is the value of l. If l 1 goes to b l 2 then l 1 dot length is l 2 dot length plus 1 which is very simple because we are generating a new bit here and l 1 dot value is b dot value this is a new bit position into 2 to the power l 2 dot length this is what I was telling you just now l has some number of bits already generated and plus l 2 dot value. So, this is the new value of l 1 here. Similarly, r to b is r dot value equal to b dot value which is simple either 0 or 1 and r 1 going to b r 2. So, this becomes r 1 dot value equal to b dot value plus r 2 dot value by 2. So, here this would have generated certain number of bits here and we have actually now said a new bit is being generated r has a certain value which is already old. So, take the value of r add the value of b and divide the whole thing by 2 because b now has a value which is always 0.5 the maximum value of the bit after the dot is 0.5. So, that is the reason why we say a b dot value plus r dot value by 2. So, b 2 0 and b 2 1 have b equal to 0 and b dot value equal to 1. So, this is the attribute grammar as far as valuation of real numbers from the bit string is concerned. So, we will stop here and in the next class we will consider the parse trees for this particular attribute grammar. Thank you.