Compiler Design Lab Manual
Compiler Design Lab Manual
&
TECHNOLOGY MEERUT
LAB MANUAL
Subject Code :( KCS-552)
Lab Name: Compiler Design Using C
(Session -2023-2024 B.Tech CSE V Semester)
1. CoverPagementioningfacultyname,designationandsubject/sessiondetails
5. Lab Plan
6. Lab Manuals
7. Sample Quizzes/Viva-questions
8. Students Lists
10. Attainment Levels and Assessment Tools(direct and indirect methods both)
Record of CO-Attainment including End Sem Marks and Course End/Exit Survey, and
11.
Actions taken / to be taken to improve attainment / academic performance
15. Evaluated Record of Lab Files ,Jobs, Drawing Sheets, Quizzes etc.
Vision of the Institute
To be an outstanding institution in the country imparting technical education, providing need based, value
based and career based programmes and producing self-reliant, self-sufficient technocrats, capable of
meeting new challenges.
To educate young aspirants in various technical fields to fulfill global requirement of human resources by
providing sustainable quality education, training and invigorating environment, also molding them into
skilled competent and socially responsible citizens who will lead the building of a powerful nation
Vision of Department
To be an excellent department that imparts value based quality education and uplifts innovative research
in the ever-changing field of technology.
Mission of Department
1. To fulfill the requirement of skilled human resources with focus on quality education.
2. To create globally competent and socially responsible technocrats by providing value
and need based training.
3. To improve Industry-Institution Interaction and encourage the innovative research
activities.
Program Educational Objectives (PEOs)
Students will have the successful careers in the field of computer science and allied
PEO 1 sectors as an innovative engineer.
Students will continue to learn and advance their careers through participation in
professional activities, attainment of professional certification and seeking advance
PEO 2
studies.
PO2 Problem analysis: Identify, formulate, review research literature, and analyze complex
engineering problems reaching substantiated conclusions using first principles of mathematics,
natural sciences, and engineering sciences.
PO3 Design/development of solutions: Design solutions for complex engineering problems and
design system components or processes that meet the specified needs with appropriate
consideration for the public health and safety, and the cultural, societal, and environmental
considerations.
PO4 Conduct investigations of complex problems: Use research-based knowledge and research
methods including design of experiments, analysis and interpretation of data, and synthesis of
the information to provide valid conclusions.
PO5 Modern tool usage: Create, select, and apply appropriate techniques, resources, and modern
engineering and IT tools including prediction and modeling to complex engineering activities
with an understanding of the limitations.
PO6 The engineer and society: Apply reasoning informed by the contextual knowledge to assess
societal, health, safety, legal and cultural issues and the consequent responsibilities relevant to
the professional engineering practice.
PO7 Environment and sustainability: Understand the impact of the professional engineering
solutions in societal and environmental contexts, and demonstrate the knowledge of, and need
for sustainable development.
PO8 Ethics: Apply ethical principles and commit to professional ethics and responsibilities and
norms of the engineering practice.
PO9 Individual and team work: Function effectively as an individual, and as a member or leader in
diverse teams, and in multidisciplinary settings.
PO11 Project management and finance: Demonstrate knowledge and understanding of the
engineering and management principles and apply these to one’s own work, as a member and
leader in a team, to manage projects and in multidisciplinary environments.
PO12 Life-long learning: Recognize the need for, and have the preparation and ability to engage in
independent and life-long learning in the broadest context of technological change.
Lab Outcomes
Subject Name: Compiler Design Lab Subject code: KCS-552
Bloom’s
At the end of course, the student will be able to understand Knowledge
Course Outcome
Level
LO1 Identify patterns, tokens & regular expressions for lexical analysis. K4
Design Lexical analyzer for given language using C and K3,K5
LO2 LEX/YACC tools
LO3 Design and analyze top down and bottom up parsers. K5
LO4 Generate the inter mediate code K3
LO5 Generate machine code from the intermediate code forms K5
Identify patterns, tokens & regular Analyze [K4]: In the context of designing a lexical
expressions for lexical analysis. analyzer, this task requires analyzing the structure of a
programming language to identify patterns, tokens, and
regular expressions that define the lexical elements of the
LO1 language. It involves breaking down the language's lexical
rules, understanding the relationships between different
lexical elements, and recognizing the patterns that
characterize valid tokens.
Design Lexical analyzer for given Apply / Synthesis [K3,K5]: Designing a lexical analyzer
language using C and requires combining knowledge of the specific programming
LEX/YACC tools language's lexical rules, understanding the Lex/Yacc tools,
and creating a coherent system that can recognize and
LO2 tokenize the input based on the language's lexical structure.
This task involves the application of knowledge and skills to
create something new (the lexical analyzer) by integrating
different components and tools.
Design and analyze top down and Synthesis [K5]: Designing parsers requires synthesizing
bottom up parsers. knowledge of language grammar, parsing algorithms, and
compiler theory to create a complete parsing system.
Additionally, analyzing the performance and characteristics
LO3 of top-down and bottom-up parsers involves synthesizing
information and evaluating their strengths and weaknesses in
different contexts.
LO4 Generate the inter mediate code Apply [K3]: Generating intermediate code requires applying
programming concepts, language syntax, and compiler
design principles to produce a representation of a program
that serves as an intermediate step between the source code
and the eventual machine code. This task involves a practical
application of knowledge and skills in the specific context of
compiler construction.
Generate machine code from the Synthesis [K5]: In the context of programming and compiler
intermediate code forms design, generating machine code from intermediate code
requires synthesizing and organizing information to create a
LO5 final executable program. It involves the application of
knowledge and skills to transform abstract representations
(intermediate code) into a concrete and functional form
(machine code).
4 Write program to find ε–closure of all states of any given NFA with ε transition.
9 Write program to find Simulate First and Follow of any given grammar.
15 Implement the back end of the compiler which takes the three address code and produces the
8086 assembly language instructions that can be assembled and run using an 8086
assembler. The target assembly instructions can be simple move, add, sub, jump etc
COURSE NAME DESCRIPTION SEM
Assembly level programming Various operations like MOV, INCR, DCR, ADD, III
and SUB. "EAX," "EBX" and "ECX" are the
variables
Lab Pre-Requisites:
Curriculum & Evaluation Scheme CS & CSE (V semester)
End
Sl.No. Subjec t Subject Periods Evaluation Scheme Semester Total Credi
t
Codes
L T P CT TA Total PS TE PE
KCS-502 3 1 0 30 20 50
Compiler Design
2 100 150 4
KCS553 50 50
9 Mini Project or Internship 0 0 2 0
Assessment*
Total 17 3 8 950
*The Mini Project or internship (4 weeks) conducted during summer break after IV semester and will be assessed during V semester.
Lesson Plan / Teaching Plan / Lecture Plan with Progress: B Tech - …. Semester : 2023-24
Course
: CD Lab KCS 552 Faculty: Ms.Sonam
Name(Code)
Topics / lectures are arranged in sequence - same - as to be taught in the class. Maintain data related to
"Date" in its hard copy.
Lab Date
Tur Referen
S. Teaching
n CO Experiment (Title) to be ce Remark
N Pedagog
Sequ (No) conducted Materia Actual s, if any
o. y Planned
ence l Delivery
No
CO Chalk
3 3 Implementation of Calculator R1 12/9/23 12/9/23
1 and Talk
using LEX and YACC
4 4 CO Develop an operator Chalk R1 19/9/23 19/9/23
2 precedence parser for a given and
language. Talk,PPT
Write program to find Chalk
CO R1 10/10/23 10/10/23
5 5 Simulate First and Follow of and Talk
2
any given grammar.
CO-wise Syllabus #
Name of the
1 B.Tech / CD 3 Theory / Lab Lab
Course / Subject
Subject / Course CD-LAB 4 Name of the Faculty Ms. Sonam
2
Code KCS-552
CO- wise Syllabus
LO1 Statement Identify patterns, tokens & regular expressions for lexical analysis.
1
Syllabus Implement a lexical analyzer for given language using C and the lexical
analyzer should ignore redundant spaces, tabs and new lines. To
implement lexical Analyzer for any arithmetic expression.
LO2 Statement Design Lexical analyzer task for given language using C and LEX
2 /YACC tools
LO3 Statement Design and analyze the top down and bottom up parsers.
3
Syllabus Write program to find Simulate First and Follow of any given grammar.
Implement a recursive descent parser for an expression. Implement a
Shift Reduce Parser for a given language.
5 LO5 Statement Generate machine code from the intermediate code forms.
Syllabus Implement the back end of the compiler which takes the three address
code and produces the 8086 assembly language instructions that can be
assembled and run using an 8086 assembler. The target assemblies
instructions can be simple move, add, sub, jump etc.
CO-PO Mapping
COURSE OUT COMES VS POS MAPPING (DETAILED; HIGH: 3; MEDIUM: 2; LOW: 1)-
Program Outcomes
Course
outcome PO1 PO2 PO3 PO4 PO5 PO6 PO7 PO8 PO9 PO10 PO11 PO1
2
LO1 - - - - - - - - -
LO2 - - - - - - - - -
LO3 - - - - - - - - -
LO4 - - - - - - - - -
LO5 - - - - - - - - - -
Average
CO-PSO Mapping
Course Outcome Program Specific Outcomes
LO1 - -
LO2 - -
LO3 - -
LO4 - -
LO5 - -
Average
This laboratory course is intended to make the students experiment on the basic techniques of compiler
construction and tools that can used to perform syntax-directed translation of a high-level programming
language into an executable code. Students will design and implement language processors in C by using
tools to automate parts of the implementation process. This will provide deeper insights into the more
advanced semantics aspects of programming languages, code generation, machine independent
optimizations, dynamic memory allocation, and object orientation.
OUTCOMES:
Upon the completion of Compiler Design practical course, the student will be able
To:
2. Understand and define the role of lexical analyzer, use of regular expression
and transition diagrams.
3. Understand and use Context free grammar, and parse tree construction.
4. Learn & use the new tools and technologies used for designing a compiler.
5 Write program to find Simulate First and Follow of any given CO2
grammar.
VALUE ADDITION:
Implement a lexical analyzer for a given language using C and the lexical analyzer should ignore
redundant spaces, tabs, and new lines.
ALGORITHAM:
SOURCE CODE:
#include <stdio.h>
#include <ctype.h>
intisWhitespace(char c) {
return (c == ' ' || c == '\t' || c == '\n');}
void lexicalAnalyzer(char input[]) {
inti = 0;
while (input[i] != '\0') {
while (isWhitespace(input[i])) {
i++; }
if (!isWhitespace(input[i])) {
printf("Token: ");
while (!isWhitespace(input[i]) && input[i] != '\0') {
printf("%c", input[i]);
i++; }
printf("\n");
} }}
intmain() {
char input[] = "This is a sample input\nwith redundant spaces and tabs.";
lexicalAnalyzer(input);
return 0;
}
Experiment 2:
OBJECTIVE:
ALGORITHAM:
SOURCE CODE:
%{ #include <stdio.h>%}
%%
[0-9]+ {
[a-zA-Z_][a-zA-Z0-9_]* {
\( {printf("OPEN_PAREN\n");}
\) {
printf("CLOSE_PAREN\n");
}%%
intmain() {
return 0;
To generate the lexer code from this Lex specification, use the following commands:
lexarithmetic.l
gcclex.yy.c -o lexer–ll
Now you can use the generated lexer executable to analyze arithmetic expressions.
OBJECTIVE:
ALGORITHAM:
Purpose: Lex helps generate lexical analyzers or scanners. A lexical analyzer breaks
down the source code into a sequence of tokens.
Usage: It reads an input file containing regular expressions and corresponding actions and
generates a C program that recognizes those patterns in an input stream and takes
specified actions when a pattern is matched.
Features:
Lex allows you to specify patterns using regular expressions.
It generates C code for a finite automaton that recognizes the specified patterns.
Purpose: Yacc helps generate parsers. A parser processes the tokens generated by the
lexical analyzer and constructs a syntax tree or an abstract syntax tree (AST).
Usage: It reads an input file containing a context-free grammar and associated actions,
and generates a C program that can parse sentences conforming to that grammar.
Features:
Yacc handles context-free grammars and generates LALR(1) parsers.
It allows you to specify actions to be taken when specific grammar rules are
recognized.
%{
#include "y.tab.h"
%}
%%
. { yyerror("Invalid character"); }
%%
intyywrap() {
return 1;
%{
#include <stdio.h>
%}
%token NUMBER
%%
program: /* empty */
expression: NUMBER
intmain() {
yyparse();
return 0;
return 0;
yacc -d calculator.y
gcclex.yy.cy.tab.c -o calculator
./calculator
Result: 7
Experiment 4:
OBJECTIVE:
ALGORITHAM:
An operator precedence parser is a type of bottom-up parser used to parse expressions in a programming
language based on the precedence and associativity of operators. It's particularly well-suited for parsing
arithmetic expressions.
SOURCE CODE:
#include<stdio.h>
#include<string.h>
char *input;
int i=0;
char lasthandle[6],stack[50],handles[][5]={")E(","E*E","E+E","i","E^E"};
//(E) becomes )E( when pushed to stack
int top=0,l;
char prec[9][9]={
/*input*/
/*stack + - * / ^ i ( ) $ */
/* + */ ’>’, ’>’,’<’,’<’,’<’,’<’,’<’,’>’,’>’,
/* - */ ’>’, ’>’,’<’,’<’,’<’,’<’,’<’,’>’,’>’,
/* * */ ’>’, ’>’,’>’,’>’,’<’,’<’,’<’,’>’,’>’,
/* / */ ’>’, ’>’,’>’,’>’,’<’,’<’,’<’,’>’,’>’,
/* ^ */ ’>’, ’>’,’>’,’>’,’<’,’<’,’<’,’>’,’>’,
/* i */ ’>’, ’>’,’>’,’>’,’>’,’e’,’e’,’>’,’>’,
/* ( */ ’<’, ’<’,’<’,’<’,’<’,’<’,’<’,’>’,’e’,
/* ) */ ’>’, ’>’,’>’,’>’,’>’,’e’,’e’,’>’,’>’,
/* $ */ ’<’, ’<’,’<’,’<’,’<’,’<’,’<’,’<’,’>’,
};
int getindex(char c)
{
switch(c)
{
case ’+’:return 0;
case ’-’:return 1;
case ’*’:return 2;
case ’/’:return 3;
case ’^’:return 4;
case ’i’:return 5;
case ’(’:return 6;
case ’)’:return 7;
case ’$’:return 8;
}
}
int shift()
{
stack[++top]=*(input+i++);
stack[top+1]=’\0’;
}
int reduce()
{
int i,len,found,t;
for(i=0;i<5;i++)//selecting handles
{
len=strlen(handles[i]);
if(stack[top]==handles[i][0]&&top+1>=len)
{
found=1;
for(t=0;t<len;t++)
{
if(stack[top-t]!=handles[i][t])
{
found=0;
break;
}
}
if(found==1)
{
stack[top-t+1]=’E’;
top=top-t+1;
strcpy(lasthandle,handles[i]);
stack[top+1]=’\0’;
return 1;//successful reduction
}
}
}
return 0;
}
void dispstack()
{
int j;
for(j=0;j<=top;j++)
printf("%c",stack[j]);
}
void dispinput()
{
int j;
for(j=i;j<l;j++)
printf("%c",*(input+j));
}
void main()
{
int j;
input=(char*)malloc(50*sizeof(char));
printf("\nEnter the string\n");
scanf("%s",input);
input=strcat(input,"$");
l=strlen(input);
strcpy(stack,"$");
printf("\nSTACK\tINPUT\tACTION");
while(i<=l)
{
shift();
printf("\n");
dispstack();
printf("\t");
dispinput();
printf("\tShift");
if(prec[getindex(stack[top])][getindex(input[i])]==’>’)
{
while(reduce())
{
printf("\n");
dispstack();
printf("\t");
dispinput();
printf("\tReduced: E->%s",lasthandle);
}
}
}
if(strcmp(stack,"$E$")==0)
printf("\nAccepted;");
else
printf("\nNot Accepted;");
}
OUTPUT:
i*(i+i).
i*(i+i).
STACK INPUT ACTION
$i *(i+i).$ Shift
$E *(i+i).$ Reduced: E->i
$E* (i+i).$ Shift
$E*( i+i).$ Shift
$E*(i +i).$ Shift
$E*(E +i).$ Reduced: E->i
$E*(E+ i).$ Shift
$E*(E+i ).$ Shift
$E*(E+E ).$ Reduced: E->i
$E*(E ).$ Reduced: E->E+E
Experiment 5:
OBJECTIVE:
Write program to find Simulate First and Follow of any given grammar.
ALGORITHAM:
SOURCE CODE:
#include <ctype.h>
#include <stdio.h>
#include <string.h>
// Functions to calculate Follow
void followfirst(char, int, int);
void follow(char c);
// Function to calculate First
void findfirst(char, int, int);
int count, n = 0;
// Stores the final result
// of the First Sets
char calc_first[10][100];
// Stores the final result
// of the Follow Sets
char calc_follow[10][100];
int m = 0;
// Stores the production rules
char production[10][10];
char f[10], first[10];
int k;
char ck;
int e;
int main(int argc, char** argv)
{
int jm = 0;
int km = 0;
int i, choice;
char c, ch;
count = 8;
// The Input grammar
strcpy(production[0], "X=TnS");
strcpy(production[1], "X=Rm");
strcpy(production[2], "T=q");
strcpy(production[3], "T=#");
strcpy(production[4], "S=p");
strcpy(production[5], "S=#");
strcpy(production[6], "R=om");
strcpy(production[7], "R=ST");
int kay;
char done[count];
int ptr = -1;
// Initializing the calc_first array
for (k = 0; k < count; k++) {
for (kay = 0; kay < 100; kay++) {
calc_first[k][kay] = ’!’;
}
}
int point1 = 0, point2, xxx;
for (k = 0; k < count; k++) {
c = production[k][0];
point2 = 0;
xxx = 0;
// Checking if First of c has
// already been calculated
for (kay = 0; kay <= ptr; kay++)
if (c == done[kay])
xxx = 1;
if (xxx == 1)
continue;
// Function call
findfirst(c, 0, 0);
ptr += 1;
// Adding c to the calculated list
done[ptr] = c;
printf("\n First(%c) = { ", c);
calc_first[point1][point2++] = c;
// Printing the First Sets of the grammar
for (i = 0 + jm; i < n; i++) {
int lark = 0, chk = 0;
for (lark = 0; lark < point2; lark++) {
if (first[i] == calc_first[point1][lark]) {
chk = 1;
break;
}
}
if (chk == 0) {
printf("%c, ", first[i]);
calc_first[point1][point2++] = first[i];
}
}
printf("}\n");
jm = n;
point1++;
}
printf("\n");
printf("-----------------------------------------------"
"\n\n");
char donee[count];
ptr = -1;
// Initializing the calc_follow array
for (k = 0; k < count; k++) {
for (kay = 0; kay < 100; kay++) {
calc_follow[k][kay] = ’!’;
}
}
point1 = 0;
int land = 0;
for (e = 0; e < count; e++) {
ck = production[e][0];
point2 = 0;
xxx = 0;
// Checking if Follow of ck
// has already been calculated
for (kay = 0; kay <= ptr; kay++)
if (ck == donee[kay])
xxx = 1;
if (xxx == 1)
continue;
land += 1;
// Function call
follow(ck);
ptr += 1;
// Adding ck to the calculated list
donee[ptr] = ck;
printf(" Follow(%c) = { ", ck);
calc_follow[point1][point2++] = ck;
// Printing the Follow Sets of the grammar
for (i = 0 + km; i < m; i++) {
int lark = 0, chk = 0;
for (lark = 0; lark < point2; lark++) {
if (f[i] == calc_follow[point1][lark]) {
chk = 1;
break;
}
}
if (chk == 0) {
printf("%c, ", f[i]);
calc_follow[point1][point2++] = f[i];
}
}
printf(" }\n\n");
km = m;
point1++;
}
}
void follow(char c)
{
int i, j;
// Adding "$" to the follow
// set of the start symbol
if (production[0][0] == c) {
f[m++] = ’$’;
}
for (i = 0; i < 10; i++) {
for (j = 2; j < 10; j++) {
if (production[i][j] == c) {
if (production[i][j + 1] != ’\0’) {
// Calculate the first of the next
// Non-Terminal in the production
followfirst(production[i][j + 1], i,
(j + 2));
}
if (production[i][j + 1] == ’\0’
&& c != production[i][0]) {
// Calculate the follow of the
// Non-Terminal in the L.H.S. of the
// production
follow(production[i][0]);
}
}
}
}
}
void findfirst(char c, int q1, int q2)
{
int j;
// The case where we
// encounter a Terminal
if (!(isupper(c))) {
first[n++] = c;
}
for (j = 0; j < count; j++) {
if (production[j][0] == c) {
if (production[j][2] == ’#’) {
if (production[q1][q2] == ’\0’)
first[n++] = ’#’;
else if (production[q1][q2] != ’\0’
&& (q1 != 0 || q2 != 0)) {
// Recursion to calculate First of New
// Non-Terminal we encounter after
// epsilon
findfirst(production[q1][q2], q1,
(q2 + 1));
}
else
first[n++] = ’#’;
}
else if (!isupper(production[j][2])) {
first[n++] = production[j][2];
}
else {
// Recursion to calculate First of
// New Non-Terminal we encounter
// at the beginning
findfirst(production[j][2], j, 3);
}
}
}
}
void followfirst(char c, int c1, int c2)
{
int k;
// The case where we encounter
// a Terminal
if (!(isupper(c)))
f[m++] = c;
else {
int i = 0, j = 1;
for (i = 0; i < count; i++) {
if (calc_first[i][0] == c)
break;
}
// Including the First set of the
// Non-Terminal in the Follow of
// the original query
while (calc_first[i][j] != ’!’) {
if (calc_first[i][j] != ’#’) {
f[m++] = calc_first[i][j];
}
else {
if (production[c1][c2] == ’\0’) {
// Case where we reach the
// end of a production
follow(production[c1][0]);
}
else {
// Recursion to the next symbol
// in case we encounter a "#"
followfirst(production[c1][c2], c1,
c2 + 1);
}
}
j++;
}
}
}
OUTPUT:
First(X) = { q, n, o, p, #, }
First(T) = { q, #, }
First(S) = { p, #, }
First(R) = { o, p, q, #, }
-----------------------------------------------
Follow(X) = { $, }
Follow(T) = { n, m, }
Follow(S) = { $, q, m, }
Follow(R) = { m, }
Experiment 6:
OBJECTIVE:
ALGORITHAM:
A recursive descent parser is a top-down parser that starts with the topmost rule of the grammar and
recursively expands non-terminals by matching the input against the production rules. Below are the
algorithm steps to construct a recursive descent parser for an expression grammar:
function parse_E():
parse_T()
parse_E_prime()
function parse_E_prime():
if current_token is '+':
match('+')
parse_T()
parse_E_prime()
function parse_T():
parse_F()
parse_T_prime()
function parse_T_prime():
if current_token is '*':
match('*')
parse_F()
parse_T_prime()
function parse_F():
if current_token is '(':
match('(')
parse_E()
match(')')
elif current_token is 'id':
match('id')
else:
error("Invalid token")
function match(expected_token):
if current_token == expected_token:
consume_token()
else:
error("Unexpected token")
function consume_token():
move to the next token
SOURCE CODE:
#include <stdio.h>
#include <string.h>
#define SUCCESS 1
#define FAILED 0
int E(), Edash(), T(), Tdash(), F();
const char *cursor;
char string[64];
int main()
{
puts("Enter the string");
// scanf("%s", string);
sscanf("i+(i+i)*i", "%s", string);
cursor = string;
puts("");
puts("Input Action");
puts("--------------------------------");
if (E() && *cursor == ’\0’) {
puts("--------------------------------");
puts("String is successfully parsed");
return 0;
} else {
puts("--------------------------------");
puts("Error in parsing String");
return 1;
}
}
int E()
{
printf("%-16s E -> T E’\n", cursor);
if (T()) {
if (Edash())
return SUCCESS;
else
return FAILED;
} else
return FAILED;
}
int Edash()
{
if (*cursor == ’+’) {
printf("%-16s E’ -> + T E’\n", cursor);
cursor++;
if (T()) {
if (Edash())
return SUCCESS;
else
return FAILED;
} else
return FAILED;
} else {
printf("%-16s E’ -> $\n", cursor);
return SUCCESS;
}
}
int T()
{
printf("%-16s T -> F T’\n", cursor);
if (F()) {
if (Tdash())
return SUCCESS;
else
return FAILED;
} else
return FAILED;
}
int Tdash()
{
if (*cursor == ’*’) {
printf("%-16s T’ -> * F T’\n", cursor);
cursor++;
if (F()) {
if (Tdash())
return SUCCESS;
else
return FAILED;
} else
return FAILED;
} else {
printf("%-16s T’ -> $\n", cursor);
return SUCCESS;
}
}
int F()
{
if (*cursor == ’(’) {
printf("%-16s F -> ( E )\n", cursor);
cursor++;
if (E()) {
if (*cursor == ’)’) {
cursor++;
return SUCCESS;
} else
return FAILED;
} else
return FAILED;
} else if (*cursor == ’i’) {
cursor++;
printf("%-16s F ->i\n", cursor);
return SUCCESS;
} else
return FAILED;
}
OUTPUT:
OBJECTIVE:
ALGORITHAM:
A Shift-Reduce parser is a type of bottom-up parser that processes the input from left to right, using two
main operations: shift and reduce. The parser shifts input symbols onto a stack and attempts to reduce
them according to the production rules of the grammar. Here are the algorithm steps for constructing a
Shift-Reduce parser:
Initialization:
Initialize an empty stack to hold symbols (both terminals and non-terminals).
Read the input string from left to right.
Initialize the pointer to the first input symbol.
Parsing Loop:
Repeat until the stack contains only the start symbol and the input is fully processed.
Check if there is a reduce operation possible by matching the stack's top with the right-
hand side of any production. If a match is found, perform reduce.
If no reduce is possible, perform shift by pushing the next input symbol onto the stack.
Reduce Operation:
Identify the rightmost substring on the stack that matches the right-hand side of a
production.
Replace the matched substring with the non-terminal on the left-hand side of the
production.
Shift Operation:
Push the next input symbol onto the stack.
Acceptance:
If the stack contains only the start symbol and the input is fully processed, accept the
input.
Otherwise, reject the input.
SOURCE CODE:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//Global Variables
int z = 0, i = 0, j = 0, c = 0;
// Modify array size to increase
// length of string to be parsed
char a[16], ac[20], stk[15], act[10];
// This Function will check whether
// the stack contain a production rule
// which is to be Reduce.
// Rules can be E->2E2 , E->3E3 , E->4
void check()
{
// Copying string to be printed as action
strcpy(ac,"REDUCE TO E -> ");
// c=length of input string
for(z = 0; z < c; z++)
{
//checking for producing rule E->4
if(stk[z] == ’4’)
{
printf("%s4", ac);
stk[z] = ’E’;
stk[z + 1] = ’\0’;
//printing action
printf("\n$%s\t%s$\t", stk, a);
}
}
for(z = 0; z < c - 2; z++)
{
//checking for another production
if(stk[z] == ’2’ && stk[z + 1] == ’E’ &&
stk[z + 2] == ’2’)
{
printf("%s2E2", ac);
stk[z] = ’E’;
stk[z + 1] = ’\0’;
stk[z + 2] = ’\0’;
printf("\n$%s\t%s$\t", stk, a);
i = i - 2;
}
}
for(z=0; z<c-2; z++)
{
//checking for E->3E3
if(stk[z] == ’3’ && stk[z + 1] == ’E’ &&
stk[z + 2] == ’3’)
{
printf("%s3E3", ac);
stk[z]=’E’;
stk[z + 1]=’\0’;
stk[z + 1]=’\0’;
printf("\n$%s\t%s$\t", stk, a);
i = i - 2;
}
}
return ; //return to main
}
//Driver Function
int main()
{
printf("GRAMMAR is -\nE->2E2 \nE->3E3 \nE->4\n");
// a is input string
strcpy(a,"32423");
// strlen(a) will return the length of a to c
c=strlen(a);
// "SHIFT" is copied to act to be printed
strcpy(act,"SHIFT");
// This will print Labels (column name)
printf("\nstack \t input \t action");
// This will print the initial
// values of stack and input
printf("\n$\t%s$\t", a);
// This will Run upto length of input string
for(i = 0; j < c; i++, j++)
{
// Printing action
printf("%s", act);
// Pushing into stack
stk[i] = a[j];
stk[i + 1] = ’\0’;
// Moving the pointer
a[j]=’ ’;
// Printing action
printf("\n$%s\t%s$\t", stk, a);
// Call check function ..which will
// check the stack whether its contain
// any production or not
check();
}
// Rechecking last time if contain
// any valid production then it will
// replace otherwise invalid
check();
// if top of the stack is E(starting symbol)
// then it will accept the input
if(stk[0] == ’E’ && stk[1] == ’\0’)
printf("Accept\n");
else //else reject
printf("Reject\n");
}
OUTPUT:
GRAMMAR is -
E->2E2
E->3E3
E->4
$ 32423$ SHIFT
$3 2423$ SHIFT
$32 423$ SHIFT
$324 23$ REDUCE TO E -> 4
$32E 23$ SHIFT
$32E2 3$ REDUCE TO E -> 2E2
$3E 3$ SHIFT
$3E3 $ REDUCE TO E -> 3E3
$E $ Accept
Experiment 8:
OBJECTIVE:
ALGORITHAM:
Intermediate code generation is a crucial step in the compilation process, where the high-level source
code is translated into an intermediate representation. For simplicity, let's focus on generating
intermediate code for simple arithmetic expressions. Here's a basic algorithm with steps:
Input:
A simple arithmetic expression in infix notation.
Output:
Intermediate code in a suitable representation (e.g., three-address code).
Steps:
Tokenization:
Break the input expression into tokens, such as operators (+, -, *, /), operands (variables
or constants), and parentheses.
Conversion to Postfix Notation:
Use the Shunting Yard Algorithm or a similar method to convert the infix expression into
postfix notation. This simplifies the expression evaluation.
Generate Intermediate Code:
Initialize an empty stack to hold intermediate results.
Iterate through the postfix expression.
For each token:
If it is an operand, push it onto the stack.
If it is an operator, pop the required number of operands from the stack,
perform the operation, and push the result back onto the stack.
If it is a variable or constant, push it onto the stack.
Handle parentheses appropriately.
At the end of the iteration, the stack should contain the final result or the intermediate
results.
Display Intermediate Code:
The final stack or generated intermediate code can be displayed in a suitable format.
Common formats include three-address code, quadruples, or any other representation that
suits your needs.
Example:
Consider the expression: a = b + c * (d - e)
Tokenization: [a, =, b, +, c, *, (, d, -, e, )]
Postfix Notation: [a, b, c, d, e, -, *, +, =]
Intermediate Code:t1 = d - e
t2 = c * t1
t3 = b + t2
a = t3
SOURCE CODE:
#include<stdio.h>
#include<string.h>
int i=1,j=0,no=0,tmpch=90;
char str[100],left[15],right[15];
void findopr();
void explore();
void fleft(int);
void fright(int);
struct exp
{
int pos;
char op;
}k[15];
void main()
{
printf("\t\tINTERMEDIATE CODE GENERATION\n\n");
printf("Enter the Expression :");
scanf("%s",str);
printf("The intermediate code:\n");
findopr();
explore();
}
void findopr()
{
for(i=0;str[i]!=’\0’;i++)
if(str[i]==’:’)
{
k[j].pos=i;
k[j++].op=’:’;
}
for(i=0;str[i]!=’\0’;i++)
if(str[i]==’/’)
{
k[j].pos=i;
k[j++].op=’/’;
}
for(i=0;str[i]!=’\0’;i++)
if(str[i]==’*’)
{
k[j].pos=i;
k[j++].op=’*’;
}
for(i=0;str[i]!=’\0’;i++)
if(str[i]==’+’)
{
k[j].pos=i;
k[j++].op=’+’;
}
for(i=0;str[i]!=’\0’;i++)
if(str[i]==’-’)
{
k[j].pos=i;
k[j++].op=’-’;
}
}
void explore()
{
i=1;
while(k[i].op!=’\0’)
{
fleft(k[i].pos);
fright(k[i].pos);
str[k[i].pos]=tmpch--;
printf("\t%c := %s%c%s\t\t",str[k[i].pos],left,k[i].op,right);
printf("\n");
i++;
}
fright(-1);
if(no==0)
{
fleft(strlen(str));
printf("\t%s := %s",right,left);
exit(0);
}
printf("\t%s := %c",right,str[k[--i].pos]);
}
void fleft(int x)
{
int w=0,flag=0;
x--;
while(x!= -1 &&str[x]!= ’+’ &&str[x]!=’*’&&str[x]!=’=’&&str[x]!=’\0’&&str[x]!=’-’&&str[x]!
=’/’&&str[x]!=’:’)
{
if(str[x]!=’$’&& flag==0)
{
left[w++]=str[x];
left[w]=’\0’;
str[x]=’$’;
flag=1;
}
x--;
}
}
void fright(int x)
{
int w=0,flag=0;
x++;
while(x!= -1 && str[x]!= ’+’&&str[x]!=’*’&&str[x]!=’\0’&&str[x]!=’=’&&str[x]!=’:’&&str[x]!
=’-’&&str[x]!=’/’)
{
if(str[x]!=’$’&& flag==0)
{
right[w++]=str[x];
right[w]=’\0’;
str[x]=’$’;
flag=1;
}
x++;
}
}
OUTPUT:
OBJECTIVE:
ALGORITHAM:
Constant propagation is a compiler optimization technique that replaces variables with their known
constant values where applicable. Here's a basic algorithm with steps for implementing constant
propagation:
Input:
Intermediate code with potential constant values.
Output:
Optimized intermediate code with constants replaced where possible.
Steps:
Initialize:
Create a symbol table to store variable values.
Initialize the symbol table with known constant values.
Iterate through Intermediate Code:
For each statement in the intermediate code:
Identify assignment statements and update the symbol table accordingly.
For example, if the statement is x = 5, update the symbol table with x = 5.
Propagate Constants:
Iterate through the intermediate code again.
For each statement:
If it's an expression like y = a + b, check if a and b are constants in the
symbol table.
If both are constants, evaluate the expression and replace a and b with the
result in the intermediate code.
Update the symbol table with the result: y = constant_result.
Remove Redundant Assignments:
Iterate through the intermediate code once more.
For each assignment statement like z = x, check if x is a constant in the symbol
table.
If x is a constant, replace z with the constant value in the intermediate code.
Display or Output Optimized Code:
The final optimized intermediate code can be displayed or used for further compilation
phases.
Example:
x=5
y=x+3
z=y*2
x=5
y=5+3
z = (5 + 3) * 2
x=5
y=8
z = 16
SOURCE CODE:
#include<stdio.h>
#include<string.h>
void input();
void output();
void change(int p,char *res);
void constant();
struct expr
{
char op[2],op1[5],op2[5],res[5];
int flag;
}arr[10];
int n;
void main()
{
input();
constant();
output();
}
void input()
{
int i;
printf("\n\nEnter the maximum number of expressions : ");
scanf("%d",&n);
printf("\nEnter the input : \n");
for(i=0;i<n;i++)
{
scanf("%s",arr[i].op);
scanf("%s",arr[i].op1);
scanf("%s",arr[i].op2);
scanf("%s",arr[i].res);
arr[i].flag=0;
}
}
void constant()
{
int i;
int op1,op2,res;
char op,res1[5];
for(i=0;i<n;i++)
{
if(isdigit(arr[i].op1[0]) && isdigit(arr[i].op2[0]) || strcmp(arr[i].op,"=")==0) /*if both digits, store them in
variab
les*/
{
op1=atoi(arr[i].op1);
op2=atoi(arr[i].op2);
op=arr[i].op[0];
switch(op)
{
case ’+’:
res=op1+op2;
break;
case ’-’:
res=op1-op2;
break;
case ’*’:
res=op1*op2;
break;
case ’/’:
res=op1/op2;
break;
case ’=’:
res=op1;
break;
}
sprintf(res1,"%d",res);
arr[i].flag=1; /*eliminate expr and replace any operand below that uses result of this expr */
change(i,res1);
}
}
}
void output()
{
int i=0;
printf("\nOptimized code is : ");
for(i=0;i<n;i++)
{
if(!arr[i].flag)
{
printf("\n%s %s %s %s",arr[i].op,arr[i].op1,arr[i].op2,arr[i].res);
}
}
}
void change(int p,char *res)
{
int i;
for(i=p+1;i<n;i++)
{
if(strcmp(arr[p].res,arr[i].op1)==0)
strcpy(arr[i].op1,res);
else if(strcmp(arr[p].res,arr[i].op2)==0)
strcpy(arr[i].op2,res);
}
}
OUTPUT:
=3-a
+ a b t1
+ a c t2
+ t1 t2 t3
=3-a
+ a b t1
+ a c t2
+ t1 t2 t3
Optimized code is :
+ 3 b t1
+ 3 c t2
+ t1 t2 t3
Experiment 10:
OBJECTIVE:
ALGORITHAM:
Loop unrolling is a compiler optimization technique that aims to reduce loop overhead by increasing the
size of the loop body. This is done by replicating or unwinding the loop code to achieve better
performance. Below are the algorithm steps for loop unrolling:
Input:
Input the loop structure and its body.
Determine Loop Characteristics:
Analyze the loop to determine its characteristics, such as loop bounds, stride, and
dependencies.
Choose Unrolling Factor:
Decide on the unrolling factor, which represents how many iterations of the loop will be
combined into a single iteration.
Loop Unrolling:
Identify the loop body and duplicate its code according to the chosen unrolling factor.
For example, if the unrolling factor is 2, each iteration of the loop will be
replaced by two copies of the loop body.
Adjust Loop Bounds:
Modify loop bounds to account for the increased loop body size.
If the original loop had n iterations, and the unrolling factor is 2, the modified
loop will have n/2 iterations.
Handle Remaining Iterations:
If the number of iterations is not divisible evenly by the unrolling factor, handle the
remaining iterations separately with a residual loop.
Optimize for Pipelining:
Consider optimizing the unrolled loop for pipelining by scheduling instructions to
minimize pipeline stalls.
Check Dependencies:
Ensure that there are no dependencies within the loop body that would prevent effective
unrolling.
Update Variable References:
Adjust variable references within the loop body to account for the modified loop bounds.
Generate Code:
Generate the optimized code with the loop unrolling applied.
Test and Validate:
Test the unrolled loop to ensure correctness and validate its performance against the
original loop.
SOURCE CODE:
#include<stdio.h>
void main() {
unsigned int n;
int x;
char ch;
printf("\nEnter N\n");
scanf("%u", & n);
printf("\n1. Loop Roll\n2. Loop UnRoll\n");
printf("\nEnter ur choice\n");
scanf(" %c", & ch);
switch (ch) {
case ’1’:
x = countbit1(n);
printf("\nLoop Roll: Count of 1’s : %d", x);
break;
case ’2’:
x = countbit2(n);
printf("\nLoop UnRoll: Count of 1’s : %d", x);
break;
default:
printf("\n Wrong Choice\n");
}
}
int countbit1(unsigned int n) {
int bits = 0, i = 0;
while (n != 0) {
if (n & 1) bits++;
n >>= 1;
i++;
}
printf("\n no of iterations %d", i);
return bits;
}
int countbit2(unsigned int n) {
int bits = 0, i = 0;
while (n != 0) {
if (n & 1) bits++;
if (n & 2) bits++;
if (n & 4) bits++;
if (n & 8) bits++;
n >>= 4;
i++;
}
printf("\n no of iterations %d", i);
return bits;
}
OUTPUT:
Enter N
3
1. Loop Roll
2. Loop UnRoll
Enter ur choice
2
no of iterations 1
Loop UnRoll: Count of 1’s : 2
VALUE ADDED PROGRAMS
Experiment 1:
OBJECTIVE:
Implement the back end of the compiler which takes the three address code and produces the 8086
assembly language instructions that can be assembled and run using an 8086 assembler. The target
assembly instructions can be simple move, add, sub, jump etc.
ALGORITHAM:
Implementing the back end of a compiler involves translating an intermediate representation, such as
three-address code, into the target assembly language, in this case, 8086 assembly language instructions.
Below is a simplified algorithm to guide you through this process:
SOURCE CODE:
class AssemblyCodeGenerator:
def __init__(self):
self.assembly_code = []
self.label_count = 0
defgenerate_label(self):
label = f'L{self.label_count}'
self.label_count += 1
return label
defgenerate_assembly(self, three_address_code):
operation = instruction[0]
if operation == '=':
# Assignment
# Addition
# Subtraction
self.assembly_code.append(f'JL {label}')
defprint_assembly(self):
print(line)
defmain():
three_address_code = [
('JMP', 'L2'),
('L1:',),
generator = AssemblyCodeGenerator()
generator.generate_assembly(three_address_code)
generator.print_assembly()
if __name__ == "__main__":
main()
VALUE ADDED PROGRAMS
Experiment 2:
OBJECTIVE:
ALGORITHM:
Initialize:
Create an empty set to represent states in the DFA.
Create an empty set to represent states in the DFA that have been visited.
Define an empty set for the DFA transitions.
Compute ε-Closure:
Compute the ε-closure of the start state of the NFA. This will be the initial state of the
DFA.
Create DFA Transition Table:
For each state set in the DFA:
For each input symbol (excluding ε):
Find the set of NFA states that can be reached from the current state set
using the input symbol.
Compute the ε-closure of the union of NFA states obtained in the
previous step.
This becomes the next state for the current state set and input symbol in
the DFA.
Repeat Until No New States:
Repeat step 3 until no new states are added to the DFA.
Keep track of visited state sets to avoid redundant computations.
Identify Accept States:
Mark a state set in the DFA as an accept state if it contains at least one accept state from
the NFA.
Finalize DFA:
The set of states in the DFA is the collection of all state sets obtained during the process.
The transitions represent the computed transitions for each state set and input symbol.
The start state is the ε-closure of the start state of the NFA.
The accept states are the state sets in the DFA marked as accept states.
Output DFA:
The DFA is now constructed and ready for use.
The algorithm essentially simulates the behavior of the NFA, exploring all possible state combinations
and transitions to build the corresponding DFA. The ε-closure operation is crucial for handling epsilon
transitions in the NFA. The process continues until no new states are discovered in the DFA.
SOURCE CODE:
#include <stdio.h>
int main()
{
int nfa[5][2];
nfa[1][1]=12;
nfa[1][2]=1;
nfa[2][1]=0;
nfa[2][2]=3;
nfa[3][1]=0;
nfa[3][2]=4;
nfa[4][1]=0;
nfa[4][2]=0;
int dfa[10][2];
int dstate[10];
int i=1,n,j,k,flag=0,m,q,r;
dstate[i++]=1;
n=i;
dfa[1][1]=nfa[1][1];
dfa[1][2]=nfa[1][2];
printf("\nf(%d,a)=%d",dstate[1],dfa[1][1]);
printf("\nf(%d,b)=%d",dstate[1],dfa[1][2]);
for(j=1;j<n;j++)
{
if(dfa[1][1]!=dstate[j])
flag++;
}
if(flag==n-1)
{
dstate[i++]=dfa[1][1];
n++;
}
flag=0;
for(j=1;j<n;j++)
{
if(dfa[1][2]!=dstate[j])
flag++;
}
if(flag==n-1)
{
dstate[i++]=dfa[1][2];
n++;
}
k=2;
while(dstate[k]!=0)
{
m=dstate[k];
if(m>10)
{
q=m/10;
r=m%10;
}
if(nfa[r][1]!=0)
dfa[k][1]=nfa[q][1]*10+nfa[r][1];
else
dfa[k][1]=nfa[q][1];
if(nfa[r][2]!=0)
dfa[k][2]=nfa[q][2]*10+nfa[r][2];
else
dfa[k][2]=nfa[q][2];
printf("\nf(%d,a)=%d",dstate[k],dfa[k][1]);
printf("\nf(%d,b)=%d",dstate[k],dfa[k][2]);
flag=0;
for(j=1;j<n;j++)
{
if(dfa[k][1]!=dstate[j])
flag++;
}
if(flag==n-1)
{
dstate[i++]=dfa[k][1];
n++;
}
flag=0;
for(j=1;j<n;j++)
{
if(dfa[k][2]!=dstate[j])
flag++;
}
if(flag==n-1)
{
dstate[i++]=dfa[k][2];
n++;
}
k++;
}
return 0;
}
OUTPUT:
f(1,a)=12
f(1,b)=1
f(12,a)=12
f(12,b)=13
f(13,a)=12
f(13,b)=14
f(14,a)=12
f(14,b)=1
Sample Quizzes/Viva-questions
LO-1:
PRE LAB QUESTIONS
1. What is token?
2. What is lexeme?
3. What is the difference between token and lexeme?
4. Define phase and pass?
5. What is the difference between phase and pass?
6. What is the difference between compiler and interpreter?
LAB ASSIGNMENT
LAB ASSIGNMENT:
PRE-LAB QUESTIONS
LAB ASSIGNMENT
POST-LAB QUESTIONS:
PRE-LAB QUESTIONS
1 What are the functions we use to construct a syntax tree?
2 What is Meta data?
3 How list of identifiers are represented using BNF rules?
4 What is three address code?
5 What are the record structures we use to represent three address code?
LAB ASSIGNMENT
POST-LAB QUESTIONS:
1. What is Abstract Syntax tree?
2. What are BNF Rules?
3. What is DAG representation?
4. How LALR (1) states are generates?
5. In which condition the user has to supply more information to YACC?
LO 5:
PRE-LAB QUESTIONS
LAB ASSIGNMENT
1 Write a program to generate the code for the following three address code
statements? A=B+C
W=X-Y
2 Write a program to generate the code for the following three address code
statements? W=(A+B)*C
POST-LAB QUESTIONS
SECTION-E
SECTION-F