Compiler Design Course Module
Compiler Design Course Module
Developed By:
July, 2025
Hossana, Ethiopia
i
Module Preface
This resource module is designed and developed in support of the Compiler Design Course. It
provides learning resources as well as teaching ideas.
Dear students, in chapter one you have been studied about Introduction of compiler, Phases of a
Compiler, Computer Language Representation, and Compiler Construction Tools within week
1.
In chapter two, Lexical Analysis have been studied, understanding of lexical analysis and how
lexical analysis works, and examples of lexical analyzer, how to handle errors in the lexical
phase , and how to convert NFA to DFA are also studied within weeks 2-3.
In chapter Three, Syntax Analyzer have been studied, understanding of syntax analysis and
how syntax analyzer works, and examples of syntax analyzer, the difference between syntax
and parse tree, understanding of types of parsing ,and how to handle errors recovery in the
syntax phase are also studied within weeks 4-6.
In chapter four, the overview of SDT have been studied, the difference between SDT versus
SDD, understanding Attribute grammar, design the evaluation orders for SDD, and how to
construct the syntax tree are well studied within weeks 7-8.
In chapter Five, introduction of Type checking in compiler design concepts have been studied,
and identify the types of conversion are well studied within weeks 9-10.
In chapter Six, Overview of Intermediate Language have been studied, understanding of kinds of
intermediate language and kinds of three address statement, how to represent the three address
code in flow control statement, back patching and procedure call are well studied within weeks
11-12.
In chapter Seven , Overview of Run time- Environment have been studied , definition of Symbol
table and hash table and their uses , representation of scope information are well studied within
weeks 13-14.
In chapter Eight, Overview of Code Generation and Optimization have been studied , how to
represent DAG , understanding of flow blocks and loops, and peephole optimization Techniques
and characteristics are also well studied within weeks 15-16.
ii | P a g e
Table of Contents
Module Preface ............................................................................................................................................. ii
List of Figures ............................................................................................................................................... vi
CHAPTER ONE INTRODUCTION ............................................................................................................ 1
Introduction .................................................................................................................................................. 1
1.1. Introduction to Compilers ............................................................................................................. 1
1.1.1. Parts of Compilation ................................................................................................. 3
1.1.2. Analysis of the Source Program ............................................................................... 5
1.2. Phases of a Compiler .................................................................................................................... 5
1.2.1. The Analysis Phases of the Compiler ....................................................................... 8
1.3. Computer Language Representation ................................................................................................ 13
CHAPTER TWO LEXICAL ANALYSIS ................................................................................................. 19
Introduction ................................................................................................................................................ 19
2.1. Overview of Lexical Analysis..................................................................................................... 19
2.1.1. The Role of the Lexical Analyzer ........................................................................... 20
2.1.2. Issues in Lexical Analysis ....................................................................................... 21
2.1.3. Tokens, Patterns, Lexemes ..................................................................................... 21
2.2. Specification and Recognition of Token ..................................................................................... 24
2.2.1. Specification of Tokens .......................................................................................... 25
2.2.2. Recognition of Tokens ............................................................................................ 28
2.3. Formal definition of a Finite Automaton .................................................................................... 30
2.3.1. NFA to DFA Conversion ........................................................................................ 31
2.4. Lexical Error ............................................................................................................................... 36
2.5. Lexical Analyzer Generator ......................................................................................................... 36
CHAPTER THREE SYNTAX ANALYSIS............................................................................................... 38
3.1. Overview of Syntax Analysis ..................................................................................................... 38
3.1.1. Role of a Parser ....................................................................................................... 39
3.3. Syntax Tree versus Parse Tree .................................................................................................... 44
3.3.1. Derivation .................................................................................................................... 45
3.3.2. Parse Tree .................................................................................................................... 47
3.3.3. Ambiguity .................................................................................................................... 48
iii | P a g e
3.3.4. Eliminating Left Recursion ..................................................................................... 50
3.3.5. Left Factoring.......................................................................................................... 52
3.4. Regular Expression Versus Context Free Grammar ................................................................... 53
3.5. Parsing......................................................................................................................................... 54
TYPES OF PARSING ............................................................................................................................ 54
3.5.1 Top-Down Parsing .................................................................................................. 56
3.5.2. Bottom up parsing ................................................................................................... 71
3.6. Error Recovery Strategies ........................................................................................................... 77
CHAPTER FOUR SYNTAX DIRECTED TRANSLATION ..................................................................................... 80
Introduction ................................................................................................................................................. 80
4.1. Overview of Syntax Directed Translation (SDT) .......................................................................... 80
4.2. Syntax Directed Definitions ........................................................................................................ 82
4.3. Syntax-Directed Translation Schemes (SDT) ............................................................................. 82
4.4. Inherited and Synthesized Attributes .......................................................................................... 83
4.5. Attribute Grammars .................................................................................................................... 86
4.6. Evaluation Orders for SDD's ...................................................................................................... 87
4.6.1. Dependency Graphs ................................................................................................ 87
4.7. Construction of Syntax Tree ....................................................................................................... 88
CHAPTER FIVE TYPE CHECKING ................................................................................................................... 90
Introduction ................................................................................................................................................ 90
5.1. Introduction to Type Checking .................................................................................................... 90
5.1.1. Designing a Type Checker ...................................................................................... 91
5.2. Rules for Type Checking ............................................................................................................ 93
5.3. Type Conversion ......................................................................................................................... 93
5.3.1. Implicit Conversion ................................................................................................ 94
5.3.2. Explicit Conversion (Casts) .................................................................................... 96
5.3.3. User-defined Conversion ........................................................................................ 97
CHAPTER SIX INTERMEDIATE LANGUAGES ................................................................................... 98
Introduction ................................................................................................................................................. 98
6.1. Overview of Intermediate Language ................................................................................................ 98
6.2. Implementation of Three-address Statements ........................................................................... 102
iv | P a g e
6.2.1. Quadruples ............................................................................................................ 102
6.2.2. Triples ................................................................................................................... 102
6.2.3. Indirect triples ....................................................................................................... 103
6.3. Flow of Control Statement ........................................................................................................ 104
6.4. BACKPATCHING ................................................................................................................... 107
6.5. Procedure Calls ......................................................................................................................... 108
CHAPTER SEVEN RUN TIME- ENVIRONMENTS.......................................................................................... 110
Introduction .............................................................................................................................................. 110
7.1. Run-Time Environment ................................................................................................................. 110
7.2. Symbol Table ................................................................................................................................. 110
7.2.1. Symbol Table Entries ............................................................................................ 112
7.2.2. Operations of Symbol Table ................................................................................. 113
7.2.3. Implementation of Symbol Table ......................................................................... 114
7.3. Hash Table ................................................................................................................................ 116
7.4. Representing Scope Information ............................................................................................... 118
CHAPTER EIGHT CODE GENERATION AND OPTIMIZATION....................................................................... 121
Introduction .............................................................................................................................................. 121
8.1. Introduction to Code Generation and Optimization .................................................................. 121
8.1.1. Issues in the Design of a Code Generator ............................................................. 121
8.2. The Target Machine .................................................................................................................. 125
8.3. Run-time Storage Management ................................................................................................ 125
8.4. Basic Blocks and Flow Graphs ................................................................................................. 125
8.4.1. Basic Blocks.......................................................................................................... 125
8.4.2. Flow Graphs .......................................................................................................... 129
8.4.3. Loops..................................................................................................................... 130
8.5. A Simple Code Generator ......................................................................................................... 131
8.6. Register and Address Descriptors ............................................................................................. 131
8.7. The DAG Representation of Basic Blocks ............................................................................... 131
8.8. The Principal Sources of Optimization ..................................................................................... 132
8.9. Peephole Optimization .............................................................................................................. 135
References ................................................................................................................................................ 136
v|Page
List of Figures
Figure1. 1: Major function of Compiler ......................................................................................... 2
Figure1. 2: Syntax Tree for Position: =initial rate*60 .................................................................... 3
Figure1. 3: Block Diagram of Phases of Compiler ......................................................................... 7
Figure1. 4: The data structure in (b) is for the tree in (a)................................................................ 9
Figure 1. 5: Source program Examples of each Phases ................................................................ 12
Figure1. 6: Language Processing System ..................................................................................... 16
Figure 2. 1: Interaction of Lexical Analyzer with Parser .............................................................. 20
Figure 2. 2:Finite Automata State Graph ...................................................................................... 31
Figure 3. 1: CFG and RE Diagram ............................................................................................... 39
Figure 3. 2: Position of parser in compiler model ....................................................................... 40
Figure 3. 3: Example of Syntax and Parse Tree............................................................................ 45
Figure 3. 4: left recursion .............................................................................................................. 51
Figure 3. 5: Types of parsing ........................................................................................................ 56
Figure 3. 6: Example of Top down and Bottom up approach ....................................................... 56
Figure 3. 7: Top-down parse for id + id * id................................................................................. 58
Figure 3. 8: LL(1) grammar .......................................................................................................... 66
Figure 3. 9: Types of errors recovery............................................................................................ 77
Figure 4. 1: synthesis and Inherited attribute ................................................................................ 84
Figure 4. 2: Dependency graphs ................................................................................................... 87
Figure 4. 3: Examples of Annotated parse tree ............................................................................. 89
Figure 6. 1: Intermediate Language ............................................................................................. 98
Figure 6. 2: Abstract syntax tree representation ......................................................................... 100
Figure 6. 3: Syntax-directed definition for flow-of-control statements ...................................... 105
Figure 6. 4: SDD for if-then, if-then-else, and while-do statements........................................... 107
Figure 7. 1: Representing scope information .............................................................................. 120
Figure 8. 1: Position of code generator ....................................................................................... 121
Figure 8. 2:Flow graph for the vector dot product ...................................................................... 130
vi | P a g e
CHAPTER ONE
INTRODUCTION
Introduction
In this chapter, you have been studied about Introduction of compiler, Phases of a Compiler,
Computer Language Representation, and Compiler Construction Tools
1
Figure1. 1: Major function of Compiler
2|Page
1.1.1. Parts of Compilation
3|Page
Pretty printers
Static checkers
Interpreters
Structure Editor
► A structure editor takes as input a sequence of commands to build a source program.
► The structure editor not only performs the text-creation and modification functions of an ordinary text editor, but it also analyzes
the program text, putting an appropriate hierarchical structure on the source program.
► For example, it can check that the input is correctly formed, can supply keywords automatically (e-g.. when the user types while.
the editor supplies the matching do and reminds the user that a conditional must come between them), and can jump from a
begin or left parenthesis to its matching end or right parenthesis.
Pretty Printers
► A pretty printer analyzes a program and prints it in which a way that the structure of the program becomes clearly visible.
► For example, comments may appear in a special font, and statements may appear with an amount of indentation proportional to
the depth of their nesting in the hierarchical organization of the statements.
Static Checkers
► A static checker reads a program, analyzes it, and attempts to discover potential bugs without running the program.
► For example, a static checker may detect that parts of the source program can never be executed.
► It can catch logical errors such as trying to use a real variable as a pointer.
Interpreters
► Interpreter performs the operations implied by the source program.
4|Page
► For an assignment statement, for example, an interpreter might build a tree like Fig. 1.2, and then carry out the operations at the
nodes as it "walks" the tree.
► Interpreters are frequently used to execute command languages, since each operator executed in a command language is usually
an invocation of a complex routine such as an editor or compiler.
► The analysis portion in each of the following examples is similar to that of a conventional compiler.
1) Linear Analysis:- is called lexical analysis or scanning. It is the process of reading a character from left-to-right and grouped
into tokens that are sequences of characters having a collective meaning.
2) Hierarchical Analysis:- is called as syntax analysis or parsing. In this analysis the characters or tokens are grouped
hierarchically into nested collections with collective meaning.
3) Semantic Analysis:- in which certain checks are performed to ensure that the components of a program fit together
meaningfully. i.e.; it check the source program for semantic errors and gathers type information for subsequent code generation
phase.
5|Page
► An essential function of a compiler is to record the identifiers used in the source program and collect information about various
attributes of each identifier.
► These attributes may provide information about the storage allocated for an identifier, its type, its scope and, in the case of
procedure names, such things as the number and types of its arguments, the method of passing each argument and the type
returned.
► A symbol table is a data structure containing a record for each identifier, with fields for the attributes of the identifier. The data
structure allows us to find the record for each identifier quickly and to store or retrieve data from that record quickly.
► When an identifier in the source program is detected by the lexical analyzer, the identifier is entered into the symbol table.
► However, the attributes of an identifier cannot normally determined during lexical analysis. For example, in a Pascal declaration
like
var position, initial, rate: real;
► The type real is not known when position, initial, and rate are seen by the lexical analyzer.
► The remaining phases enter information about identifiers into the symbol table and then use this information in various ways.
6|Page
Figure1. 3: Block Diagram of Phases of Compiler
► Each phase can encounter errors. However, after detecting an error, a phase must deal with that error, so that compilation can
proceed, allowing further errors in the source program to be detected.
► The lexical phase can detect errors where the characters remaining in the input do not form any token of the language.
► Errors where the token stream violates the structure rules of the language are determined by the syntax analysis phase.
7|Page
During semantic analysis the compiler tries to detect constructs that have the right syntactic structure but no meaning to the
operation involved.
Lexical Analysis
► Lexical Analysis or Scanners reads the source program one character at a time, carving the source program into a sequence of
automatic units is called tokens.
► The lexical analysis phase reads the characters in the source program and groups them into a stream of tokens.
► Each token represents a logically cohesive sequence of characters, such as an identifier, a keyword (if, while, etc.), a punctuation
character, or a multi-character operator like :=.
► The character sequence forming a token is called the lexeme for the token.
► Certain tokens will be augmented by a "lexical value."
► The lexical analyzer not only generates a token, say id, but also enters the lexeme rate into the symbol table.
► Consider the above expression
Position:= initial + rate * 60
Syntax Analysis
► The second stage of translation is called syntax analysis or parsing. In this phase expressions, statements, declarations etc…
are identified by using the results of lexical analysis.
► Syntax analysis is aided by using techniques based on formal grammar of the programming language.
8|Page
► It groups token together into syntactic structures. (Fig.1.11a.. syntax tree)
► A typical data structure for the tree is shown in Fig. 1.11(b) in which an interior node is a record with a field for the operator and
two fields containing pointers to the records for the left and right children.
► A leaf is a record with two or more fields, one to identify the token at the leaf, and the others to record information about the
token.
Semantic Analysis
► An important component of semantic analysis is type checking. Here the compilers checks that each operator has operands that
are permitted by the source language specification.
► For example; many programming language definition require a compiler to report an error every time a real number is used to
index an array.
► However, the language specification may permit some operand coercions, for example, when a binary arithmetic operator is
applied to an integer and real, in this case, the compiler may need to convert the integer to a real.
9|Page
Intermediate Code Generation
► After syntax and semantic analysis, some compilers generates an explicit intermediate representation of the source program.
► An intermediate representation of the final machine language code is produced. This phase bridges the analysis and synthesis
phases of translation.
► This intermediate representation should have two important properties. It should be easy to produce and easy to translate into the
target program.
► The intermediate representation can have a variety of forms and one of the forms is called “Three address code”, which is like
the assembly language for a machine in which every memory location can act like a register.
► Three address code consists of a sequence of instructions, each of which has at most three operands.
► Three address code for the statement position : = initial + rate * 60 is
temp1:=int to real(60)
temp2:=id3*temp1
temp3:=id2*temp2
id1:=temp3
Inter mediate form has several properties.
► First, each three-address instruction has at most one operator in addition to the assignment. Thus, when generating these
instructions, the compiler has to decide on the order in which operations are to be done; the multiplication precedes the addition
in the source program of (1.1).
► Second, the compiler must generate a temporary name to hold the value computed by each instruction.
► Third, some "three address" instructions have fewer than three operands, e.g., the first and last instructions in (1.3).
Code Optimization
10 | P a g e
► The code optimization phase attempts to improve the intermediate code, so that the output faster running machine code and
takes less space.
► The above intermediate code is optimized like this,
temp1:=id3*60.0
id1:=id2+temp1
► int to real operation can be eliminated by the conversion of 60 integer in to real and temp3 is used only once, to transmit its
value to id1, so it can be eliminated.
Code Generation
► The final phase of the compiler is the generation of target code, consisting normally of relocatable machine code or assembly
code, Memory locations are selected for each of the variables used by the program.
► Then, intermediate instructions are each translated into a sequence of machine instructions that perform the same task.
► A crucial aspect is the assignment of variables to registers. For example, using registers 1and 2, the translation of the code of the
above code might become:
MOVF id3, R2
MULF #60.0, R2
MOF id2, R1
ADDF R2, R1
MOVF R1, id1
► The first and second operands of each instruction specify a source and destination, respectively. The F in each instruction tells us
that instructions deal with floating-point numbers.
► This code moves the contents of the address id3 into register 2, then multiplies it with the real constant 60.0
11 | P a g e
► The third instruction moves id2 into register 1 and adds to it the value previously computed in register 2. Finally, the value in
register 1 is moved into the address of id1.
EXERCISE 1.1
12 | P a g e
1. Write output of all the phases of compiler for following statements:
A. x=b-c*2
B. I=P*n*r/100
C. E=M * C**2
Preprocessors
A preprocessor, generally considered as a part of compiler, is a tool that produces input for compilers. It deals with macro-processing,
augmentation; file inclusion, language extension, etc. They may perform the following functions:
1) Macro Processing: - A preprocessor may allow a user to define macros that are short hands for longer constructs.
2) File Inclusion: - A preprocessor may include header files into the program text. For example, the C preprocessor causes the
contents of the file <global.h> to replace the statement #include <global.h> when it processes a file containing this statement.
3) Rational Preprocessors:- These processors augment older languages with more modern flow-of-control and data-structuring
facilities. For example, such a preprocessor might provide the user with built-in macros for constructs like while-statements or if-
statements, where none exist in the programming language itself.
4) Language Extensions:- These preprocessors attempt to add capabilities to the language by what amounts to built-in macros, For
example. The language Equel is a database query language embedded in C.
► Statements beginning with ## are taken by the preprocessor to be database-access statements, unrelated to C, and are translated
into procedure calls on routines that perform the database access.
► Macro processors deal with two kinds of statement:-
macro definition
macro use
13 | P a g e
► Definitions are normally indicated by some unique character or keyword, like define or macro. They consist of a name for the
macro being defined and a body, forming its definition.
► The use of a macro consists of naming the macro and supplying actual parameters, that is values for its formal parameters. The
macro processor substitutes the actual parameters for the formal parameters in the body of the macro; the transformed body
then replaces the macro use itself.
Assemblers
► Some compilers produce assembly code that is passed to an assembler for further processing, other compilers perform the job of
the assembler, producing relocatable machine code that can be passed directly to the loader/link-editor.
► Assembly code is a mnemonic version of machine code, in which names are used instead of binary codes for operations, and
names are also given to memory addresses.
► A typical sequence of assembly instruction might be :
MOV a, R1
ADD #2, R1
MOV R1, b
► This code moves the contents of the address a in to register 1, then adds the constant 2 to it and finally stores the result in the
location named by b. Thus, it computes b: = a + 2.
► The simplest form of assembler makes two passes over the input, where a pass consists of reading an input file once. In the first
pass, all the identifiers that denote storage locations are found and stored in a symbol table.
14 | P a g e
► Identifiers are assigned storage locations as they are encountered for the first time, so after reading, the symbol table might contain
the entries shown in Figure. In that figure, we have assumed that a word, consisting of four bytes, is set aside for each identifier,
and that addresses are assigned starting from byte 0.
Symbol table
IDENTIFIER ADDRESS
a 0
b 4
► In the second pass, the assembler scans the input again. This time, it translates each operation code into the sequence of bits
representing that operation in machine language, and it translates each identifier representing a location into the address given for
that identifier in the symbol table.
► The output of the second pass is usually relocatable machine code, meaning that it can be loaded starting at any location L in
memory.
► Usually, a program called a loader performs the two functions of loading and link-editing.
► The process of loading consists of taking relocatable machine code, altering the relocatable addresses, and placing the altered
instructions and data in memory at the proper locations.
► The link-editor allows us to make a single program from several files of relocatable machine code, these files may have been the
result of several different compilations, and one or more may be library files of routines provided by the system and available to
any program that needs them.
15 | P a g e
► If the files art to be used together in a useful way, there may be some external references, in which the code of one file refers to a
location in another file. This reference may be to a data location defined in one file and used in another, or it may be to the entry
point of a procedure that appears in the code for one file and is called from another file.
► The relocatable machine code file must retain the information in the symbol table for each data location or instruction label that is
referred to externally. If we do not know in advance what might be referred to, we in effect must include the entire assembler
symbol table as part of the relocatable machine code.
16 | P a g e
► The following is a list of some useful compiler construction tools:
1) Parser generators:- These produce syntax analyzers, normally from input that is based on a context-free grammar. In early
compilers, syntax analysis consumed not only a large fraction of the running time of a compiler, but a large fraction of the
intellectual effort of writing a compiler. This phase is now considered one of the easiest to implement. Many parser generators
utilize powerful parsing algorithms that are too complex to be carried out by hand.
2) Scanner generators:- These automatically generate lexical analyzers, normally from a specification based on regular expressions.
The basic organization of the resulting lexical analyzer is in effect a finite automaton.
3) Syntax-directed translation engines:- These produce collections of routines that walk the parse tree, such as intermediate code.
The basic idea is that one or more "translations" are associated with each node of the parse tree, and each translation is defined in
terms of translations at its neighbor nodes in the tree.
4) Automatic code generators:- Such a tool takes a collection of rules that define the translation of each operation of the
intermediate language into the machine language for the target machine. The rules must include sufficient detail that we can
handle the different possible access methods for data; e.g.. Variables may be in registers, in a fixed (static) location in memory, or
may be allocated a position on a stack.
The basic technique is "template matching." The intermediate code statements are replaced by "templates" that represent
sequences of machine instructions, in such a way that the assumptions about storage of variables match from template to template.
5) Data flow engines:- Much of the information needed to perform good code optimization involves "data-flow analysis," the
gathering of information about how values are transmitted from one part of a program to each other part. Different tasks of this
nature can be performed by essentially the same routine, with the user supplying details of the relationship between intermediate
code statements and the information being gathered.
Features of compiler construction tools:
17 | P a g e
► Lexical Analyzer Generator: This tool helps in generating the lexical analyzer or scanner of the compiler. It takes as
input a set of regular expressions that define the syntax of the language being compiled and produces a progra m that
reads the input source code and tokenizes it based on these regular expressions.
► Parser Generator: This tool helps in generating the parser of the compiler. It takes as input a context-free grammar that
defines the syntax of the language being compiled and produces a program that parses the input tokens and builds an
abstract syntax tree.
► Code Generation Tools: These tools help in generating the target code for the compiler. They take as input the abstract
syntax tree produced by the parser and produce code that can be executed on the target machine.
► Optimization Tools: These tools help in optimizing the generated code for efficiency and performance. They can
perform various optimizations such as dead code elimination, loop optimization, and register allocation.
► Debugging Tools: These tools help in debugging the compiler itself or the programs that are being compiled. They can
provide debugging information such as symbol tables, call stacks, and runtime errors.
► Profiling Tools: These tools help in profiling the compiler or the compiled code to identify performance bottlenecks and
optimize the code accordingly.
► Documentation Tools: These tools help in generating documentation for the compiler and the programming language
being compiled. They can generate documentation for the syntax, semantics, and usage of the language.
► Language Support: Compiler construction tools are designed to support a wide range of programming languages,
including high-level languages such as C++, Java, and Python, as well as low-level languages such as assembly language.
► Cross-Platform Support: Compiler construction tools may be designed to work on multiple platforms, such as
Windows, Mac, and Linux.
► User Interface: Some compiler construction tools come with a user interface that makes it easier for developers to work
with the compiler and its associated tools.
18 | P a g e
CHAPTER TWO
LEXICAL ANALYSIS
Introduction
In this chapter, Lexical Analysis have been studied, understanding of lexical analysis and how lexical analysis works, and examples of lexical
analyzer, how to handle errors in the lexical phase , and how to convert NFA to DFA
Understanding of the overview of lexical analysis, the role of lexical analyzer, and their issues of lexical analyzer
Identify the basic terms of lexical analyzer:- tokens, lexemes, and pattern
Identifies and understand the Specification and Recognition of Token, and formal definition of finite automata
Analyze and develop lexical error and Lexical Analyzer Generator
► When lexical analyzer identifies the first token it will send it to the parser, the parser receives the token and calls the lexical
analyzer to send next token by issuing the getNextToken() command. This Process continues until the lexical analyzer identifies
all the tokens. During this process the lexical analyzer will neglect or discard the white spaces and comment lines.
20 | P a g e
Secondary Tasks
► One such task is stripping out from the source program comments and white space in the form of blank, tab, and newline
characters.
Another is correlating error messages from the compiler with the source program. For example, the lexical analyzer may keep track of
the number of newline characters seen, so that a line number can be associated with an error message.
Token:
► A lexical token is a sequence of characters that can be treated as a unit in the grammar of the programming languages.
21 | P a g e
Example of tokens:- keywords, operators, identifiers, constants, literal strings, and punctuation symbols such as parentheses,
commas, and semicolons.
► In programming, a token is the smallest unit of meaningful data; it may be an identifier, keyword, operator, or symbol.
► A token represents a series or sequence of characters that cannot be decomposed further.
► Tokens may be divided into three categories:
1. Terminal Symbols (TRM) : Keywords and operators.
2. Literals (LIT) : Values like numbers and strings.
3. Identifiers (IDN) : Names defined by the user.
Example 1: In c programming language how many total number of tokes?
int a=2; // input source code
Tokens
int (keyword), a(identifier), =(operator), 2(constant) and ;(punctuation-semicolon)
Total number of tokens = 5
Pattern:-
► There is a set of strings in the input for which the same token is produced as output. This set of strings is described by a
rule called a pattern associated with the token. The pattern is said to match each string in the set.
► A pattern is a description of the form that the lexemes of a token may take [or match]. In the case of a keyword as a
token, the pattern is just the sequence of characters that form the keyword. For identifiers and some other tokens, the
pattern is a more complex structure that is matched by many strings.
E.g. The pattern for the identifier token id, is id-> letter(letter/digit)*
► A pattern is a rule describing the set of lexemes that can represent a particular token in source program.
22 | P a g e
Lexeme:-
► A lexeme is a sequence of characters in the source program that is matched by the pattern for a token and is identified by
the lexical analyzer as an instance of that token.
► Lexemes: - are the specific character strings that make up a token.
For example, in the Pascal statement, const pi = 3.1416
The substring pi is a lexeme for the token "identifier".
For example: abc and 123
main is lexeme of type identifier(token)
(,),{,} are lexemes of type punctuation(token)
23 | P a g e
Tokens for the above C language
Total identifier 1
= operator 1
Sum identifier 2 tokens
+ operator2
35 constant
Exercises
Q1. Identify the numbers of tokens from c programming language source code and how many total number of tokens for
given c language code?
int main() {
//printf()sends the string inside quotation to the standard output (the display)
printf(“Welcome to Computer Science!”);
return 0;
}
Q2. C source language expression: - W=x+y; write output of lexical analyzer
Types of Tokens
24 | P a g e
► Keywords: Reserved words that have a special meaning to the compiler and cannot be used for naming variables or
functions. Examples include int, char,[Link].
► Identifiers: Names given to various components in the program, such as variables and functions. Keywords cannot be used
as identifiers.
► Operators: Symbols used to perform operations in a programming language, such as +, -, *, /.
► Punctuations: Special symbols that separate different code elements, such as commas, semicolons, and braces.
Alphabets
Special symbols
♦ A typical high-level language contains the following symbols:-
Languages
26 | P a g e
Operations on languages include:
1. Union:
► Combines two languages by including all strings from both.
► Union of two languages L and M is written as L U M = {s | s is in L or s is in M}
2. Concatenation:
► Links strings from one language to all strings of another.
► Concatenation of two languages L and M is written as LM = {st | s is in L and t is in M}
3. Kleene Closure:
► Provides a set of all strings obtained by concatenating a language zero or more times.
► The Kleene Closure of a language L is written as L* = Zero or more occurrence of language L.
4. Positive Closure:
► Provides a set of all strings obtained by concatenating a language one or more times
Regular Expressions
► Regular expressions are strings of characters that define a searching pattern. They are used to specify tokens in a
programming language. Common symbols in regular expressions include:
Asterisk (*): Indicates that the preceding symbol can be repeated any number of times.
Plus (+): Indicates that the preceding symbol can be repeated one or more times.
Wildcard (.): Can be replaced by any character.
Character Class: Represents multiple characters, such as [a-z]
► The lexical analyzer needs to scan and identify only a finite set of valid string/token/lexeme that belong to the language in
hand. It searches for the pattern defined by the language rules.
27 | P a g e
► Regular expressions have the capability to express finite languages by defining a pattern for finite strings of symbols.
► The grammar defined by regular expressions is known as regular grammar.
► The language defined by regular grammar is known as regular language.
Notations
If r and s are regular expressions denoting the languages L(r) and L(s), then
► The specification of tokens is crucial in compiler design as it helps in the accurate parsing and interpretation of code.
► Tokens, patterns, and lexemes represent the basic elements of any programming language, aiding in breaking down and
understanding the code
► Understanding the specification of tokens ensures that the code is parsed correctly and that valid sequences for tokens are
predefined, helping in the efficient analysis and compilation of programs
1. Tokens: These are the basic building blocks of a program. Examples include:
Keywords (e.g., if, for, while)
Identifiers (e.g., variable names, function names)
Literals (e.g., numbers, strings, characters)
Operators (e.g., +, -, =, ==)
Punctuation (e.g., commas, semicolons, braces)
2. Lexical Analyzer (Scanner):
The component of the compiler responsible for token recognition.
It reads the source code character by character and groups them into tokens.
It also handles tasks like removing whitespace, comments, and other irrelevant characters.
3. Regular Expressions:
Used to define patterns that match different types of tokens.
The lexical analyzer uses these patterns to identify and classify tokens.
4. Token Stream:
After tokenization, the compiler receives a sequence of tokens, which is then passed to the next phase (syntax analysis).
29 | P a g e
To convert the raw source code into a structured format (tokens) that can be processed by the compiler.
To detect syntax errors early in the compilation process.
To provide a common interface for the rest of the compiler (e.g., syntax analysis, semantic analysis, code generation).
Example
int main() {
int x = 10;
return 0;
}
The lexical analyzer would break this into the following tokens: int, main, (, ), {, int, x, =, 10, ;, return, 0, ;, }
31 | P a g e
To convert the NFA to its equivalent transition table, we need to list all the states, input symbols, and the transition rules. The
transition rules are represented in the form of a matrix, where the rows represent the current state, the columns represent the
input symbol, and the cells represent the next state.
Step 2: Create the DFA‟s start state:
The DFA‟s start state is the set of all possible starting states in the NFA. This set is called the “epsilon closure” of the NFA‟s
start state. The epsilon closure is the set of all states that can be reached from the start state by following epsilon (λ) transitions.
Step 3: Create the DFA‟s transition table:
The DFA‟s transition table is similar to the NFA‟s transition table, but instead of individual states, the rows and columns
represent sets of states. For each input symbol, the corresponding cell in the transition table contains the epsilon closure of the
set of states obtained by following the transition rules in the NFA‟s transition table.
Step 4: Create the DFA‟s final states:
The DFA‟s final states are the sets of states that contain at least one final state from the NFA.
Step 5: Simplify the DFA:
The DFA obtained in the previous steps may contain unnecessary states and transitions. To simplify the DFA, we can use
the following techniques:
Remove unreachable states: States that cannot be reached from the start state can be removed from the DFA.
Remove dead states: States that cannot lead to a final state can be removed from the DFA.
Merge equivalent states: States that have the same transition rules for all input symbols can be merged into a single state.
Step 6: Repeat steps 3-5 until no further simplification is possible:
32 | P a g e
After simplifying the DFA, we repeat steps 3-5 until no further simplification is possible. The final DFA obtained is the
minimized DFA equivalent to the given NFA.
example1: Convert the given NFA to DFA.
Solution: For the given transition diagram we will first construct the transition table.
33 | P a g e
Now we will obtain δ' transition on [q0, q1].
Similarly,
As in the given NFA, q1 is a final state, then in DFA wherever, q1 exists that state becomes a final state. Hence in the DFA,
final states are [q1] and [q0, q1]. Therefore set of final states F = {[q1], [q0, q1]}.
The transition table for the constructed DFA will be:
34 | P a g e
The Transition diagram will be:
With these new names the DFA will be as follows:
35 | P a g e
2.4. Lexical Error
► Lexical errors are the errors thrown by the lexer when unable to continue.
► Which means that there‟s no way to recognise a lexeme as a valid token for you lexer? Syntax errors, on the other side, will be
thrown by your scanner when a given set of already recognized valid tokens don't match any of the right sides of your grammar
rules.
► If the string fi is encountered in a C program for the first time in the context
fi (a==b)
► "Panic mode" recovery will be taken that delete successive characters from the remaining input until the lexical analyzer can
find a well-formed token.
► Other possible error-recovery actions are:
Deleting an extraneous character e.g printff-> printf
Inserting a missing character in to the remaining input e.g prinf->insert t = printf
Replacing an incorrect character by a correct character e.g. frintf-> printf
Transposing two adjacent characters. e.g. rpintf-> printf
36 | P a g e
Lex Specification
► A Lex program consists of three parts:
{ definitions } %%
{ rules }
%%
{user subroutines }
Definitions include declarations of variables, constants, and regular definitions
Rules are statements of the form p1 {action1}p2 {action2} … pn {action}
Where pi is regular expression and actioni describes what action the lexical analyzer should take when pattern pi
matches a lexeme. Actions are written in C code.
User subroutines are auxiliary procedures needed by the actions. These can be compiled separately and loaded with the
lexical analyzer.
37 | P a g e
CHAPTER THREE
SYNTAX ANALYSIS
After completing this chapter, the students will be able to:
38 | P a g e
Figure 3. 1: CFG and RE Diagram
► It implies that every Regular Grammar is also context-free, but there exists some problems, which are beyond the scope of Regular
Grammar.
► CFG is a helpful tool in describing the syntax of programming languages.
39 | P a g e
Figure 3. 2: Position of parser in compiler model
► Parser obtains a string of token from the lexical analyzer and reports syntax error if any otherwise generates syntax tree. In this way, the
parser accomplishes two tasks,
1. parsing the code and looking for errors and
2. Generates a parse tree as the output of the phase.
40 | P a g e
► A syntax analyzer checks whether a given program satisfies the rules implied by a CFG or not.
► If it satisfies, the syntax analyzer creates a parse tree for the given program.
► A Context-Free Grammar is a quadruple that consists of terminals, non-terminals, start symbol and productions.
► A context-free grammar has four components; G= {V, €, P, S}
1. Non-terminals (V): are a set of syntactic variables that defines set of strings.
They are represented by Capital letters and they can be further derivate.
2. Terminals Symbols (€): are the basic symbols from which strings are formed, also set of tokens.
Terminals are represented by small letters and they don‟t have further derivation.
3. Production (P): is a set of production of a grammar specify the manner in which the terminals and non-terminals can be
combined to form strings.
► Each production consists of three things:-
i. a non-terminals (called left side of the production)
ii. an arrow, and
iii. a sequence of tokens with non-terminals (right side of the production)
4. Start symbols(S): is one of the non-terminals from where the production begins.
Example1
exprexpr op expr
expr(expr)
id,+,-*,/,(,) are Terminals
expr-expr expr,op,are non-terminals
exprid
expr is the start symbol
op+
op- Each line is production
41 | P a g e
op*
op/
Class activity
1. The given grammar is CFG or not
A. EaB
B. a bSa
C. SbSa
D. aba
2. Distinguish the following CFG compononts: as Production, terminals, non-terminals and start symbol.
II. S AaAb
Aa
Bb
III. EE+E
E( E*E)
E id
Notational Conventions
To avoid always having to state that "these are the terminals," "these are the non-terminals," and so on, the following notational conventions for
grammars will be used throughout the remainder of this book.
This language cannot be described by means of regular expression, because it is not regular language, but it can be described by means of CFG, as
illustrated below:
43 | P a g e
S= {Q}, which is start symbol
This grammar describes palindrome language, such as: 1001. 11100111, 00100, 1010101, 11111, etc.
44 | P a g e
Grammar: E E * E | E + E | id
Grammar: E E * E | E + E | id
String: id + id * id or a + b* c
String: id + id * id or a + b* c
E
+
id E * E
*
id id E + E id
id id
► A parse tree is the graphical representation of the structure of a sentence according to its grammar.
► Parsing is performed at the Syntax Analysis phase where a streams of tokens is taken as input from the Lexical Analysis and parser
produces the parse tree for the tokens against the syntax error.
► The concept of parsing is basically address various concepts such as; Derivation, Parse tree, Ambiguity, Left recursion.
3.3.1. Derivation
► The construction of a parse tree can be made precise by taking a derivational view, in which productions are treated as
rewriting rules.
► Beginning with the start symbol, each rewriting step replaces a nonterminal by the body of one of its productions.
► This derivational view corresponds to the top-down construction of a parse tree, but the precision afforded by derivations will
be especially helpful when bottom-up parsing.
45 | P a g e
► A derivation is basically a sequence of production rules, in order to get the input string. During parsing, we take two decisions
for some sentential form of input. Deciding the non-terminal which is to be replaced and deciding the production rules, by
which the non-terminal will be replaced.
► Derivation means to replace a non-terminal by the body of its production. It is used to find whether the string belongs to a
given grammar or not.
► There are two types of derivations are leftmost derivation and rightmost derivation.
1. Leftmost Derivation
A derivation of a string in a grammar G is a left most derivation if at every step the left most non-terminal is replaced. Here, the sentential form of
an input is scanned and replaced from left to right. Sentential form derived from left-most derivation is called a left-sentential form.
2. Rightmost Derivation
A derivation of a string in a grammar G is a right most derivation if at every step the right most non-terminal is replaced. It is all called canonical
derivation. Here, we scan and replace the input with production rules, from right to left. Sentential form derived from right-most derivation is
called right-sentential form.
EE+E
E E+E
E id
For the input string: id + id * id
The left-most derivation is:
E E*E
E E+E*E
Eid+E*E
46 | P a g e
Eid+id*E
Eid+id*id
The right-most derivation is:
EE+E
E E+E*E
EE+E*id
EE+id*id
E id+id*id
N.B:
In left-most derivation, the left-most side non-terminal is always processed first; whereas
In right-most derivation, the right-most side non-terminal is always processed first.
3.3.3. Ambiguity
A grammar that produces more than one parse tree for some sentence is said to be ambiguous grammar.
Put another way, an ambiguous grammar is one that produces more than one leftmost derivation or more than one rightmost derivation for
the same sentence.
A grammar G is said to be ambiguous if it has more than one parse tree (either left or right derivation) for at least one string.
Example: using the production rule, generates two parse trees, for the string id – id + id
48 | P a g e
E E+E E E
E E-E
E id E + E E - E
id E + E
E - E id
id id
id id
Here, two leftmost derivation for string a +a * a is possible hence, above grammar is ambiguous.
The language generated by an ambiguous grammar is known as inherently ambiguous.
There is no any method to detect and remove ambiguity automatically, it is not good.
49 | P a g e
Ambiguity can be removed by either re-writing the whole grammar without ambiguity, or by setting and following associativity and
precedence constraints.
I. Associativity
► If an operand has operators on both sides, the side on which the operator takes this operand is decided by the associativity of those
operators.
► If the operation is left-associative, then the operand will be taken by the left operator or if the operation is right-associative, the right
operator will take the operand.
► The operations like Addition, Multiplication, Subtraction, and Division are left associative.
► For example; if the expression contains: id op id op id it will be evaluated as: (id op id) op id i.e.; (id + id) +id. Operations like
Exponentiation are right associative.
II. Precedence
► If two different operators share a common operand, the precedence of operators decides which will take the operand.
► For example, 2 +3 * 4 have two different parse tree; (2+3)*4 and 2+ (3*4).
► By setting precedence among operators, this problem can easily removed. i.e.; mathematically *(multiplication) has precedence over +
(addition), so the expression 2+3*4 will always be interpreted as: 2+ (3*4).
50 | P a g e
► A grammar becomes left-recursive if it has any non-terminal „A‟ whose derivation contains „A‟ itself as the left-most symbol.
► Left-recursive grammar is considered as a problem for top-down parser.
► Top-down parsers start parsing from the start symbol, which in itself is non-terminal.
► So, when the parser encounters the same non-terminal in its derivation, it becomes hard for it to judge when to stop parsing the left non-
terminal and it goes into an infinite loop.
Example: A => Aσ |β; this is an example of immediate left recursion, where A is any non-terminal symbol and σ represents a string of non-
terminals.
► A top-down parser will first parse the A, in-turn A will produce a string consisting of A itself and the parser may go into a loop forever.
This is called left recursion problem.
A Aσ
Aσ
A σ
A Aσ Aσ
A σ
A Aσ
Aσ
A σ
51 | P a g e
3.3.5. Left Factoring
► Left factoring is a grammar of transformations in which the common parts of two productions are isolated into a single production. It is
suitable for predictive parsing.
► It is a grammar transformation that is useful for producing a grammar suitable for predictive parsing.
► When it is not clear which of two alternative productions to use to expand a non-terminal A, we can rewrite the A-productions to defer the
decision until we have seen enough of the input to make the right choice.
► If there is any production A → αβ1 | αβ2, it can be rewritten as
A → αA‟
A‟ → β1 | β2
► Consider the grammar, G: S → iEtS | iEtSeS | a
E→b
Left factored, this grammar becomes
S → iEtSS‟ | a
S‟ → eS | ɛ
E→b
A‟ → β1 | β2
► Left factoring is required because it is difficult to decide which production is to select either αβ1 | αβ2 to expand A. In left factoring, we
defer this decision by expanding A to A‟ until we have seen enough input to make the right choice.
For example, consider CFG,
52 | P a g e
S → iEtS | iEtSeS
E→b
► In this grammar, the common part is iEtS. Then after elimination of left factoring, the productions are
S → iEtSS‟
S‟ → eS | ɛ
E→b
53 | P a g e
Separating the syntactic structure of a language into lexical and non-lexical parts provides a convenient way of modularizing the front end into
two manageable-sized components.
3.5. Parsing
► Parsing is the activity of checking whether a string of symbols is in the language of some grammar, where this string is usually the stream
of tokens produced by the lexical analyzer. If the string is in the grammar, we want a parse tree, and if it is not, we hope for some kind of
error message explaining why not.
► Parsing is a technique that takes input string and produces output either a parse tree, if string is valid sentence of grammar, or an error
message indicating that string is not a valid.
► It is the process of analyzing a continuous stream of input in order to determine its grammatical structure with respect to a given formal
grammar.
► Parser is that phase of the compiler which takes tokens as input and with the help of CFG, converts it into the corresponding parse tree.
► Parser is also called Syntax Analyzer.
TYPES OF PARSING
There are two main kinds of parsing in use, named for the way they build the parse trees:-
1) Top down parsing:- A top-down parser attempts to construct a tree from the root, applying productions forward to expand non-terminals into
strings of symbols. In top down parsing parser build parse tree from top to bottom.
Example: LL Parsers
► A top down parsers starts at the root of the parse tree and grows towards leaves. At each node, the parser picks a production and tries to
match the input. However, the parser may pick the wrong production in which case it will need to backtrack. Some grammars are
backtrack- free.
54 | P a g e
► Top down parser generates parse tree for the given input string with the help of grammar productions by expanding the non-terminals, i.e.;
it starts from the start symbol and ends on the terminals. It uses leftmost derivation.
2) Bottom up parsing:- A bottom-up parser builds the tree starting with the leaves, using productions in reverse to identify strings of symbols
that can be grouped together. In bottom up parser starts from leaves and work up to the root.
Example: LR Parsers
► A bottom up parser starts at the leaves and grows toward root of the parse tree. As input is consumed, the parser encodes possibilities in an
internal state. The bottom- up parser starts in a state valid for legal first tokens. Bottom-up parsers handle a large class of grammars.
Bottom up parser generates the parse tree for the given input string with the help of grammar productions by compressing the non-
terminals, i.e.; it start from the terminals and ends in the start symbol. It uses reverse of rightmost derivation.
► In both cases the construction of derivation is directed by scanning the input sequence from left to right, one symbol at a time.
55 | P a g e
Figure 3. 5: Types of parsing
56 | P a g e
► Top-down parsing can be viewed as the problem of constructing a parse tree for the given input string, starting from the root and creating
the nodes of the parse tree in preorder (depth-first left to right).
► Equivalently, top-down parsing can be viewed as finding a leftmost derivation for an input string.
(1) The parser scans the input from left to right and identifies that the derivation is left most or right most.
(2) The parser makes use of production rules for choosing the appropriate derivation. The different parsing techniques use different approaches in
selecting the appropriate rules for derivation. And finally a parse tree is constructed.
Example: The sequence of parse trees for the input id + id * id is a top-down parse according to grammar:
ETE‟
E‟+TE‟ |ɛ
TFT‟
T‟*FT‟ |ɛ
F (E) |id
57 | P a g e
Figure 3. 7: Top-down parse for id + id * id
58 | P a g e
[Link]. Predictive Parsing
► Predictive parsing is a special case of recursive descent parsing where no backtracking is required.
► It is possible to build a non-recursive predictive parser by maintaining a stack explicitly, rather than implicitly via recursive calls.
► The key problem during predictive parsing is that of determining the production to be applied for a nonterminal in case of alternatives.
► The non-recursive parser in figure looks up the production to be applied in parsing table.
► In what follows, we shall see how the table can be constructed directly from certain grammars.
A→ab|a
The parse tree can be constructed using the following top-down approach:
59 | P a g e
Step 1:
Step 2:
Step 3:
60 | P a g e
Step 4: Now try the second alternative for A.
A left-recursive grammar can cause a recursive-descent parser to go into an infinite loop. Hence, elimination of left-recursion
must be done before parsing.
EE+T |T
61 | P a g e
TT*F |T
F(E) |id
ETE‟
E‟+TE‟ |ɛ
TFT‟
T‟*FT‟ |ɛ
F (E) |id
Now we can write the procedure for grammar as follows:
Recursive procedure
E()
begin
T()
EPRIME();
end
procedure EPRIME()
begin
62 | P a g e
if input_symbol=‟+‟ then
ADVANCE();
T();
EPRIME();
end
procedure T()
begin
F();
TPRIME();
end
procedure TPRIME()
begin
if input_symbol=‟*‟ then
ADVANCE();
F();
TPRIME();
end
procedure F()
begin
if input_symbol=‟id‟ then
ADVANCE();
else if input_symbol=‟(‟ then
63 | P a g e
ADVANCE();
E();
else if input_symbol=‟)‟ then
ADVANCE();
end
else ERROR();
Stack implementation:
64 | P a g e
[Link]. LL (1) Grammar
► Predictive parsers are those recursive descent parsers needing no backtracking.
► LL(1) is non-recursive top down parser.
65 | P a g e
1. First L indicates input is scanned from Left to Right.
2. The second L means it uses Leftmost derivation for input string.
3. 1 means it uses look-ahead of only one input symbol to predict the parsing process.
► The parsing table entries are single entries. So each location has not more than one entry. This type of grammar is called LL(1) grammar.
66 | P a g e
A parsing table and An output stream.
► Input buffer: It consists of strings to be parsed, followed by $ to indicate the end of the input string.
► Stack: It contains a sequence of grammar symbols preceded by $ to indicate the bottom of the stack. Initially, the stack contains the start
symbol on top of $.
► Parsing table: It is a two-dimensional array M[A, a], where „A‟ is a non-terminal and „a‟ is a terminal.
► Predictive parsing program:
The parser is controlled by a program that considers X, the symbol on top of stack, and a, the current input symbol.
These two symbols determine the parser action.
There are three possibilities:
1) If X = a = $, the parser halts and announces successful completion of parsing.
2) If X = a ≠ $, the parser pops X off the stack and advances the input pointer to the next input symbol.
3) If X is a non-terminal , the program consults entry M[X, a] of the parsing table M. This entry will either be an X-production of the
grammar or an error entry.
If M[X, a] = {X → UVW},the parser replaces X on top of the stack by WVU.
If M[X, a] = error, the parser calls an error recovery routine.
67 | P a g e
1) FIRST
2) FOLLOW
FIRST and FOLLOW are two functions associated with a grammar that help us fill in the entries of an M-table.
Benefits of First() and Follow()
Can be used to prove the LL(K) characteristic of a grammar
Can be used to aid in the construction of predictive parsing table.
Provides selection information for recursive descent parsers.
FIRST:
First() is a function which gives the set of terminals that begins strings derived from the production rule.
It computes the set of terminal symbols with which the RHS of the productions begin.
Rules for First( ):
1. If X is terminal, then FIRST(X) is {X}.
2. If X → € is a production, then add € to FIRST(X).
3. If X is non-terminal and X → aα is a production then add a to FIRST(X).
4. If X is non-terminal and X → Y1 Y2…Yk is a production, then place a in FIRST(X) if for some i, a is in FIRST(Yi), and € is in all of
FIRST(Y1),…,FIRST(Yi-1); that is, Y1,…. Yi-1 => € . If is in FIRST(Yj) for all j=1,2,..,k, then add € to FIRST(X).
Follow:
Follow() is a function which gives the set of terminals that can appear immediately to the right of a given symbol.
It is nothing but the set of terminal symbols of the grammar that are immediately following the Non-terminal A.
Rules for Follow( ):
1. $ is an element of follow(S), where S is start symbol and $ indicates the end of the input.
2. If X → αXY is a production, the set First(Y) is in the set Follow(X), excluding € .
3. If X → αX or X → αXY are productions and FIRST(Y) has an element €, the set Follow(X) is in the set Follow(Y)
TT*F |T
F(E) |id
ETE‟
E‟+TE‟ |ɛ
TFT‟
T‟*FT‟ |ɛ
F (E) |id
69 | P a g e
Predictive parsing Table
70 | P a g e
3.5.2. Bottom up parsing
Bottom-up parser builds a derivation by working from the input sentence back towards the start symbol S. Right most derivation in
reverse order is done in bottom-up parsing.
Top-down Bottom-up
71 | P a g e
1. Construct tree from root to leaves 1. Construct tree from leaves to root
72 | P a g e
S aAcBe SaAcBe
AAb/b aAcde
Bd aAbcde
SaABe
AAbc/b
Bd
1/p: abbcde
aABe
aAde Since ( ) Bd
aAbcde Since ( ) AAbc
abbcde Since ( ) Ab
[Link]. Handles
► Always making progress by replacing a substring with LHS of a matching production will
not lead to the goal/start symbol.
For example:
abbcde
aAbcde A→b
aAAcde A→b
Struck
► Informally, A Handle of a string is a substring that matches the right side of a production,
and whose reduction to the non-terminal on the left side of the production represents one step
along the reverse of a right most derivation. If the grammar is unambiguous, every right
sentential form has exactly one handle. More formally, A handle is a production A→ and a
position in the current right-sentential form such that:
SA/
73 | P a g e
For example grammar, if current right-sentential form is
a/Abcde
Then the handle is A→Ab at the marked position. „a‟ never contains non-terminals.
HANDLE PRUNING:
Keep removing handles, replacing them with corresponding LHS of production, until we reach S.
Example:
E→E+E/E*E/(E)/id
Right-sentential form Handle Reducing production
a+b*c a E→id
E+b*c b E→id
E+E*C C E→id
E+E*E E*E E→E*E
E+E E+E E→E+E
E
The grammar is ambiguous, so there are actually two handles at next-to-last step. We can use
parser-generators that compute the handles for us.
Possible Conflicts:
74 | P a g e
2. Reduce-reduce: Two or more distinct reduce actions are possible in the same state.
(Which production should we reduce with 2).
75 | P a g e
[Link]. Operator – Precedence Parsing
► Precedence/ Operator grammar: The grammars having the property:
1. No production right side is should contain .
2. No production sight side should contain two adjacent non-terminals. Is called an
operator grammar.
► Operator – precedence parsing has three disjoint precedence relations, between certain pairs
of terminals. These precedence relations guide the selection of handles and have the
following meanings:
Relation Meaning
a<.b „a‟ yields precedence to „b‟.
a=b „a‟ has the same precedence „b‟
a.>b „a‟ takes precedence over „b‟.
► Operator precedence parsing has a number of disadvantages:
1. It is hard to handle tokens like the minus sign, which has two different
precedence.
2. Only a small class of grammars can be parsed.
3. The relationship between a grammar for the language being parsed and the
operator- precedence parser itself is tenuous, one cannot always be sure the parser
accepts exactly the desired language.
Disadvantages:
1. L(G) L(parser)
2. error detection
3. usage is limited
4. They are easy to analyze manually Example:
Grammar: E→EAE|(E)|-E/id
A→+|-|*|/|
Input string: id+id*id
The operator – precedence relations are:
Id + * $
Id .> .> .>
76 | P a g e
+ <. .> <. .>
* <. .> .> .>
$ <. <. <.
Solution: This is not operator grammar, so first reduce it to operator grammar form, by
eliminating adjacent non-terminals.
Operator grammar is: E→E+E|E-E|E*E|E/E|EE|(E)|-E|id
The input string with precedence relations interested is: $<.id.>+<.id.> *<.id.> $
Input string Precedence relations inserted Action
id+id*id $<.id.>+<.id.>*<.id.>$
E+id*id $+<.id.>*<.id.>$ E→id
E+E*id $+*<.id.>$ E→id
E+E*E $+*$
E+E*E $<.+<.*.>$ E→E*E
E+E $<.+$
E+E $<.+.>$ E→E+E
E $$ Accepted
77 | P a g e
compile time error can be classified into four major parts.
1. Lexical Error
Lexical errors can be detected during lexical analysis phase.
Typical lexical phase errors are:-
Spelling errors
Exceeding length of identifier or numeric constants
Appearance of illegal characters
Example:
fi ()
In the above code „fi‟ cannot be recognized as misspelling of keyword if rather lexical analyzer will
understand that it is an identifier and will return it as its valid identifier. Thus misspelling causes errors in
token formation.
2. Syntax Error
Syntax error appear during syntax analysis phase of compiler.
Typical syntax phase errors are:-
Errors in structure
Missing operators
Unbalanced parenthesis or missing parenthesis
The parser demands for tokens from lexical analyzer and if the tokens do not satisfy the grammatical rules
of programming language then the syntactical errors get raised.
Example:
3. Semantic Error
Semantic error detected during semantic analysis phase.
Typical semantic phase errors are:-
Incompatible types of operands
Undeclared variable
78 | P a g e
Not matching of actual argument with formal argument
Example:
4. Logical Error
It is next to impossible for the compilers to handle the logical errors. Infinite loops is the example of
logical error. In these type of errors code is correct syntactically but it does not operate as indeed.
A good compiler have following goals for error handling:
Detect the presence of errors and produce, “meaningful” diagnostics.
To recover quickly enough to be able to detect subsequent errors.
Error handling components should not significantly slow down the compilation of syntactically
correct programs.
79 | P a g e
CHAPTER FOUR
SYNTAX DIRECTED TRANSLATION
Introduction
In this chapter four, the overview of SDT have been studied, the difference between SDT versus
SDD, understanding Attribute grammar, design the evaluation orders for SDD, and how to
construct the syntax tree are well studied
Define the term SDT, and identify the difference between SDT versus SDD
understanding Attribute grammar
design the evaluation orders for SDD
Construct the syntax tree
Advantages of SDT
♦ Clarity: the association of semantic actions with grammar rules provides a clear
representation of the translation process, enhancing the understanding of the
compiler‟s behavior.
♦ Modularity: the modular nature of SDT allows for easy addition or modification of
language constructs, making it adaptable to changes in programming languages.
♦ Efficiency: by integrating syntax analysis with semantic actions, SDT facilitates
efficient translation of source code into executable forms.
In summary, Syntax Directed Translation is a fundamental technique in compiler design that
effectively bridges the gap between syntax and semantics, enabling the accurate and efficient
translation of programming languages. It plays a crucial role in the development of compilers
and an interpreter, ensuring that source code is transformed correctly into executable code.
81 | P a g e
4.2. Syntax Directed Definitions
Syntax Directed Definition is a context free grammar with semantic rules attached to the
productions.
The semantic rules define values for attributes associated with the non-terminal symbols of the
production.
SDD is a CFG which consists of attributes and rules.
1. Attributes:- They are associated with grammar symbols.
2. Rules: - They are associated with productions.
An attribute has a name and an associated value: a string, a number, a type, a memory location, an
assigned register.
For example, if X is grammar symbol and „b‟ is one of its attributes, then we write X.b denotes the
value of „b‟ at a particular parse tree node labeled as „X‟.
If we implement the nodes of the parse tree by records or objects, then the attributes of X can be
implemented by data fields in the records that represent the nodes for X.
The attributes are evaluated by the semantic rules attached to the productions.
Example: PRODUCTION SEMANTIC RULE
This production has two non-terminals, E and T; the subscript in distinguishes the occurrence of E
in the production body from the occurrence of E as the head.
Both E and T have a string-valued attribute code.
The semantic rule specifies that the string [Link] is formed by concatenating .code , [Link], and
the character ‘+’.
While the rule makes it explicit that the translation of E is built up from the translations of , T,
and ‘+’, it may be inefficient to implement the translation directly by manipulating strings.
SDDs are highly readable and give high-level specifications for translations. But they hide many
implementation details.
For example, they do not specify order of evaluation of semantic actions.
A synthesized attribute at node N is defined only in terms of attribute values at the children of N
and at N itself.
An attribute is synthesized if all its dependencies point from child to parent in the parse-tree.
The value of synthesized attribute at a node is computed from the values of attributes at the
children of that node in the parse tree.
Synthesized attribute for a non terminal S at the parse tree with node N is defined by a semantic
rule with production at N.
Here [Link] is synthesized attribute. Let us see another fig to make the concept
Inherited Attribute
84 | P a g e
An inherited attribute for a non-terminal B at a parse-tree node N is defined by a semantic rule
associated with the production at the parent of N.
Note that the production must have B as a symbol in its body.
An inherited attribute at node N is defined only in terms of attribute values at N's parent, N
itself, and N's siblings.
The value of inherited attribute is computed from the values of attributes at the siblings and
parent of that node.
Inherited attribute for non-terminal A at the parse tree with node N is defined by a semantic rule
associated with the production at the parent node N.
The following example shows the SDD for a simple desk calculator, „n‟ is end marker.
♦ The rule for production 1, L En , sets [Link] to [Link] which we shall see is the numerical
value of the entire expression.
♦ Production 2, E E1 T , also has one rule, which computes the val attribute for the head
E as the sum of the values at and T. At any pare-tree node N labeled E, the value of val for
E is the sum of the values of val at the children of node N labeled E and T.
♦ Production 3, E T , has a single rule that defines the values of val for E to be the same
as the values of val at the child for T.
♦ Production 4, is similar to the second production; its rule multiplies the values at the children
instead of adding them.
♦ The rules for production 5 and 6 copy values at a child, like that for the third production.
85 | P a g e
♦ Production 7 gives [Link] the value of a digit, that is, the numerical value of the token digit
that the lexical analyzer returned.
♦ Symbols E, T and F are associated with a synthesized attribute val.
♦ The token digit has a synthesized attribute lexval (it is assumed that it is evaluated by the
lexical analyzer).
DTL [Link]=[Link]
Tint [Link]=integer
Treal [Link]=real
LL1id L1in=[Link], add type ([Link], [Link])
Lid add type([Link], [Link])
♦ Symbol T is associated with a synthesized attribute type.
♦ Symbol L is associated with an inherited attribute in.
86 | P a g e
Inherited attributes, which must be passed down from parent nodes to children nodes of
the abstract syntax tree during the semantic analysis, pose a problem for bottom-up
parsing because in bottom-up parsing, the parent nodes of the abstract syntax tree are
created after creation of all of their children.
Attribute evaluation in S-attributed grammars can be incorporated conveniently in both
top-down parsing and bottom-up parsing .
88 | P a g e
The corresponding annotated parse tree shown below, for the string 3*5+4n.
89 | P a g e
CHAPTER FIVE
TYPE CHECKING
Introduction
In this chapter, introduction of Type checking in compiler design concepts have been studied,
and identify the types of conversion are well studied.
90 | P a g e
Type checking – may occur either at compile time (static check) or run time (dynamic
check).
91 | P a g e
Type checking is a crucial process in compiler design that ensures the correctness of a
program by verifying that the types of all variables, expressions, and functions conform
to the rules of the programming language.
Static vs. Dynamic Typing Checking
Dynamically-typed variable can hold values of any type (e.g.; integers, strings, or arrays).
Operations on dynamically-typed variables are checked at runtime.
In a dynamically-typed program all( or most) variables are dynamically typed.
Statically-typed variables are declared to hold values of a specific type.
When using static typing, a program is type checked before running it.
Type checking verifies that operations on a value are valid for the type of the value.
For example, trying to add a string to an integer results in a type check error in a
statically-typed program.
In a dynamically-typed program the error would be caught only at a runtime, and only
when that particular operation would be performed.
Static Typing has the following main benefits:
Reduced CPU usage since types don‟t need to be checked at runtime.
Reduced memory usage since types don‟t need to be stored at runtime.
Other type-directed optimizations
Discovery of errors as early as possible.
Dynamic typing has a different, complementary set of benefits:
Easier to run and test since there are practically no compile-time or link-time
errors.
Programs are not limited by the expressiveness of the type system of the
language.
Allows for implicit open recursion, late binding dynamic scope, and duck typing.
Programs are simpler since we don‟t have to worry about types.
Benefits of Type Checking
Type checking offers several benefits:
Error Detection: Catches type-related errors early, improving program reliability.
Optimization: Allows for compiler optimizations since types are known at
compile time.
92 | P a g e
Safety: ensure that operations are performed on compatible data types, preventing
that runtime errors
#include <stdio.h>
int main() {
93 | P a g e
int number = 35.88;
printf("%d", number);
return 0;
}
// Output: 35
► This type of conversion is known as implicit type conversion. In C, there are two types of
type conversion:
1. Implicit Conversion
2. Explicit Conversion
#include<stdio.h>
int main() {
// create a double variable
double value = 4150.12;
printf("Double Value: %.2lf\n", value);
// convert double value to integer
int number = value;
printf("Integer Value: %d", number);
94 | P a g e
return 0;
}
//Outputs
Double Value: 4150.12
The above example has a double variable with a value 4150.12. Notice that we have assigned the
double value to an integer variable.
Since the conversion is happening automatically, this type of conversion is called implicit type
conversion.
#include<stdio.h>
int main() {
// character variable
char alphabet = 'a';
printf("Character Value: %c\n", alphabet);
return 0;
}
Output
Character Value: a
Integer Value: 97
95 | P a g e
The code above has created a character variable alphabet with the value 'a'. Notice that we are
assigning alphabet to an integer variable.
Here, the C compiler automatically converts the character 'a' to integer 97. This is because, in C
programming, characters are internally stored as integer values known as ASCII Values.
#include<stdio.h>
int main() {
// create an integer variable
int number = 35;
printf("Integer Value: %d\n", number);
// explicit type conversion
double value = (double) number;
printf("Double Value: %.2lf", value);
return 0;
}
//Output
Integer Value: 35
Double Value: 35.00
We have created an integer variable named number with the value 35 in the above program.
Notice the code,
// explicit type conversion
96 | P a g e
double value = (double) number;
Here,
(double) - represents the data type to which number is to be converted
number - value that is to be converted to double type
Type coercion is the automatic conversion of a datum from one data type to another within
an expression.
Coercion occurs because the datum is stored as one data type but it context requires a
different data type.
E.g.; double x=1;
In this statement, the constant 1 is an integer but its context requires a double value.
Many programming languages, including java, allow type coercion as a convenience to the
programmer.
1. double average, sum;
2. int count;
3. average= sum/count;
In line 3 the value of the count is automatically converted to double before executing.
Type Systems
It is a collection rules for assigning type expressions to the various part of the program.
It specified using syntax directed definitions.
A type checker implements a type system.
97 | P a g e
CHAPTER SIX
INTERMEDIATE LANGUAGES
Introduction
In this chapter, Overview of Intermediate Language have been studied, understanding of kinds of
intermediate language and kinds of three address statement, how to represent the three address code in
flow control statement, back patching and procedure call are well studied.
If the compiler directly translates source code into the machine code without generating
intermediate code then a full native compiler is required for each new machine.
98 | P a g e
The intermediate code keeps the analysis portions same for all the compilers that's why it
doesn't need a full compiler for every unique machine.
Intermediate code generator receives input from its predecessor phase and semantic
analyzer phase. It takes input in the form of an annotated syntax tree.
Using the intermediate code, the second phase of the compiler synthesis phase is
changed according to the target machine.
There are three kinds of intermediate representations, they are
1. Syntax trees
2. Postfix notations
3. Three address code
Syntax trees
A syntax tree depicts the natural hierarchical structure of a source program.
In the parse tree, most of the leaf nodes are single child to their parent nodes.
In the syntax tree, we can eliminate this extra information.
Syntax tree is a variant of parse tree.
In the syntax tree, interior nodes are operators and leaves are operands.
Syntax tree is usually used when represent a program in a tree structure.
A sentence id + id * id would have the following syntax tree:
99 | P a g e
Abstract syntax trees are important data structures in a compiler. It contains the least
unnecessary information.
Abstract syntax trees are more compact than a parse tree and can be easily used by a
compiler.
Abstract syntax tree can be represented as:
Postfix notation
Postfix notation is a linearized representation of a syntax tree; it is a list of the nodes of
the tree in which a node appears immediately after its children.
It is the useful form of intermediate code if the given language is expressions.
100 | P a g e
In the postfix notation, any expression can be written unambiguously without
parentheses.
The ordinary (infix) way of writing the sum of x & y is with operator in the middle: x *
y. But in the postfix notation, we place the operator at the right end as xy *.
In postfix notation, the operator follows the operand.
The postfix notation for the syntax tree is abc uminus c*b uminus c*+assign
Three-Address Code
In three-address code, the given expression is broken down into several separate
instructions. These instructions can easily translate into assembly language.
Each three address code instruction has at most three operands. It is a combination of
assignment and a binary operator.
x : y op z
Three address code is a sequence of statements of the general form where x, y, and z
are names, constants, or compiler-generated temporaries;
op stands for any operator, such as a fixed or floating-point arithmetic operator, or a
logical operator on boolean-valued data.
The reason for the term "three-address code" is that each statement usually contains three
addresses, two for the operands and one for the result.
Example 1:
x y*z
t1: y * z
t 2 : x t1
101 | P a g e
6.2. Implementation of Three-address Statements
A three address statement is an abstract form of intermediate code.
There are three types of representations for three address code:-
1. Quadruples
2. Triples
3. Indirect Triples
6.2.1. Quadruples
The quadruples have four fields to implement the three address code. The field of
quadruples contains the name of the operator, the first source operand, the second source
operand and the result respectively.
A quadruple is a record structure with four fields, which we call op, arg1, arg2, and
result. The op field contains an internal code for the operator.
The three-address statement x: = y op z is represented by placing y in arg 1, z in arg2, and
x in result.
Statements with unary operators like x: = -y or x: = y does not use arg2.
Quadruple for the statement a: = b* - c + b* -c is
Op Arg1 Arg2 result
(0) Uminus c t1
(1) * b t1 t2
(2) Uminus c t3
(3) * b t3 t4
(4) + t2 t4 t5
(5) := t5 a
6.2.2. Triples
The triples have three fields to implement the three address code. The field of triples
contains the name of the operator, the first source operand and the second source
operand.
102 | P a g e
Three-address statements can be represented by records with only three fields: op,
arg1 and arg2. Since three fields are used, this intermediate code format is known as
triples.
The field‟s arg1 and arg2, for the arguments of op, are either pointers to the symbol
table or pointers into the triple structure.
To avoid entering temporary names into the symbol table. We might refer to a
temporary value by the position of the statement that computes it.
Op Arg1 Arg2
(0) Uminus c
(1) * b (0)
(2) Uminus c
(3) * b (2)
(4) + (1) (3)
(5) Assign a (4)
Statement
(0) (14)
(1) (15)
(2) (16)
(3) (17)
(4) (18)
(5) (19)
103 | P a g e
Op Arg1 Arg2
(14) Uminus c
(15) * b (14)
(16) Uminus c
(17) * b (16)
(18) + (15) (17)
(19) Assign a (18)
104 | P a g e
Figure 6. 3: Syntax-directed definition for flow-of-control statements
If-Then
In translating the if-then statement S if E then S1, a new label [Link] is created and attached to
the first three-address instruction generated for the statement as in Fig, 8.22(a).
The code for E generates a jump to [Link] if E is true and a jump to [Link] if E is false. We
therefore set [Link] to [Link].
If-then-else
105 | P a g e
In translating the if-then-else statement S if E then S1 else S2. The code for the Boolean
expression E has jumps out of it to the first instruction of the code for S1 if E is true, and to
the first instruction of the code for S2 if E is false, as illustrated in Fig. 8.22(b).
While - do statement
In translating Swhile E do S1, A new label [Link] is created and attached to the first
instruction generated for E. Another new label [Link] is attached to the first instruction for
S1.
The code for E generates a jump to this label if E is true, a jump to [Link] if E is false and set
[Link] to be [Link]. After the code for S1 we place the instruction goto [Link], which
causes a jump back to the beginning of the code for the boolean expression,
Example :
106 | P a g e
Figure 6. 4: SDD for if-then, if-then-else, and while-do statements
6.4. BACKPATCHING
Backpatching is a technique to solve the problem of replacing symbolic names into goto
statements by the actual target addresses.
This problem comes up because if some languages do not allow symbolic names in the
branches.
Backpatching can be used to do generate code for Boolean expression and control
statements in one pass.
Leaving the labels as empty and filling them later.
During one pass, the following steps are executed
1. Construct the syntax tree.
2. Generate jumps with unspecified targets.
3. Keep a list of such statements.
4. Subsequently fill in the labels.
backpatch(p, i ) - inserts i as the target label for each of the statements on the list
pointed to by p.
Example: consider the expression a<b or c<d and e<f.
The six statements generated so far are thus:
107 | P a g e
100: if a<b goto_
101: goto_
102: if c<d goto_
103: goto_
104: if e<f goto_
105: goto_
The corresponding semantic action calls Backpatch ({102), 104) where (102) as argument
denotes a pointer to the list containing only 102, that list being the one pointed to by
[Link].
This call to backpatch fills in 104 in statement 102. The six statements generated so far are thus:
100: if a<b goto_
101: goto_
102: if c<d goto_
103: goto_
104: if e<f goto_
105: goto_
6.5. Procedure Calls
Procedures, also known as functions or subroutines, are fundamental building blocks in
programming languages. In compiler design, handling procedure calls efficiently and correctly is
crucial.
Steps during a Procedure Call:
1. Lexical Analysis and Parsing: The compiler identifies the procedure name and its arguments
during these initial stages.
2. Semantic Analysis: Type checking and argument passing mechanisms are verified to ensure
compatibility between actual parameters (arguments in the call) and formal parameters
(arguments defined in the procedure).
3. Code Generation: The compiler translates the procedure call into machine code instructions.
This involves:
Activation Record Creation: A block of memory is allocated to store local variables,
parameters, and the return address for the called procedure.
108 | P a g e
Argument Passing: The values of actual parameters are passed to the corresponding
formal parameters in the called procedure. Different mechanisms like pass-by-value or
pass-by-reference can be used.
Control Flow Transfer: The program execution jumps to the beginning of the called
procedure's code.
4. Procedure Execution: The called procedure executes its instructions using the allocated
activation record for its local data.
5. Procedure Return:
The called procedure performs any necessary actions before returning.
The return address stored in the activation record is used to transfer control back to the
calling procedure's continuation point.
The activation record of the called procedure might be deallocated depending on the
language and memory management strategy.
Challenges in Handling Procedure Calls:
Recursion Handling: When a procedure calls itself (recursion), the compiler needs to manage
the activation record stack effectively to avoid stack overflow.
Tail Recursion Optimization: Some compilers can optimize tail-recursive calls to improve
efficiency.
Parameter Passing Mechanisms: Different languages have different mechanisms for passing
arguments, and the compiler needs to generate code accordingly.
Example:
F(x1, x2, x3,…,xn)
Param x1
Param x2
Param x3
Call f,n Where f name of procedure, n number of parameter
Example: n=f(a[i]); a array of interger , f function from integers three address code is:
1. t1:=i*5
2. t2:=a[t1]
3. param t2
4. t3:=call f,1
5. n:=t3
109 | P a g e
CHAPTER SEVEN
RUN TIME- ENVIRONMENTS
Introduction
In this chapter, Overview of Run time- Environment have been studied , definition of Symbol
table and hash table and their uses , representation of scope information are well studied
110 | P a g e
The information is collected by the analysis phases of the compiler and is used by the
synthesis phases of the compiler to generate code.
It is used by the compiler to achieve compile-time efficiency.
It is used by various phases of the compiler as follows:-
1) Lexical Analysis: Creates new table entries in the table, for example like entries
about tokens.
2) Syntax Analysis: Adds information regarding attribute type, scope, dimension, line
of reference, use, etc. in the table.
3) Semantic Analysis: Uses available information in the table to check for semantics
i.e. to verify that expressions and assignments are semantically correct (type
checking) and update it accordingly.
4) Intermediate Code generation: Refers symbol table for knowing how much and
what type of run-time is allocated and table helps in adding temporary variable
information.
5) Code Optimization: Uses information present in the symbol table for machine-
dependent optimization.
6) Target Code generation: Generates code by using address information of identifier
present in the table.
► A symbol table may serve the following purposes depending upon the language in hand:
To store the names of all entities in a structured form at one place.
To verify if a variable has been declared.
To implement type checking, by verifying assignments and expressions in the source
code are semantically correct.
To determine the scope of a name (scope resolution).
► The symbol table is defined as the set of Name and Value pairs.
► A compiler uses a symbol table to keep track of scope and binding information about names.
The symbol table is searched every time a name is encountered in the source text. Changes to
the table occur if a new name or new information about an existing name is discovered.
► The two most important functions of the symbol table in a compiler.
► First, they store useful information about various symbols from the source code program for
later use during code generation
111 | P a g e
► Second, they provide the type checking mechanism that determines the semantic correctness
of a program.
► A symbol-table mechanism must allow us to add new entries and find existing entries
efficiently. The two symbol-table mechanism presented in this section are linear lists and
hash tables.
► A linear list is the simplest to implement. But its performance is poor when the number of
entries is large.
► Hashing schemes provide better performance for somewhat greater programming effort and
space overhead.
112 | P a g e
Variable names and constants
Procedure and function names
Literal constants and strings
Compiler generated temporaries
Labels in source languages
Information used by the compiler from Symbol Table:
► If a compiler is to handle a small amount of data, then the symbol table can be implemented
as an unordered list, which is easy to code, but it is only suitable for small tables only.
► Among all, symbol tables are mostly implemented as hash tables, where the source code
symbol itself is treated as a key for the hash function and the return value is the information
about the symbol.
► The symbol table can be implemented in the unordered list if the compiler is used to handle
the small amount of data. Following are commonly used data structures for implementing
symbol table:-
List
We use a single array or equivalently several arrays, to store names and their associated
information; new names are added to the list in the order in which they are encountered. The
position of the end of the array is marked by the pointer available, pointing to where the next
symbol table entry will go. The search for a name proceeds backwards from the end of the array
to the beginning. When the name is located the associated information can be found in the words
following next.
► The simplest and easiest to implement data structure for a symbol table is a linear list of
records. New names are added to the list in the order in which they are encountered.
► The position of the end of the array is marked by the pointer available.
► The search for a name proceeds backwards from the end of the array to the beginning. When
the name is located, the associated information can be found in the words following next.
► If we reach the beginning of the array without finding the name, a fault occurs - an expected
name is not in the table.
The average search for the name in the symbol table is N/2 comparisons.
Advantage
It takes less space.
Insertion to the table is simple.
115 | P a g e
Linked List
This implementation is using a linked list. A link field is added to each record.
Searching of names is done in order pointed by the link of the link field.
A pointer “First” is maintained to point to the first record of the symbol table.
Insertion is fast O(1), but lookup is slow for large tables – O(n) on average
7.3. Hash Table
In hashing scheme, two tables are maintained – a hash table and symbol table and are the
most commonly used method to implement symbol tables.
A hash table is an array with an index range: 0 to table size – 1. These entries are pointers
pointing to the names of the symbol table.
To search for a name we use a hash function that will result in an integer between 0 to table
size – 1.
Insertion and lookup can be made very fast – O(1).
The advantage is quick to search is possible and the disadvantage is that hashing is
complicated to implement.
The basic hashing scheme consists of two parts
116 | P a g e
records built on that hash index, if the name is not present in that list we create a record for name
and insert it at the head of the list built on that hash index.
The retrieval of the information associated with the name is done as follows.
First, the hash value of the name is obtained and the list button on this hash value is searched
for getting the information about the name.
Binary Search Tree
Another approach to implementing a symbol table is to use a binary search tree i.e. we add
two link fields i.e. left and right child.
All names are created as child of the root node that always follows the property of the binary
search tree.
Insertion and lookup are O(log2 n) on average.
Advantages of Symbol Table
1) The efficiency of a program can be increased by using symbol tables, which give quick and
simple access to crucial data such as variable and function names, data kinds, and memory
locations.
2) Better coding structure Symbol tables can be used to organize and simplify code, making it
simpler to comprehend, discover, and correct problems.
3) Faster code execution: By offering quick access to information like memory addresses,
symbol tables can be utilized to optimize code execution by lowering the number of memory
accesses required during execution.
4) Symbol tables can be used to increase the portability of code by offering a standardized
method of storing and retrieving data, which can make it simpler to migrate code between
other systems or programming languages.
5) Improved code reuse: By offering a standardized method of storing and accessing
information, symbol tables can be utilized to increase the reuse of code across multiple
projects.
6) Symbol tables can be used to facilitate easy access to and examination of a program‟s state
during execution, enhancing debugging by making it simpler to identify and correct mistakes.
Disadvantages of Symbol Table
117 | P a g e
1) Increased memory consumption: Systems with low memory resources may suffer from
symbol tables‟ high memory requirements.
2) Increased processing time: The creation and processing of symbol tables can take a long
time, which can be problematic in systems with constrained processing power.
3) Complexity: Developers who are not familiar with compiler design may find symbol tables
difficult to construct and maintain.
4) Limited scalability: Symbol tables may not be appropriate for large-scale projects or
applications that require o the management of enormous amounts of data due to their limited
scalability.
5) Upkeep: Maintaining and updating symbol tables on a regular basis can be time and resource
consuming.
6) Limited functionality: It‟s possible that symbol tables don‟t offer all the features a
developer needs, and therefore more tools or libraries will be needed to round out their
capabilities.
Applications of Symbol Table
1) Resolution of variable and function names: Symbol tables are used to identify the data
types and memory locations of variables and functions as well as to resolve their names.
2) Resolution of scope issues: To resolve naming conflicts and ascertain the range of variables
and functions, symbol tables are utilized.
3) Symbol tables, which offer quick access to information such as memory locations, are used
to optimize code execution.
4) Code generation: By giving details like memory locations and data kinds, symbol tables are
utilized to create machine code from source code.
5) Error checking and code debugging: By supplying details about the status of a program
during execution, symbol tables are used to check for faults and debug code.
6) Code organization and documentation: By supplying details about a program‟s structure,
symbol tables can be used to organize code and make it simpler to understand.
7.4. Representing Scope Information
► In the source program, every name possesses a region of validity, called the scope of that
name.
The rules in a block-structured language are as follows:
118 | P a g e
1) If a name declared within block B then it will be valid only within B.
2) If B1 block is nested within B2 then the name that is valid for block B2 is also valid for B1
unless the name's identifier is re-declared in B1.
These scope rules need a more complicated organization of symbol table than a list of
associations between names and attributes.
Tables are organized into stack and each table contains the list of names and their
associated attributes.
Whenever a new block is entered then a new table is entered onto the stack. The new
table holds the name that is declared as local to this block.
When the declaration is compiled then the table is searched for a name.
If the name is not found in the table then the new name is inserted.
When the name's reference is translated then each table is searched, starting from the
each table on the stack.
For example:
int x;
void f (int m) {
float x, y;
{
int i, j;
int u, v;
}
}
int g (int n)
{
bool t;
}
119 | P a g e
Figure 7. 1: Representing scope information
120 | P a g e
CHAPTER EIGHT
CODE GENERATION AND OPTIMIZATION
Introduction
In this chapter, Overview of Code Generation and Optimization have been studied , how to
represent DAG , understanding of flow blocks and loops, and peephole optimization Techniques
and characteristics are also well studied
► The input to the code generation consists of the intermediate representation of the source
program produced by front end, together with information in the symbol table to
determine run-time addresses of the data objects denoted by the names in the
intermediate representation.
► There are several choices for the Intermediate language representation that can be:-
♦ Linear representation such as postfix notation
♦ Three address representation such as quadruples
♦ Virtual machine representation such as stack machine code
♦ Graphical representations such as syntax trees and DAGs.
► Prior to code generation, the front end must be scanned, parsed and translated into
intermediate representation along with necessary type checking.
► Therefore, input to code generation is assumed to be error-free.
► The code generation phase can therefore proceed on the assumption that its input is free
of errors.
► In some compilers, this kind of semantic checking is done together with code generation.
Target Programs
► The output of the code generator is the target program, like the intermediate code, this
output may be take on a variety of forms:-
a) Absolute machine language:- It can be placed in a fixed memory location and can be
executed immediately.
122 | P a g e
c) Assembly language:- Code generation is made easier. Producing an AL program as
output makes the process of code generation somewhat easier. We use assembly code
as the target language for readability.
Memory management
► Names in the source program are mapped to addresses of data objects in run-time
memory by the front end and code generator.
► It makes use of symbol table, that is, a name in a three-address statement refers to a
symbol-table entry for the name.
Instruction Selection
► The uniformity and completeness of the instruction set are important factors.
► If the target machine does not support each data type in a uniform manner, then each
exception to the general rule requires special handling.
► Instruction speeds and machine idioms are other important factors when efficiency of
target program is considered. That means; if we do not care about the efficiency of the
target program, instruction selection is straightforward.
► The quality of the generated code is determined by its speed and size.
► For example, every three-address statement of the form x: =y+z where x, y, and z are
statically allocated, can be translated into the code sequence
ADD z, RO /* add z to RO */
123 | P a g e
► Unfortunately, this kind of statement-by-statement code generation often produces poor
code. For example, the sequence of statements
Register Allocation
► Instructions involving register operands are usually shorter and faster than those
involving operands in memory.
► Therefore, efficient utilization of registers is particularly important in generating good
code.
► The use of registers is often subdivided into two sub problems:
♦ During register allocation, we select the set of variables that will reside in
registers at a point in the program is selected.
♦ During a subsequent register assignment phase, we pick the specific register that
a variable will reside in.
Evaluation Order
► The order in which computations are performed can affect the efficiency of the target
code.
► Some computation orders require fewer registers to hold intermediate results than others.
125 | P a g e
t1: a * a
t 2 : a * b
t 3 : 2 * t 2
t 4 : t1 t 3
t 5 : b * b
t 6 : t 4 t 5
► A name in a basic block is said to be live at a given point if its value is used after that
point in the program or in basic block.
Output: A list of basic blocks with each three-address statement in exactly one block
Method:
1. We first determine the set of leaders, the first statements of basic blocks.
the rules use are of the following:
a. The first statement is a leader.
b. Any statement that is the target of a conditional or unconditional
goto is a leader.
c. Any statement that immediately follows a goto or conditional goto
statement is a leader.
2. For each leader, its basic block consists of the leader and all statements up
to but not including the next leader or the end of the program.
► A basic block computes a set of expressions. These expressions are the values of the
names live on exit from the block.
126 | P a g e
► Two basic blocks are said to be equivalent if they compute the same set of expressions.
► A number of transformations can be applied to a basic block without changing the set of
expressions computed by the block. This is for optimizing the code.
► There are two important classes of transformations that can be applied to basic blocks;
2) Algebraic Transformations
a : b c
a:=b+c
b : a d b:=a-d
c : b c c:=b+c
d :ad d:=b
► Since the second and fourth expressions compute the same expression, namely, b + c – d,
and hence this basic block may be transformed into the equivalent block.
► Note that although the first and third statements in the above appear to have the same
expression on the right, the second statement redefines b. therefore, the value of b in the
third statement is different from the value of b in the first, and the first and third
statements do not compute the same expression.
127 | P a g e
Dead-code elimination
Suppose x is dead, that is, never subsequently used, at the point where the statement x : =
y + z appears in a basic block.
Then this statement may be safely removed without changing the value of the basic
block.
Interchange of statements
t1: b c
t 2 : x y
► Then we can interchange the two statements in the basic block when the value of t1 does
not affect the value of t2.
4. Algebraic Transformations
Examples:
► The nodes of the flow graph are basic blocks. It has a distinguished initial; it is the block
whose leader is the first statement.
B2 immediately follows B1 in the natural order of the program, and B1 does not
end in an unconditional jump.
► E.g.; Flow graph for the vector dot product is given as follows:
129 | P a g e
Figure 8. 2:Flow graph for the vector dot product
The target of jump from last statement of B1 is the first statement B2, so there is
an edge from B1 (last statement) to B2 (first statement).
8.4.3. Loops
► For example, in the above Figure 8.2 there is one loop, consisting of block B2.
1) All nodes in the collection are strongly connected; that is, from any node in the
loop to any other, there is a path of length one or more, wholly within the loop,
and
130 | P a g e
2) The collection of nodes has a unique entry, that is, a node in the loop such that the
only way to reach a node of the loop from a node outside the loop is to first go
through the entry.
131 | P a g e
► It gives a picture of how the value computed by a statement is used in subsequent
statements.
► It provides a good way of determining common sub- expressions.
A DAG for a basic block is a directed acyclic graph with the following labels on nodes:
1) Leaves are labeled by unique identifiers, either variable names or constants.
2) Interior nodes are labeled by an operator symbol.
3) Nodes are also optionally given a sequence of identifiers for labels to store the
computed values.
8.8. The Principal Sources of Optimization
► A transformation of a program is called local if it can be performed by looking only at the
statements in a basic block; otherwise, it is called global.
► Many transformations can be performed at both the local and global level.
132 | P a g e
t 6 : 4 * i
x : a[t 6]
t 6 : 4 * i
t 7 : 4 * i x : a[t 6]
t 8 : 4 * j t 8 : 4 * j
t 9 : a[t 8] t 9 : a[t 8]
a[t 7] : t 9 a[t 6] : t 9
t10 : 4 * j a[t 8] : x
a[t10] : x
goto B2
goto B2
A) Before B) After
Copy Propagation
For example;
x=PI;
…
a=x*r*r;
► The optimization using copy propagation can be done as follows
a=PI * r * r
► Here the variable x is eliminated.
► A variable is live at a pint in a program if its value can be used subsequently; otherwise,
it is dead at that point.
► A related idea is dead or useless code, statements that compute values that never get
used.
133 | P a g e
Example:
i:=0;
if (i==1) {
a=b+5;
}
Here if statement is a dead code because this condition will never get satisfied.
Hence if statement can be eliminated.
Constant folding
Loop Optimization
134 | P a g e
► Code motion will result in the equivalent of
t=n-2
While (i<=t)
Reduction in Strength
136 | P a g e