 Welcome to part 5 of the lecture on attribute grammars. We will continue with semantic analysis and attributed translation grammars today also. So, let us start looking at this symbol table structure which is necessary to perform semantic analysis. So far, I only added a few routines to search and insert into the symbol table and showed you how to do some semantic analysis, but I did not give you a structure for the symbol table and a data structure for the symbol table. So, let us do that now. A symbol table stores names of all kinds that occur in the program along with the information about them. So, for example, the type of the name you know whether it is an integer or a floating point number or a function etcetera, the level at which it has been declared. So, it is possible to have blocks in C and in Pascal and you know Algol we can even have procedures and functions embedded within other procedures and functions. So, the level at which the name has been declared. So, which block you know the level of that block whether it is a declared parameter of a function or it is an ordinary variable all this information must be stored along with the name itself. In the case of a function we also need to access the list of parameters from the function name along with their types of course. And we must also be able to say that these are the set of local variables corresponding to this particular function, the result type of the function etcetera etcetera all these must be stored along with the function name in the symbol table. During semantic analysis of course, we use the symbol table we already know that it is necessary, but we actually perform you know intermediate code generation in which the variables that we use including the temporaries that are necessary will all be installed in the symbol table you know. So, at the end of the intermediate code generation phase we will still have pointers into the symbol table for all the names. Therefore, during the machine independent code optimization phase it becomes essential to access the symbol table as well. And during machine code generation we definitely need to access the symbol table to find out the type of the name how much storage is needed for it, what is the format in which must be stored these are can be these are all these can be determined only by looking at the type of that particular name. How do we organize the symbol table? So, it must be organized to enable a search based on the level of declaration this will become clear as we go along. And what are the data structures that can be used to organize a symbol table well if we organize it as a binary search tree then it is fairly straight forward to insert, delete, search this search tree. But then the amount of time needed to search or insert or delete a name is proportional to log n where n is the number of nodes in the tree that is assuming that the binary search tree is also a balanced binary search tree. To get a better you know search time a hash table is quite easy to use. So, we can use a hash table to store the symbol table, but there may be some issues regarding levels and one must organize a hash table carefully if we want to you know search based on levels. A simple array that is always possible a link list is also possible. So, let us look at a simple structure to store a symbol table. So, we will our data structure will be a very simple data structure and it is going to be quite restricted and not really very fast. The reason is the description of a complex symbol table you know is actually an unrelated topic any complicated data structure may be suitable for a symbol table and it does not add value to the semantic analysis or other compiler functions as far as the lecture goes. So, we will use a simple symbol table and then I will leave it to the students to understand how to use more complicated symbol tables. We will have it as an array called as the function name table and this table stores the function name records assuming that there are no nested function definitions. If there are nested function definitions then the table can be changed it is possible to do that I will show you how to do it little later. Each function name record has fields it has name it has result type parameter list pointer and variable list pointer. So, let me show you a picture. So, the function name table is a simple array or a table. So, with several fields name result type parameter list pointer you know local variable list pointer and then the number of parameters. So, each function name record you know has fields. So, and then the parameter and variable names are stored as list. So, I showed you the pointers to these lists. So, why should we store these as lists. So, for example, we want to access all the parameters possibly together and similarly, we may want to search only variable names and not get mixed up with parameters and so on. So, it is for this reason the order in which the parameters are stored is also very important for us while checking the declaration and the call. So, each parameter and variable name record has many fields. So, it has name it has type it has parameter or variable tag and the level of declaration. So, the level is set as 1 for the parameters and 2 are more for the variables. So, this is how a parameter variable name record looks like there is name type a tag and then level of declaration. Suppose, let me now describe the procedure for searching and inserting names etcetera. Suppose, there are 2 variables in the same function with the same name, but different different declaration levels. These are obviously, treated as different variables in their respective scopes. This is governed by the language specification itself and the implementation will also support it. If a variable at level greater than 2 and a parameter have the same name then the variable name over writes the parameter name. So, only within the corresponding scope this is very important, but let me read the next point and then let us see what the implications are. However, a declaration of a variable at level 2 with the same name as a parameter is flagged as an error. So, the implication is at the level just after the parameter declaration, if you have a name which is the same as that of a parameter then it is an error, but if you declare at an interior level a name which is the same as that of a parameter then the local name which is declared in the block over writes the parameter name. So, these cases must be checked very carefully and we will see how to do that and whenever we search a symbol table for a given name then suppose we start at level l the name has been used at level l then we must search the name in the symbol table at level l and if it is not found then at level minus 1 etcetera till level 2 in that order where l is the current level. So, if we find the name at level l itself and that is the declaration which is taken as relevant to us otherwise whenever we as soon as we hit a declaration which is the say which is satisfies our requirement we take that as the relevant declaration. So, let me show you how the symbol table looks like for a small sample program here. There is a function f 1 and there is another function f 2 f 2 does not return any result whereas f 1 returns an integer result f 1 has two parameters p 1 and p 2 the first one is of type floating point the other one is of type integer. So, whereas f 2 it does not have any parameters either. So, let us look at the function name record in the function name table. So, the name of the function is f 1 it is it returns a result of type int whereas f 2 returns a result which is void and then the name the function f 1 has a parameter list. So, the parameter list points to a link list containing p 1 and p 2 in which the information about it is type real whether it is a parameter or otherwise and the level are all stored and then the second function f 2 does not have any parameters. So, this becomes a nil pointer and within the function f 1 we have float v 1 and int v 2 at the level just below the parameters and then somewhere inside there is a nested block with int v 3 and float v 1. So, corresponding to that there is a link list you know of variables which is pointed to by the local var list pointer. So, the variables are stored in the order of declaration. So, first is v 1 it is type is real it is a variable and it is level is 2 the next one is v 2 it is an integer it is a variable and level 2. The third one would be v 3 integer variable, but the level is 3. Similarly, the fourth one again is v 1 real variable and level 3. So, what we must observe here is this name v 1 is the same as this name v 1, but since this has been declared in an interior block this name actually holds within this block whereas, this name does not hold within this block. So, whenever we make an assignment or read from the variable v this is the declaration which is valid for us. So, this could have been integer as well. So, if we had made it int v 1 then int v 1 would have been relevant within this block. So, and similarly suppose this name p 1 was again re declared here you know. So, say int p 1 this would have been an error because it is just below the parameter list whereas, here if we had another int p 1 it would have actually over written this declaration within this particular block it would not have been flagged as an error. What about function f 2? So, the function f 2 also has two variables v 3 and v 4. So, they are linked in this list and its pointer is present in the local var list pointer here. The number of parameters of f 1 is 2 and the number of parameters of f 2 is 0. So, this is the structure of our simple table. So, the variables v 1 at level 2 and v 1 at level 3 in the function f 1 are different variables they are not treated as the same. And when we search for a name we actually start from let us say we are within this block. So, we really start from this point then you know we start from this search this list and then we need to search the rest of it as well if we do not find anything within this list then we need to search the others as well. So, this is how it is. So, what we really need to do is make sure that we get the right variable. So, of course it is always possible to have another you know maintain another link which is in the reverse direction. So, that the search becomes faster and so on, but that is you know a minor detail. So, if we have a reverse link as well then whenever we want to search something we could search here if we do not get anything then we could start search in the reverse direction, but we should be careful to make sure that the search covers the levels in the appropriate order. So, carrying this discussion further now let us see what are the various global variables and functions we require to operate the symbol table. So, we require a variable active func p t r which stores a pointer to the function name entry in the function name table of the function that is being currently compiled. So, this will tell us that you know we are now looking at a particular function and it is being processed. The global variable level stores the current nesting level of a statement block the global variable call name pointer stores a pointer to the function name entry in the function table of the function that is being whose call is being currently processed. So, there is a difference between the call name pointer and the active function pointer. Active function pointer is useful when we compile functions and call name pointer is useful when we process calls to a particular function. Then the search function function the it actually takes the three parameters n found and f n p t r it is you know starts for it is searches the symbol table for the name n it searches only the functions and then if it is found it returns found as true otherwise it returns found as false. If the name is found then the pointer to that particular name is returned in f n p t r. So, this is the use of search function routine then the routine search param searches the parameter list of the you know function at f n p t r. So, this is the function pointer whose parameter list has to be searched and if we find it this found is made as true otherwise it is made as false and if it is if we find a parameter declaration then a pointer to that is returned in p n p t r. So, the function search where does a similar thing on the variable list. So, the name is v the function in which we want to search is f n p t r the pointer to that the level is l found is a flag and v n p t r is the pointer to it if the name is found. So, this search must be started at level l and then it should go to lower levels. So, this is where you know providing a reverse link is useful in this list. So, but I am only showing you the minimal structure that is needed to make the symbol table functional, but as I said efficiency is not the most important thing it is the understanding of how semantic analysis is performed and how the information is stored in the symbol table that is important to us at this point. So, the other symbol table routines will be explained as we go on. So, here is the grammar that we consider for as for analyzing the functions and calls to functions. A function declaration consists of a function head and then it has a variable declaration list and a body. Function head says there is a result and i d. So, this is a I know we are breaking up this production to enable better semantic analysis. So, there is result is either integer float or void i d is the name of the function and then we have a parameter list here. So, the declaration p list generates several declarations. So, dcl p l or epsilon. So, this is necessary to make sure that we can have 0 parameters or more than you know 0 parameters. So, if we have 0 parameters then we use the epsilon option otherwise we use the dcl p l option. So, dcl p l generates a list of params. So, declaration of a parameter is a type followed by a name. So, the type can be either integer or float and a variable declaration here. Again this we have seen already there is a list of declarations and each declaration is of the form d going to t l. So, l is a list of names for this declaration going further the body has variable declarations followed by a statement list. So, these are the variables which are local to the block and the block is begun using the flower bracket and ended with another flower bracket. Statement list in the block generates a list of statements. So, that is quite easy to understand and each statement could be again a body itself. So, the it starts a new body a block it could be a function call it could be an assignment and many others. We have already considered the other types of statements. So, we will consider only these three to show our semantic analysis on function declarations and function calls. So, the body actually can be regarded as a compound statement and assignment statement has been singled out because we want to show how functions can be function calls can be handled. An assignment has a form left hand side and then there is an assignment operator and E an expression. So, the left hand side we have already seen this can be either an identifier or it you know array expressions are all left for exercises. So, we are not we have done that already to some extent. So, extension of the symbol table for arrays etcetera is left for exercises. Then the expression on the right hand side. So, LHS can only be a name expression on the right hand side can be either LHS which generates a name or it could be a function call or many other types of expressions which we are not concerned with. So, what is the function call it is a name followed by a parameter list. So, the parameter list can be empty or it could be one or more parameters. So, we generate the parameters as expressions here using this production. So, let us look at the semantic analysis for the function declarations first. Function declaration starts with function head where declaration and body. So, we now look at SATG that is the with synthesized attributes. So, there is no need to write the arrow. So, what is it that we require. So, when we process the function declaration by the time we try execute this action the entire function has been parsed and analyzed. So, at this point we really delete the variable list of functions which we have function which we have just now parsed and analyzed why and that is pointed to by active funk PTR. The level of the function of course, is level itself which is obviously one at this point. If why should we delete the list well we have no use for the variable list after the body of the function has been completely analyzed. So, that is the reason we do not need the variable list anymore. So, at this point I want to show you that the deletion is important, but later I will also show you how to keep this variable list for use in a two pass compiler. The assumption here is the intermediate code generation also happens along with semantic analysis and parsing. So, after the intermediate code has been generated we do not require the variable list. However, if you observe this we have not deleted the parameter list the reason is once the function body has been completed we do not need the parameter we do not need the variable list, but we are going to use the function at a later point in time. So, at that point we definitely require the parameter list to check against the parameters in the call. So, we must be careful to delete only the variable list which is not needed anymore, but not the parameter list. So, we just reset the active function pointer to null and the level is made as 0 to make sure that we start all over again. So, here function head goes to s i d and then a declaration of the p list the parameter list. So, at this point in the production we have completed the analysis of this entire right hand side. So, the level can be raised to 2 to make sure that we now start processing variables. So, at this point the level is 0 and then functions are entered with at level 0 parameters at level 1 and variables at level 2 or more. The r s i d non terminal expands to result followed by name. So, now there is some action we need to such the function table for this name. If the name is already present then there is an error which is declared that the function is already declared and if the name is not present we enter the function with this particular name this particular type and this name p t r is the pointer returned by the routine and this pointer points to this particular function entry in the symbol table. So, active fung pointer is set as name p t r because that is the function which we are now going to analyze level is set as 1. So, now level 1 implies parameter list so that is why we need to parse this next. So, result of course either goes to int or float or void and the actions are very simple the result type is set as integer real or void appropriately. So, now let us look at the declaration p list. So, this declaration p list going to decl p l or epsilon there is nothing to do similarly, this also expands to decl p l decl param or decl param there is nothing to do, but once we have a concrete declaration decl param goes to t i d we have an action as well. So, as usual we search whether this name has already been declared as a parameters using the routine search param. So, if it is already found to found in the symbol table there is an error parameter already declared otherwise we enter the name into the symbol table using the routine enter param the type of the name is t dot type the pointer at which the function corresponding to this parameter list exist is active fung p t r. So, enter param does its job and enters the name into the symbol table at the appropriate place t expands to int or float which is trivial we have seen this so many times already where decl going to d list or epsilon and d list going to d or d list semicolon d these are quite simple there is nothing to do really. Now, once we expand d we will have some action so we will show the analysis of simple variable declarations and handling arrays is left as an exercise d going to t l. So, now l has generated a list of names so we have seen this before, but let me just repeat it you know for the sake of continuity patch var type patches the entire list l dot list at the level l with all the names in this l dot list at the level l with the type t dot type. So, that takes care of completing the information within the symbol table for the particular name then l going to i d. So, this generates either a single name or many names so let us see what happens when we search for a name. So, here so far you know we have already process the parameters so now we are processing variables. So, that is why we search whether the name has already been declared in the variable list at the appropriate level. So, if the name is found and the level is the current level then we are not allowed to declare the name again. So, the error message variable already declared at the same level is issued and we make the list as null. So, there is nothing more to do here otherwise if the level is 2 so that means we did not find the variable in the variable list anywhere, but we also have to check whether it has been declared before as a parameter which is also an erroneous situation. So, search param for this name and if it is found when re declaration of a parameter as variable that is the error message which is issued and we make l dot list as null. If there are no errors found and the name is not in the symbol table we enter the variable with the name id dot name at the appropriate level and the place where to enter is pointed to by active fung p t r. So, this is the function in which we are analyzing the variables. So, once the entry is made a pointer to that entry is returned in v n p t r. So, l dot list now has make list v n p t r. So, v n p t r is one of the nodes in that list. So, this is a single variable if there is a list of variables there is nothing very different we search the param var list issue an error if found search the parameter list issue an error if found and otherwise we simply attend append the new name to l 2 dot list and pass it on as l 1 dot list. So, whereas here we had just passed on l 1 dot list equal to l 2 dot list. So, whatever was previously found here is passed on to this the variable which was already declared is not added to the list again. So, here we add it and send it out to l 1 dot list. So, this l 1 dot list now contains all the names and d 2 t l now the types information is available in this production. So, patch var type operates properly and patches all the types of these particular names body expands to begin block that is the parances flower brackets and then the radical statement list and finally, the parances and the block ending parances. So, what do we do here we really have an action to increment the level because we are now inside this block. So, the level has increased. So, increase the level then parse these and finally, we delete all the variables at the level the current level that is after the incrementation decrement the level and get out. So, this is the action corresponding to body and statement is generates many statements the statement itself can be any one of these. So, now let us see how the functions calls etcetera processed. So, we have seen how to process the declaration. So, now, we move on to the call this is an assignment statement. So, we have already you know seen this in great detail with a cohesion and all that possible. So, here we just do this. So, we have the most basic things LHS dot type equal not equal to error type and E dot type not equal to error type and then if LHS dot type is not equal to E type we have an error error type mismatch in operands. Otherwise, if everything is there is nothing to do. So, here of course, type conversion etcetera needs to be checked as well whether each type can be converted to the LHS type has to be checked LHS expands to I D because we have not considered arrays in this particular example. So, when we get an I D we search for that name at the current level in the variable list of the current function active funk p t r. So, if it is found then you know see this is an assignment which has happened in a particular function. So, the function is still active it has not closed that is why we are searching with this name in with the currently active function and if it is not found then we search whether it is a parameter. So, if the parameter it is a parameter then fine otherwise if it is an error identifier not declared. So, if the name is found as a parameter then LHS dot type is p n pointer type if it was found as a variable then LHS dot type is v n pointer type. So, that takes care of the LHS part which can be checked here again along with E E goes to LHS there is only a type copy E dot type equal to LHS dot type. And if it is a function call then E dot type becomes function call dot type because function call itself is yet to be expanded it returns a type. So, what is function call it is a name and a parameter list. So, when we have a name we must check whether it is a function. So, search funk searches for this name and if it is found then it returns the pointer in f n p t r if it is not found we issue an error function not declared. And if it is found we assign function call dot type to the appropriate type obtained by executing get result type f n p t r. So, this function goes into the symbol table looks at the result type of the function at f n p t r and returns it as its result. So, call name pointer is f n p t r that is because this is the pointer we have just now obtained by searching. And we also need to check whether the number of parameters in the parameter list is the same as the number of parameters in the declaration. So, if call name pointer dot num param is not equal to parameter list dot p n o we have not yet seen parameter list, but it you can assume that it returns the number of parameters or number of expressions in this parameter list as p n o. So, if the number obtained from the symbol table is not equal to the number obtained through the parameter list then there is a mismatch in the number of parameters in declaration and call and this error message is printed out. What does parameter list expand to? Parameter list expands to p list. So, here there is nothing to do really parameter list dot p n o is p list dot p n o or parameter list can also expand to f 7 in which case we there are no parameters and we set it as 0 p list generates E. So, in this case this is the first parameter. So, p list dot p num p n o is set as 1 then we check the parameter type by using check param type function. So, the function pointer is call name p t r this is the first parameter and the type of the parameter you know parameter is E dot type and ok is a flag. So, if the types are matching then ok is true otherwise ok is returned as false. So, if it is false then the error message parameter type mismatch in declaration and call is issued. So, otherwise everything is ok in this. So, that means the first parameter has matched with the declaration now this is the next production p list going to p list comma E. So, this says we have checked all these parameters and we need to check this. So, p list 1 dot p n o is p list 2 dot p n o plus 1. So, the plus 1 corresponds to this E now check the parameter type corresponding to this E. So, call name pointer p list 2 dot p n o plus 1 E dot type and ok if it is not ok then issue the same error message as before. So, this is how we actually process the parameter list in a call. Let us move on and find out how we can process multidimensional arrays. So, as I told you I am going to give you only the basics and leave the implementation as an exercise. Length of each dimension must be stored in the symbol table and connected to the array name while processing declarations because it is possible to assign array slices C L O C D. So, therefore, size and types of slices must be checked during the semantic analysis of assignments that is why this particular statement is very important. We must store information about every dimension carefully. So, consider this example there is an array declaration a 10 b 20 and another declaration b 20 and third declaration c 10 10. So, this is a two dimensional array this is single dimensional and this is two dimensional. So, if you write a 5 equal to b let us analyze and find out the types. So, a of 5 is actually an array of size 20 because we have provided a subscript only for the first part. So, each element of the this dimension is an array of size 20 integers. So, a of size is an array of 20 integers the right hand side b is an array of 20 integers. So, the types match and the first this assignment is definitely valid. So, this is an example of assignment to a slice of an array in the second case c 7 is an array of size 10 whereas, a 8 is an array of size 20. So, the types do not match and therefore, this is an erroneous assignment. So, if getting down to the slices and checking the compatibility with respect to the size of the array the type of the elements etcetera is called as structure equivalence. So, this is basically structure equivalence what we have done here is structure equivalence and it is different from name equivalence which will be explained shortly. So, for multi dimensional arrays with assignment of array slices we require structure equivalence what about structs the names inside structs belong to a higher level. So, the names inside can do not collide with the names outside and equivalence of structs is based on what is known as a name equivalence and not on structure equivalence. So, let us take an example here is a struct int a comma b then float c 10 char d. So, these are the 3 4 fields of this struct and x and y are variables of this type. The second struct has a char d then float c 10 and finally, int a comma b a and b are 2 variables of this type. So, observe that this a and b and this a and b are different there is no collision between these 2. Now, if you observe carefully the 2 structs are almost the same you know this int a b is in the first 2 fields of the first struct whereas, it is in the last 2 fields of the second struct. Similarly, char d is the last one here and the first one here float is in the middle in both of them. So, when we write x equal to y we are actually looking at the same structure declaration. So, x and y belong to the same structure declaration and this you know the assignment is correct. This is the name equivalence because we are just looking at the same declaration corresponding to both x and y. If we had provided a name to this struct then you know we would really have said x and y are of that particular type name whereas, the second one a equal to x requires us to look at a permutation of the second struct and then compare it with the first which is not really done by any compiler because with large structures any number of permutations large number of permutations will be possible and checking all of them is very time consuming and possibly erroneous as well. So, when we check the struct assignments we check whether they belong to the same declaration. So, if they are then we say yes they are equivalent otherwise we say they are not equivalent. So, this is basically the name equivalence and for storing the names of the fields inside a struct an extra pointer pointing to the fields of the struct variable along with their levels can be maintained in the symbol table. So, just provide an extra pointer and store it that should be good enough. So, let us look at operator overloading this is something well known. So, operators such as plus are usually overloaded in most languages for example, we use plus with integers and we use plus with reals as well, but then in C plus plus operators such as plus can be given new or we can define new functions for the existing operators in C plus plus another this is called as operator overloading. For example, we can define plus on complex numbers we can plus on define plus on rational numbers or we can say add time you know two objects which are of time type time etcetera etcetera. So, here is an example complex operator plus this is the declaration of the overloaded operator plus its parameters are two complex quantities. So, we just want to add them. So, we have a temporary then temp dot real is temp dot real plus LHS dot RHS dot real. So, we had initialize it to LHS and similarly, we add the imaginary parts and return temp. So, whenever we write when there are say two complex numbers x and y and we simply write x plus y automatically the result is produced to be of type complex and the appropriate addition function is called. So, this is called as operator overloading there is function overloading as well. So, whatever we did with operators we can do with functions as well you know we can have the functions with the same name or and then there must be some difference between these they that various functions can return results with different types or they have can have different types of parameters or different number of parameters. So, something must be different between the functions which have the same name. So, so that the compiler can differentiate between the various functions. So, the meaning of the overloaded operators in C plus plus with built in types of parameters cannot be redefined. So, this is with respect to operator overloading. So, for example, we already have a plus on integers. So, now we cannot redefine plus on integers to mean something else and we cannot say that you know plus will now have 3 parameters. So, we must always have exactly 2 parameters for the overloaded operator plus irrespective of what type of parameters it takes. So, both function and operator overloading are resolved at compile time and both are different from virtual functions or virtual function overloading over riding. Function overloading really is useful in inheritance. So, when there is a subclass and we redefine a function which was defined in the base class that is called as function over riding and virtual functions of course, are the methods inside the inherited class. So, at a point in time I could possibly call the function within the subclass or some other base class as well. So, this is these two are very different from the overloading that we are talking about. So, here is the function overloading example. So, the same name area is used for square rectangle and circle. In this case it returns integer, here also it returns integer whereas, here it returns a float. And the number of parameters it is 1 here, 2 here and 1 here the types of parameters are also different. So, here it is float and here it is int. So, when we write something like this, this area 10 corresponds to square, area 12 comma 8 corresponds to rectangle and area 2.5 corresponds to the circle. How is this really implemented? So, for operator overloading we will need a list of operator functions along with their parameter types. So, we can store this as a hash table and the hash function must be designed very carefully. It must be designed to take the operator and its parameter types into account. So, for example, when we handle the production such as e going to e 1 plus e 2, the above hash table is searched with the signature plus e 1 dot type comma e 2 dot type. So, in other words we hash using plus then e 1 dot type and e 2 dot type. Look at the entries in the hash table match them appropriately. So, if there is exactly one match with the same operand types then we have resolved the overloading. And if there is more than one match an error is flagged, but in practice the situation is a little more complicated in C plus plus because conversions of operand types carry into float etcetera also possible before we make a decision as to which function operator is relevant at this point. Function overloading is also not so trivial to implement. The symbol table should be able to store multiple instances of the same function name along with the parameter types and other information. So, while resolving a function call say test a b c all the overloaded functions with the name test are collected. So, and the closest possible match is chosen. So, what is the closest possible match? Suppose the parameters a b c are all type int in this case and we have two of these test routines with the first one with int int and float the second one with float int and float. If the first one is chosen because we need to convert only you know one of the parameters possibly from int to float these are all int. So, whereas for the second one we need to convert two of them to float. So, converting one is faster than converting both. So, the first one is chosen. So, now let me also introduce you to semantic analysis on functions and declarations in a two pass compiler. Suppose we have we permit the variable declarations before the body and after the body instead of just before the body and the body itself has variable declarations then statement list and then variable declarations. So, we would actually be permitting declarations before and after the use. So, in such a case the symbol table must be constructed in the first pass declarations are processed in the first pass, but the statements are analyzed and errors are issued only in the second pass. The second pass of course, can be made over the parse tree whereas the first pass can be integrated with L R parsing. So, let me show you an example here. So, along with the name we need to store you know the here we call it as a block table instead of a symbol table just to distinguish between the various types and this is indexed using the block number 1 2 3 4. And we also store what is known as a surround block number along with this extra information. So, let me show you an example first. Here is the program in which we have int f 1 then we have the blocks and then we have blocks inside again and then the end of f 1 and f 2. The reason we want to show this is you know the variables etcetera here will have to be retained within the symbol table and cannot be disposed of. So, in other words we need the symbol table to be persistent it cannot be destroyed after the block or function is processed in pass 1 and it should be stored in a form that can be accessed according to levels in pass 2. So, here these are the various functions. So, we have a function f 1 and then we entered this is block number 1 function f 1 is block number 1 whereas, block number 2 is within function 1. So, it has no type or parameters etcetera, but it definitely has a list of variables attached to it block number 3 again has a list of variables attached to it block number 4 is within block 4 and it has a list of variables attached to it, but the and then function f 2 corresponds to block number 5. The important factor here is the surround block number for example, block 4 is enclosed within nested within block 3 block 3 is nested within block 1. So, corresponding to that we have the surround block number as 3 and for 3 we have the surround block number as 1 and for 1 we have the surround block number as 0 otherwise the variables are all maintained as a separate list for each block. So, this is the specialty in the case of the 2 pass compiler. So, the symbol table is indexed by block numbers and there were no separate entries for blocks in the previous case here there is a surrounded block corresponding to the enclosing block. So, all the blocks below the function entry f correspond to the function f up to the next function of course and to get the name of the parent function we just have to use the surround block numbers to until it becomes a 0. So, here for example, if we are in block 4 we just you know use the surround block number to go to 3 then to 1 and then it becomes a 0. So, f 1 is the function which nests these blocks. So, block numbers begin from 1 and there is a counter last block number which generates new block numbers and the current block number is the currently open block. So, while we open a new block current block number becomes the surrounded block number and similarly while closing a block surrounded block number is copied into the current block. So, apart from the current you know active function pointer we needs a call name pointer and of course, we also need the active block pointer itself level remains the same search function remains the same except that the entries corresponding to surrounded block number equal to 0 or the only one search search param remains the same search where is also almost the same it is just that we need to now search the blocks separately and instead of level we use the active block pointer. So, the search starts from active block pointer and proceeds upwards using the surrounded block numbers until the enclosing function is reached. So, in other words here we search from the innermost block till we reach the function as in the previous case it is just that we have all the information about all the blocks towards separately and therefore, in the second pass of the compiler you know searching these possible. So, we will stop here. So, this completes the semantic analysis phase of our compiler and from the next lecture we will concentrate on intermediate code generation. Thank you.