Lab Manual Compiler (Cs316)
Lab Manual Compiler (Cs316)
LAB MANUAL
Session 2021-2022
Branch : - CSE
Year : - 3rd
Semester : - 6th
REVISION HISTORY
REVISIO AUTHOR(S) REVIEWER(S DATE OF OWNER SUMMARY OF
N# ) RELEASE CHANGES
Revision Mis. Kavita Agarwal Internal 01/01/201 HOD - Initial Release
-1 Mr. Shoeb Ahad Siddiqui Reviewers 4 CSE
Mr. Mohd Arshad Ali
Mr. Tabrez Khan
Compiler is a System Software that converts High level language to low level language. We human beings
can’t program in machine language (low level lang.) understood by Computers so we programmed. In high
level language and compiler is the software which bridges the gap between user and computer.
Compiler:-
A compiler is a program that translates a source program written in some high-level programming language
(such as Java) into machine code for some computer architecture (such as the Intel Pentium architecture).
The generated machine code can be later executed many times against different data each time.
Interpreter:-
An interpreter reads an executable source program written in a high-level programming language as well as
data for this program, and it runs the program against the data to produce some results. One example is the
UNIX shell interpreter, which runs operating system commands interactively.
An interpreter is generally slower than a compiler because it processes and interprets each statement in a
program as many times as the number of the evaluations of this statement. For example, when a for-loop is
interpreted, the statements inside the for-loop body will be analyzed and evaluated on every loop step. Some
languages, such as Java and Lisp, come with both an interpreter and a compiler.
A compiler is a special program that processes statements written in a particular programming language and
turns them into machine language or code that a computer's processor uses. Typically, a programmer writes
language statements in a language such as Pascal or C one line at a time using an editor. The file that is
created contains what are called the source statements. The programmer then runs the appropriate language
compiler, specifying the name of the file that contains the source statements.
It’s a very complicated piece of software which took 18 man years to build first compiler. To build this S/w
it is divided into six phases which are
1) Lexical Analysis
2) Syntax Analysis
3) Semantic Analysis
4) Intermediate Code Generation
5) Code Optimization
6) Code Generation.
In the lab sessions students implement Lexical Analyzers and code for each phase to understand compiler
software working and its coding in detail.
OBJECTIVE
Switch Statements
A switch statement allows a variable to be tested for equality against a list of values. Each value is called a
case, and the variable being switched on is checked for each switch case.
Syntax:
The syntax for a switch statement in C programming language is as follows:
switch(expression){
case constant-expression :
statement(s);
break; /* optional */
case constant-expression :
statement(s);
break; /* optional */
/* you can have any number of case statements */
default : /* Optional */
statement(s);
}
The expression used in a switch statement must have an integer or enumerated type, or be of a class
type in which the class has a single conversion function to an integral or enumerated type.
You can have any number of case statements within a switch. Each case is followed by the value to
be compared to and a colon.
The constant-expression for a case must be the same data type as the variable in the switch, and it
must be a constant or a literal.
When the variable being switched on is equal to a case, the statements following that case will
execute until a break statement is reached.
When a break statement is reached, the switch terminates, and the flow of control jumps to the next
line following the switch statement.
Not every case needs to contain a break. If no break appears, the flow of control will fall through to
subsequent cases until a break is reached.
A switch statement can have an optional default case, which must appear at the end of the switch.
The default case can be used for performing a task when none of the cases is true. No break is needed in the
default case.
Function
Example:
Solution:
#include<stdio.h>
#include<conio.h>
#include<string.h>
void sum_mat();
void mul_mat();
void str_pal();
void main()
{
int ch;
printf(“Press 1 for sum of matrix\n”);
printf(“Press 2 for Multiplication of matrix\n”);
printf(“Press 3 for to check whether string given string is palindrome or not\n”);
printf(“Press 4 for exit\n”);
printf(“Enter the choice\n”);
scanf(“%d”,&ch);
switch(ch)
{
case 1:
sum_mat();
break;
case 2:
mul_mat();
break;
case 3:
str_pal();
break;
case 4:
exit(0);
default:
printf(“Wrong choice”);
}}
void sum_mat()
{ //code for matrix addition }
void mul_mat()
{
//code for matrix multiplication }
void str_pal()
{
//code for string palindrome
}
Postfix expression is one of the intermediate codes to be used in the translation of source code into machine
code. For example, if an expression is like “a+b*c”, it is converted into “abc*+”. In postfix expression, the
operators are moved to the position that is next to the required operands. In order to convert the infix to
postfix expression, three data structure are used: (i) a stack, (ii) input buffer, and (iii) output buffer. During
the evaluation of the expression some portion of the expressions are evaluated first and the other portions are
evaluated next, depending on the priority of the operator.
Algorithms
Steps:
3. Fix priorities for the operators in the infix string and stack.
4. For every operand or operator read from the infix expression string do step 5.
Else either push the operator on to stack or move the contents of stack to postfix string depending on the
priority of the operator compared with the priority of the stack operator.
Modules:
o Checking the empty and full status of the stack for pop and push operations
Operator array
Solution:
#include<stdio.h>
#include<conio.h>
#include<ctype.h>
#include<string.h>
int top=-1,i=0,l;
char infix[50],stk[50],a;
void postfix(char);
int p(char);
void main()
{
clrscr();
printf("Enter infix expression");
gets(infix);
l=strlen(infix);
infix[l]='#';
stk[++top]='#';
printf("Postfix Expression");
while(infix[i]!='#')
{
if(isalpha(infix[i]))
printf("%c",infix[i]);
else
postfix(infix[i]);
i++;
}
while((top!=-1)&&(stk[top]!='#'))
printf("%c",stk[top--]);
getch();
}
void postfix(char a)
{
switch(a)
{
case '(':stk[++top]=a;
break;
default:
if(a==')')
{
while(stk[++top]!=')')
printf("%c",stk[--top]);
}
else if(stk[++top]=='(')
stk[++top]=a;
else if(p(a)>p(stk[top]))
case '/':
case '*':
return 2;
break;
case '+':
case '-':
return 1;
break;
default:
return 0;
}
}
Output
#include<stdio.h>
#include<conio.h>
#include<ctype.h>
#include<string.h>
int top=-1,t=-1;
char infix[20],sym[20],inf[20],pre[20];
void prefix(char);
int p(char);
void main()
{
int l,i=0;
clrscr();
printf("\n Convert Infix to PrefixExpression\n");
printf("Enter infix expression");
gets(infix);
l=strlen(infix);
while(i<l)
{
inf[i+1]=infix[i];
i++;
}
inf[0]='(';
sym[++t]=')';
i=strlen(inf)-1;
while(i!=0)
{
if(isalpha(inf[i]))
pre[++top]=inf[i];
else
prefix(inf[i]);
i--;
}
while((t!=-1)&&(sym[t]!=')'))
pre[++top]=sym[t--];
printf("Prefix Expression");
while(top!=-1)
printf("%c", pre[top--]);
getch();
}
void prefix(char a)
{
switch(a)
{
case ')':
default:
if(a=='(')
{
while(sym[++t]!=')')
pre[++top]=sym[t--];
t--;
}
else if(a==')')
sym[++t]=a;
else if(p(a)>p(sym[t]))
sym[++t]=a;
else
{
while(p(a)<p(sym[t]))
pre[++top]=sym[t--];
sym[++t]=a;
}
}
}
int p(char a)
{
switch(a)
{
case '^':
return 3;
break;
case '/':
case '*':
return 2;
break;
case '+':
case '-':
return 1;
break;
default:
return 0;
}
}
Output
1. Start
2. Declare a character array str[] and initialize integer variables len, a=0.
3. Input the string from the User.
4. Find the length of the string.
5. Repeat step 6 to 7 till a<len.
6. If the str[a] is numeric character then a++.
7. Else it is not a constant, break from the loop and go to step 9.
8. if a==len then print that the entered string is a constant
9. Else it is not a constant.
10. Stop.
Solution:-
#include<stdio.h>
#include<conio.h>
#include<ctype.h>
#include<string.h>
void main()
{
char str[10];
int len,a;
clrscr();
printf("\n Input a string :");
gets(str);
len=strlen(str);
a=0;
while(a<len)
{
if(isdigit(str[a]))
{
a++;
}
else
{
printf(" It is not a Constant");
break;
}
}
OUTPUT
Input a string: 23
It is a Constant
1. Start.
2. Declare a character storing the keywords, s[5][10]={"if","else","goto","continue","return"} and
another character array to store the string to be compared st[], initialize integer variables I, flag=0,m.
3. Input the string that is to be compared st[].
4. Repeat step 5 to 6 till counter i becomes equal to number of keywords stored in the array.
5. Compare the string entered by the user with the strings in the character array by using
m=strcmp(st,s[i]), where strcmp function returns true if both the strings are equal.
6. i=i+1.
7. If flag=1 then it is keyword.
8. Else it is not a keyword.
9. Stop.
OUTPUT
Enter the string: return
it is a keyword
Step 1. Start
Step 2. Check if the first char is a letter goto step 3 with rest of the string else goto 5.
Step 3. Check if the first character of the given string is a letter or a digit repeat step 3 with
rest of the string else goto step 5.
Step 4. Print “The given string is an identifier” and goto step 6.
Step 5.Print “The given string is not an identifier”.
Step 6. Exit
Solution:-
#include<stdio.h>
#include<conio.h>
int isiden(char*);
int second(char*);
int third();
void main()
{
char *str;
int i = -1;
clrscr();
printf("\n\n\t\tEnter the desired String: ");
do
{
++i;
str[i] = getch();
if(str[i]!=10 && str[i]!=13)
printf("%c",str[i]);
if(str[i] == '\b')
{
--i;
printf(" \b");
}
}while(str[i] != 10 && str[i] != 13);
if(isident(str))
printf("\n\n\t\tThe given strig is an identifier");
else
printf("\n\n\t\tThe given string is not an identifier");
getch();
}
//To Check whether the given string is identifier or not //This function acts like first stage of dfa
int isident(char *str)
{
if((str[0]>='a' && str[0]<='z') || (str[0]>='A' && str[0]<='Z'))
{
return(second(str+1));
second stage
}
else
{
OUTPUT:
Enter the desired String: a123
The given string is an identifier
Algorithms:-
Before Retract
After Retract
After Accept
4. Return: Returns a token consisting of a class and a value, as well as performs any actions associated
with that state, e.g., installing an identifier into the name table.
Out Put:
Keywords
Paranthesis
Identifier:-a
Relational Operator
Identifier:-b
Paranthesis
Identifier:-then
Identifier:-a
Assignment Operator
Identifier:-a
Operator
Number:-1
Keywords
Identifier:-b
Assignment Operator
Identifier:-b
Operator
Number:-1
ALGORITHM OF FIRST
To compute FIRST(X) for all grammar symbols x, apply the following rules until no more terminals can be
added to any FIRST set.
ALGORITHM OF FOLLOW
To compute FIRST(X) for all grammar symbols x, apply the following rules until no more terminals can be
added to any FIRST set.
Week-9
#include"stdio.h"
#include<conio.h>
#define max 10
#define MAX 15
char array[max][MAX],temp[max][MAX];
int c,n,t;
void fun(int,int[]);
int fun2(int i,int j,int p[],int key)
/* Follow Finding */
for(i=0,l=0;i<n;i++,l++)
{
f=-1;ff0=-1;
fol[l][++f]=array[i][0];
follow(i);
fol[l][++f]='\0';
}
for(i=0;i<n;i++)
{
printf("\nFollow[%c] : [ ",fol[i][0]);
for(j=1;fol[i][j]!='\0';j++)
printf("%c,",fol[i][j]);
printf("\b ]");
getch();
}
}
Out Put:
First(S) : [ ].
First(C) : [ d ].
Follow[S] : [ $ ]
Follow[C] : [ d,$ ]
One of the most straightforward forms of parsing is recursive descent parsing. This is a top-down process in
which the parser attempts to verify that the syntax of the input stream is correct as it is read from left to
right. A basic operation necessary for this involves reading characters from the input stream and matching
then with terminals from the grammar that describes the syntax of the input. Our recursive descent parsers
will look ahead one character and advance the input stream reading pointer when proper matches occur. The
routine presented in figure 1 accomplishes this matching and reading process.
Figure 3 - Factoring
Let us attempt another example, namely the old favorite, a grammar that generates strings of the
form anbn shown below.
S aSb
S ab
S aZ
Z Sb
Zb
Which is not quite the form we want since it is unclear when to apply the Z-productions. Substituting for S
will solve this problem and produce the following productions that are exactly what we want to have.
S aZ
Z aZb
Zb
Solution:
#include<stdio.h>
#include<conio.h>
#include<string.h>
void main()
{
int i,l;
int c=0;
char a[10];
clrscr();
printf("Enter the Expression and place $ at the End\n");
for(i=0;i<10;i++)
{
scanf("%c",&a[i]);
}
l=strlen(a);
for(i=0;i<l;i++)
{
if(a[i]=='a'||a[i]=='b'||a[i]=='c'||a[i]=='d'|a[i]=='e')
{
c++;
printf("\n\n Variable:-");
printf("%c",a[i]);
}
else if(a[i]=='+'||a[i]=='-'||a[i]=='*'||a[i]=='/')
{
printf("\n\n Arithmetic Operator:\t");
printf("%c",a[i]);
c++;
}
}
if(a[c]=='$')
printf("\n\n The String is Parsed");
else
printf("\n\n The String is not Parsed");
getch();
}
Variable:-a
Arithmetic Operator: +
Variable:-b
Arithmetic Operator: -
Variable:-c
Arithmetic Operator: *
Variable:-d
The String is Parsed
The goal of predictive parsing is to construct a top-down parser that never backtracks. To do so, we must
transform a grammar in two ways:
1. eliminate left recursion, and
2. Perform left factoring.
In computer science, a recursive descent parser is a kind of top-down parser built from a set of mutually
recursive procedures (or a non-recursive equivalent) where each such procedure usually implements one of
the production rules of the grammar. Thus the structure of the resulting program closely mirrors that of the
grammar it recognizes.
A predictive parser is a recursive descent parser that does not require backtracking. Predictive parsing is
possible only for the class of LL(k) grammars, which are the context-free grammars for which there exists
some positive integer k that allows a recursive descent parser to decide which production to use by
examining only the next k tokens of input. (The LL(k) grammars therefore exclude all ambiguous grammars,
as well as all grammars that contain left recursion.
Solution:
#include<stdio.h>
#include<conio.h>
#include<string.h>
void main()
{
char fin[10][20],st[10][20],ft[20][20],fol[20][20];
int a=0,e,i,t,b,c,n,k,l=0,j,s,m,p;
clrscr();
printf("\n\nenter the no. of Grammer\n");
scanf("%d",&n);
printf("enter the productions in a grammar\n");
for(i=0;i<n;i++)
scanf("%s",st[i]);
for(i=0;i<n;i++)
fol[i][0]='\0';
for(s=0;s<n;s++)
{
for(i=0;i<n;i++)
{
j=3;
l=0;
a=0;
l1:if(!((st[i][j]>64)&&(st[i][j]<91)))
{
for(m=0;m<l;m++)
{
if(ft[i][m]==st[i][j])
goto s1;
}
ft[i][l]=st[i][j];
l=l+1;
s1:j=j+1;
}
else
{
if(s>0)
{
while(st[i][j]!=st[a][0])
{
a++;
}
b=0;
while(ft[a][b]!='\0')
{
for(m=0;m<l;m++)
OutPut:
First pos
FIRS[S] = ed
FIRS[C] = ed
Follow pos
FOLLOW[S] =$
FOLLOW[C] =ed$
M [S , e] =S->CC
M [S , d] =S->CC
M [C , e] =C->eC
M [C , d] =C->d
ALGORITHM
Start.
Declare two character arrays str[],token[] and initialize integer variables a=0,b=0,c,d.
Input the string from the user.
Repeat steps 5 to 12 till str[a] =’\0’.
If str[a] =='(' or str[a] =='{' then token[b] =’4’, b++.
If str[a] ==')' or str[a] =='}’ then token[b] =’5’, b++.
Check if isdigit(str[a]) then
Repeat steps 8 till isdigit(str[a])
a++.
a--, token[b] =’6’, b++.
Ifstr[a]=='+’then token[b]='2',b++.
If(str[a]=='*')then token[b]=’3’,b++.
a++.
token[b]='\0';
then print the token generated for the string .
b=0.
Repeat step 22 to 31 till token[b]!='\0'
c=0.
Repeat step 24 to 30 till (token[b] =='6' and token [b+1] =='2' and token [b+2] =='6') or (token[b] =='6'
and token[b+1]=='3'and token[b+2]=='6') or (token[b]=='4' and
token[b+1]=='6' and token[b+2]=='5') or (token[c]!='\0').
Token[c] ='6';
c++;
Repeat step 27 to 28 till token[c]! ='\0'. token[c]=token[c+2].
c++.
token[c-2]=’\0’.
print token.
b++.
Compare token with 6 and store the result in d.
If d=0 then print that the string is in the grammar.
Else print that the string is not in the grammar.
Stop.
Solution:
#include<stdio.h>
#include<conio.h>
#include<ctype.h>
#include<string.h>
void main()
{
int a=0,b=0,c;
char str[20],tok[11];
clrscr();
printf("Input the expression = ");
gets(str);
while(str[a]!='\0')
{
if((str[a]=='(')||(str[a]=='{'))
{
tok[b]='4';
b++;
}
if((str[a]==')')||(str[a]=='}'))
{
tok[b]='5';
b++;
}
if(isdigit(str[a]))
{
while(isdigit(str[a]))
{
a++;
}
a--;
tok[b]='6';
b++;
}
if(str[a]=='+')
{
tok[b]='2';
b++;
}
if(str[a]=='*')
{
tok[b]='3';
b++;
}
a++;
}
tok[b]='\0';
puts(tok);
b=0;
while(tok[b]!='\0')
{
if(((tok[b]=='6')&&(tok[b+1]=='2')&&(tok[b+2]=='6'))||
((tok[b]=='6')&&(tok[b+1 ]=='3')&&(tok[b+2]=='6'))||
((tok[b]=='4')&&(tok[b+1]=='6')&&(tok[b+2]=='5'))/*||((tok[b ]!=6)&&(tok[b+1]!='\0'))*/)
{
Output:
Theory:
An operator precedence parser is a bottom-up parser that interprets an operator-precedence grammar. An
operator-precedence parser is a simple shift-reduce parser that is capable of parsing a subset of LR (1)
grammars. More precisely, the operator-precedence parser can parse all LR (1) grammars where two
consecutive non terminals never appear in the right-hand side of any rule.
Operator-precedence parsers are not used often in practice; however they do have some properties that make
them useful within a larger design. First, they are simple enough to write by hand, which is not generally the
case with more sophisticated shift-reduce parsers. Second, they can be written to consult an operator table at
run time, which makes them suitable for languages that can add to or change their operators while parsing.
This page lists C operators in order of precedence (highest to lowest). Their associatively indicates in what
order operators of equal precedence in an expression are applied.
Note 1:
Parentheses are also used to group sub-expressions to force a different
precedence; such parenthetical expressions can be nested and are evaluated from
inner to outer.
Note 2:
Postfix increment/decrement have high precedence, but the actual increment or
decrement of the operand is delayed (to be accomplished sometime before the
statement completes execution). So in the statement y = x * z++; the current value
of z is used to evaluate the expression (i.e., z++ evaluates to z) and z only
incremented after all else is done.
Solution:
#include<stdio.h>
#include<conio.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>
struct stack
{
char nts,trs;
};
while(top!=0)
{
pop();
for(j=0;j<nop;j++)
{
char nont=p[j][3];
if(nont==nr)
{
r=nt_no(p[j][0]);
c=t_no(tr);
install(r,c,l);
}
}
}
}
}
}
}
void trailing()
{
char a;
int r,c,no,j,l;
top=0;
for(no=0;nt[no]!='\0';no++)
{
for(i=0;i<nop;i++)
{
if(nt[no]==p[i][0])
{
l=strlen(p[i]);
a=p[i][l-1];
if(terminal(a))
{
r=nt_no(p[i][0]);
c=t_no(a);
install(r,c,2);
}
while(top!=0)
{
pop();
for(j=0;j<nop;j++)
{
char nont=p[j][3];
l=strlen(p[j]);
if(nont==nr)
{
r=nt_no(p[j][0]);
c=t_no(tr);
install(r,c,2);
}
}
}
}
}
}
}
void prec_tab()
{
int r,c,no,j,l,pos,pl;
top=0;
for(no=0;no<nop;no++)
{
l=strlen(p[no]);
for(i=3;i<l-2;i++)
{
if(terminal(p[no][i])&& terminal(p[no][i+1]))
{
r=nt_no(p[no][i]);
c=t_no(p[no][i+1]);
opt[r][c]='=';
}
if(i<l-3)
{
if(terminal(p[no][i]) && terminal(p[no][i+2]) && nonterminal(p[no][i+1]))
{
r=nt_no(p[no][i]);
c=t_no(p[no][i+1]);
opt[r][c]='=';
}
}
if(terminal(p[no][i]) && nonterminal(p[no][i+1]))