CD Lab Manual
CD Lab Manual
List of Experiments:
EXERCISE – 1:
Write a c program to identify different types of tokens in a given program.
Description:
As it is known that Lexical Analysis is the first phase of compiler also known as scanner. It
converts the input program into a sequence of Tokens.
A C program consists of various tokens and a token is either a keyword, an identifier, a
constant, a string literal, or a symbol.
Example:
Keywords: for, if, include, etc
Identifier: variables, functions, etc
Separators: ‘,’, ‘;’, etc
Operators: ‘-’, ‘=’, ‘++’, etc
PROGRAM:
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
OUTPUT:
EXERCISE-2:
Write a Lex Program to implement a Lexical Analyzer using Lex tool.
DESCRIPTION:
LEX is a lexical analyzer generator
LEX is commonly used with the YACC parser generator.
LEX is a Unix utility which generates the lexical analyzer
LEX reads an input stream specifying the lexical analyzer and outputs source code
implementing the lexer in the C programming language.
LEX specification file can be created with an extension .l EX x.l.This is given to LEX
compiler to produce lex.yy.c
The lex.yy.c is a C program which is actually a lexical analyzer program. It consists of
the tabular representations of the transition diagrams constructed for the regular
expressions of specification file say x.l
Finally the C compiler compiles this generated lex.yy.c and produces an object program
a.out.
When some input stream is given to a.out then sequence of tokens get generated.
LEX program consists of three parts that are represented as follows
%{
Declaration Section
%}
%%
Rule Section
%%
Auxiliary Procedures Section
The following Commands are used to run the lex program x.l
$ lex x.l
$ cc lex.yy.c -ll
$ .\a.out
PROGRAM:
/* Program to implement lexical Analyzer using Lex tool*/
%{
int key=0;op=0;
%}
LETTER [a-z A-Z]
DIGIT [0-9]
%%
(begin|if|else|for|when|then) {key++;}
[-+/*<>=] {op++;}
(<=|>=|!=) {op++;}
%%
main()
{
yylex();
printf("\n no of keywords:%d",key);
printf("\n no of operators:%d",op);
}
OUTPUT:
[stu@bvcits ~]$ vi prog1.l
[stu@bvcits ~]$ lex prog1.l
[stu@bvcits ~]$ cc lex.yy.c -ll
[stu@bvcits ~]$ cat>file1
else
if
for
*
+
^z
[1]+ Stopped cat > file1
[stu@bvcits ~]$ ./a.out<file1
no of keywords:3
no of operators:2
EXERCISE – 3:
Write a C program to Simulate Lexical Analyzer to validating a given input String
DESCRIPTION:
LEX is a lexical analyzer generator
LEX is commonly used with the YACC parser generator.
LEX is a Unix utility which generates the lexical analyzer
LEX reads an input stream specifying the lexical analyzer and outputs source code
implementing the lexer in the C programming language.
LEX specification file can be created with an extension .l EX x.l.This is given to LEX
compiler to produce lex.yy.c
The lex.yy.c is a C program which is actually a lexical analyzer program. It consists of
the tabular representations of the transition diagrams constructed for the regular
expressions of specification file say x.l
Finally the C compiler compiles this generated lex.yy.c and produces an object program
a.out.
When some input stream is given to a.out then sequence of tokens get generated.
LEX program consists of three parts that are represented as follows
%{
Declaration Section
%}
%%
Rule Section
%%
Auxiliary Procedures Section
The following Commands are used to run the lex program x.l
$ lex x.l
$ cc lex.yy.c -ll
$ .\a.out
PROGRAM:
/* Program to implement Lexical Analyzer using Lextool*/
%{
int key=0;op=0;
%}
LETTER [a-z A-Z]
DIGIT [0-9]
%%
begin {printf("\n keyword:%s",yytext);}
if {printf("\n keyword:%s",yytext);}
else {printf("\n keyword:%s",yytext);}
for {printf("\n keyword:%s",yytext);}
while {printf("\n keyword:%s",yytext);}
do {printf("\n keyword:%s",yytext);}
"+" {printf("\n operator:%s",yytext);}
"*" {printf("\n operator:%s",yytext);}
"<" {printf("\n operator:%s",yytext);}
">" {printf("\n operator:%s",yytext);}
"<=" {printf("\n operator:%s",yytext);}
">=" {printf("\n operator:%s",yytext);}
{LETTER}({LETTER}|{DIGIT})* {printf("\n identifier:%s",yytext);}
{DIGIT} {printf("\n number:%s",yytext);}
%%
main()
{
yylex();
}
OUTPUT:
[stu@bvcits ~]$ vi prog.l
[stu@bvcits ~]$ lex prog.l
[stu@bvcits ~]$ cc lex.yy.c -ll
[stu@bvcits ~]$ cat>file1
while
do
for
else
*
+
9
2
7
^Z
[2]+ Stopped cat > file1
[stu@bvcits ~]$ ./a.out<file1
keyword:while
keyword:do
keyword:for
keyword:else
operator:*
operator:+
number:9
number:2
number:7
EXERCISE – 4:
Write a C program to implement Brute force technique of Top down parsing.
DESCRIPTION:
PARSING:
Parsing is the process of determining how a string of terminals can be generated by a
grammar
TOP-DOWN PARSING:
Top-down parsing can be viewed as an attempt to find a left most derivation for the input
starting from the root and creating nodes of a parse tree in preorder
Let us consider an example ScAd, Aab/a
For input W=cad to construct the parse tree for this sentence top down parser ,initially
create a tree consisting of a single node labeled “S”
An input pointer points to c, the first symbol of “W” Scad
Otherwise by replacing ab we represent it by Scabd
Otherwise we replace “A” by any other symbol like “d” that is Scdd is an invalid
string.
PROGRAM:
/*Program to implement Top down parsing*/
#include<stdio.h>
#include<string.h>
int A();
void disp();
void error();
char s[20];
int i;
void main()
{
printf("s->cAd \n");
printf("A->ab/a \n");
printf("Enter the String \n");
scanf("%s",&s);
i=0;
if(s[i++]=='c' && A())
{
` if(s[++i]=='d' && s[++i]==NULL)
disp();
else
error();
}
}
int A()
{
if(s[i++]=='a' && s[i]=='b')
return(1);
else if(s[--i]=='a')
return(1);
else
return(0);
}
void disp()
{
printf("\n String is Valid \n");
}
void error()
{
printf("\n String is Invalid \n");
}
OUTPUT:
ubuntu@ubuntu:~/$ cd cd
ubuntu@ubuntu:~/cd$ cc tdp.c
ubuntu@ubuntu:~/cd$ ./a.out
s->cAd
A->ab/a
Enter the String
cad
String is Valid
ubuntu@ubuntu:~/cd$ ./a.out
s->cAd
A->ab/a
Enter the String
cbd
String is Invalid
EXERCISE – 5:
Write a C program to implement Recursive-Descent parsing
DESCRIPTION:
PARSING:
Parsing is the process of determining how a string of terminals can be generated
by a grammar
RECURSIVE-DESCENT PARSING:
A parser that uses collection of recursive procedures for parsing the given input
string is called “Recursive Descent Parser.
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
Basic steps for construction. The RHS of the rule is directly converted into
program code symbol by symbol
The RHS of the rule is directly converted into program code symbol by symbol
If the input symbol is terminal then it is matched with look ahead from input .The
look ahead pointer has to be advanced on matching of the input symbol.
If the production rule has many alternatives then all these alternates has to be
combined into a single body of procedure
Let us take an example of grammar
ETE1
E1TE1/#
TFT1
T1*FT1/#
F(E)/d
When we enter the string as d+d*d it is a valid string.
When we enter the string as ddd it is a invalid string.
PROGRAM:
/*Program to implement Recursive descent parsing*/
#include<stdio.h>
int n,i=0;
char s[20];
void E(void);
void E1(void);
void T(void);
void T1(void);
void F(void);
void error(void);
void main()
{
printf("\n Given Grammar is \n");
printf("E->TE1\n");
printf("E1->TE1/# \n");
printf("T->FT1 \n");
printf("T1->*FT1/# \n");
printf("F->(E)/d \n");
printf("Enter the string you want to parse");
scanf("%s",&s);
E();
if(s[i]==NULL)
printf("String is Valid \n");
else
printf("String is Invalid \n");
}
void E()
{
T();
E1();
}
void T()
{
F();
T1();
}
void E1()
{
if(s[i]=='+')
{
i++;
T();
E1();
}
}
void T1()
{
if(s[i]=='*')
{
i++;
F();
T1();
}
}
void F()
{
if(s[i]=='(')
{
i++;
E();
if(s[i]==')')
i++;
else
error();
}
else if(s[i]=='d')
i++;
else
error();
}
void error()
{
printf("string is invalid \n");
}
OUTPUT:
Given Grammar is
E->TE1
E1->TE1/#
T->FT1
T1->*FT1/#
F->(E)/d
Enter the string you want to parse(d)
String is Valid
Given Grammar is
E->TE1
E1->TE1/#
T->FT1
T1->*FT1/#
F->(E)/d
Enter the string you want to parse(id)*(id)
String is Invalid
EXERCISE – 6:
Write a C program to compute the First and Follow sets for the given grammar
DESCRIPTION:
The functions follow and followfirst() are both involved in the calculation of the Follow
Set of a given Non-Terminal. The follow set of the start symbol will always contain
“$”. Now the calculation of Follow falls under three broad cases
If a Non-Terminal on the R.H.S. of any production is followed immediately by a
Terminal then it can immediately be included in the Follow set of that Non-Terminal.
If a Non-Terminal on the R.H.S. of any production is followed immediately by a Non-
Terminal, then the First Set of that new Non-Terminal gets included on the follow set of
our original Non-Terminal. In case encountered an epsilon i.e. ” # ” then, move on to
the next symbol in the production.
Note: “#” is never included in the Follow set of any Non-Terminal.
If reached the end of a production while calculating follow, then the Follow set of that
non-terminal will include the Follow set of the Non-Terminal on the L.H.S. of that
production. This can easily be implemented by recursion.
PROGRAM:
#include<stdio.h>
#include<ctype.h>
#include<string.h>
void followfirst(char, int, int);
void follow(char c);
void findfirst(char, int, int);
int count, n = 0;
char calc_first[10][100];
char calc_follow[10][100];
int m = 0;
char production[10][10];
char f[10], first[10];
int k;
char ck;
int e;
if (xxx == 1)
continue;
land += 1;
follow(ck);
ptr += 1;
donee[ptr] = ck;
printf(" Follow(%c) = { ", ck);
calc_follow[point1][point2++] = ck;
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;
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')
{
followfirst(production[i][j+1], i, (j+2));
}
if(production[i][j+1]=='\0' && c!=production[i][0])
{
follow(production[i][0]);
}
}
}
}
}
void findfirst(char c, int q1, int q2)
{
int j;
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))
{
findfirst(production[q1][q2], q1, (q2+1));
}
else
first[n++] = '#';
}
else if(!isupper(production[j][2]))
{
first[n++] = production[j][2];
}
else
{
findfirst(production[j][2], j, 3);
}
}
}
}
OUTPUT :
First(E)= { (, i, }
First(R)= { +, #, }
First(T)= { (, i, }
First(Y)= { *, #, }
First(F)= { (, i, }
-----------------------------------------------
Follow(E) = { $, ), }
Follow(R) = { $, ), }
Follow(T) = { +, $, ), }
Follow(Y) = { +, $, ), }
Follow(F) = { *, +, $, ), }
EXERCISE -7:
Write a C program for eliminating the Left Recursion and Left Factoring of a given
grammar.
DESCRIPTION:
LEFT FACTORING:
Left factoring is used to convert a left-factored grammar into an equivalent grammar to
remove the uncertainty for the top-down parser. In left factoring, we separate the
common prefixes from the production rule.
The following algorithm is used to perform left factoring in the grammar-
A ⇒ αA`
common prefix.
A` ⇒ β1 | β2 | β3 | …… | βn
The top-down parser can easily parse this grammar to derive a given string. So this is
how left factoring in compiler design is performed on a given grammar.
PROGRAM:
#include<stdio.h>
#include<string.h>
int main()
{
char gram[20],part1[20],part2[20],modifiedGram[20],newGram[20],tempGram[20];
int i,j=0,k=0,l=0,pos;
printf("Enter Production : A->");
gets(gram);
for(i=0;gram[i]!='|';i++,j++)
part1[j]=gram[i];
part1[j]='\0';
for(j=++i,i=0;gram[j]!='\0';j++,i++)
part2[i]=gram[j];
part2[i]='\0';
for(i=0;i<strlen(part1)||i<strlen(part2);i++)
{
if(part1[i]==part2[i])
{
modifiedGram[k]=part1[i];
k++;
pos=i+1;
}
}
for(i=pos,j=0;part1[i]!='\0';i++,j++){
newGram[j]=part1[i];
}
newGram[j++]='|';
for(i=pos;part2[i]!='\0';i++,j++){
newGram[j]=part2[i];
}
modifiedGram[k]='X';
modifiedGram[++k]='\0';
newGram[j]='\0';
printf("\n A->%s",modifiedGram);
printf("\n X->%s\n",newGram);
}
OUTPUT:
Enter Production : A->aE+bcD|aE+eIT
A->aE+X
X-> bcD| eIT
LEFT RECURSION:
A Grammar G (V, T, P, S) is left recursive if it has a production in the form.
A → A α |β.
The above Grammar is left recursive because the left of production is occurring at a first
position on the right side of production. It can eliminate left recursion by replacing a pair
of production with
A → βA′
A → αA′|ϵ
Elimination of Left Recursion
Left Recursion can be eliminated by introducing new non-terminal A such that.
PROGRAM:
#include<stdio.h>
#include<string.h>
void main()
{
char input[100],l[50],r[50],temp[10],tempprod[20],productions[25][50];
int i=0,j=0,flag=0,consumed=0;
printf("Enter the productions: ");
scanf("%1s->%s",l,r);
printf("%s",r);
while(sscanf(r+consumed,"%[^|]s",temp) == 1 && consumed <= strlen(r))
{
if(temp[0] == l[0])
{
flag = 1;
sprintf(productions[i++],"%s->%s%s'\0",l,temp+1,l);
}
else
sprintf(productions[i++],"%s'->%s%s'\0",l,temp,l);
consumed += strlen(temp)+1;
}
if(flag == 1)
{
sprintf(productions[i++],"%s->ε\0",l);
printf("The productions after eliminating Left Recursion are:\n");
for(j=0;j<i;j++)
printf("%s\n",productions[j]);
}
else
printf("The Given Grammar has no Left Recursion");
}
OUTPUT:
Enter the productions: E->E+E|T
The productions after eliminating Left Recursion are:
E->+EE'
E'->TE'
E->ε
EXERCISE – 8:
Write a C Program to check the validity of input String using Predictive Parser
DESCRIPTION:
PARSING:
Parsing is the process of determining how a string of terminals can be generated by a
grammar
LL(1) PARSING:
An LL parser is a top-down parser for a subset of the context-free grammars. It parses
the input from left to right, and constructs a left most derivation of the sentence
For LL(1) – The first Lmeans the input is scaned from left to right, The second L means
it uses left most derivation for input string. And the number 1 in the input symbol means
it uses only one input symbol to predict the parsing process.
The class of grammars which are parse table in this way is known as the LL grammars
Am LL parser is called an LL(k) parser if it uses k tokens of look ahead when parsing a
sentence.
If such a parser exists for a certain grammar and it can parse sentences of this grammar
with out back tracking then it is called as LL(k) grammar.
A language that has an LL(k) grammar is known as an LL(k) language.
Procedure for constructing LL(1)Parser
1. Computation of FIRST,FOLLOW Functions
2. Construct the Predictive Parsing Table Using FIRST ,FOLLOW functions
3. Parse the input string with the help of Predictive Parsing Table.
PROGRAM:
#include<stdio.h>
#include<string.h>
int i,j,k,r,l,c;
char ch;
char prod[8][7]={"E->TA","A->+TA","A->@","T->FB","B->*FB","B->@","F->(E)","F->i"};
char first[8][8]={"i","+","@","i","*","@","(","i"};
char follow[8][5]={"$)","+)$","@$","*)$","@)$",")$","$)"};
char table[6][7][6];
main()
{
char trow[6]={'i','+','*','(','$',')'};
char tcol[5]={'E','A','T','B','F'};
printf("--------------------\n");
for(j=0;j<6;j+)
{
printf("\t %c",trow[j]);
}
printf("\n");
printf("--------------------\n");
for(i=0;i<8;i++)
{
if(strcmp(first[i],"@")==0)
k=strlen(follow[i]);
else
k=strlen(first[i]);
for(j=0;j<k;j++)
{
if(strcmp(first[i],"@")==0)
ch=follow[i][j];
else
ch=first[i][j];
r=row();
c=col();
strcpy(table[r][c],prod[i]);
}
}
for(i=0;i<5;i++)
{
printf("%c \t",tcol[i]);
for(j=0;j<6;j++)
{
printf("%s \t",table[i][j]);
}
printf("\n");
}
printf("----------\n");
}
row()
{
switch(prod[i][0])
{
case 'E':return 0;
case 'A':return 1;
case 'T':return 2;
case 'B':return 3;
case 'F':return 4;
}
}
col()
{
switch(ch)
{
case 'i':return 0;
case '+':return 1;
case '*':return 2;
case '(':return 3;
case ')':return 4;
case '$':return 5;
}
}
OUTPUT:
ubuntu@ubuntu:~/$ cd cd
ubuntu@ubuntu:~/cd$ cc ll.c
ubuntu@ubuntu:~/cd$ ./a.out
------------------------------------------------------------------------------------------------------------
i + * ( $ )
------------------------------------------------------------------------------------------------------------
E E->TA
A A->+TA A->@
T T->FB
B B->*FB B->@ B->@
F F->i F->(E)
-----------------------------------------------------------------------------------------------------------
EXERCISE – 9:
Write a C program to implement LR parsing algorithm to accept a given input string
DECRIPTION:
PROGRAM:
#include<stdio.h>
#include<string.h>
char stack[30];
int top=-1;
void push(char c)
{
top++;
stack[top]=c;
}
char pop()
{
char c;
if(top!=-1)
{
c=stack[top];
top--;
return c;
}
return 'x';
}
void printstat()
{
int i;
printf("\n\t\t\t $");
for(i=0;i<=top;i++)
printf("%c",stack[i]);
}
void main()
{
int i,j,k,l;
char s1[20],s2[20],ch1,ch2,ch3;
clrscr();
printf("\n\n\t\t LR PARSING");
printf("\n\t\t ENTER THE EXPRESSION");
scanf("%s",s1);
l=strlen(s1);
j=0;
printf("\n\t\t $");
for(i=0;i<l;i++)
{
if(s1[i]=='i' && s1[i+1]=='d')
{
s1[i]=' ';
s1[i+1]='E';
printstat();
printf("id");
push('E');
printstat();
}
else if(s1[i]=='+'||s1[i]=='-'||s1[i]=='*' ||s1[i]=='/' ||s1[i]=='d')
{
push(s1[i]);
printstat();
}
}
printstat();
l=strlen(s2);
while(l)
{
ch1=pop();
if(ch1=='x')
{
printf("\n\t\t\t $");
break;
}
if(ch1=='+'||ch1=='/'||ch1=='*'||ch1=='-')
{
ch3=pop();
if(ch3!='E')
{
printf("errror");
exit();
}
else
{
push('E');
printstat();
}
}
ch2=ch1;
}
getch();
}
OUTPUT:
LR PARSING
ENTER THE EXPRESSION
id+id*id-id
$
$id
$E
$E+
$E+id
$E+E
$E+E*
$E+E*id
$E+E*E
$E+E*E-
$E+E*E-id
$E+E*E-E
$E+E*E-E
$E+E*E
$E
$
EXERCISE – 10:
Write a C program for implementation of a Shift Reduce Parser using Stack Data
Structure to accept a given input string of a given grammar.
DESCRIPTION:
PARSING:
Parsing is the process of determining how a string of terminals can be generated
by a grammar
SHIFT REDUCE PARSING:
A Shift-Reduce parser uses a parse stack which contains grammar symbols.
During the operation of the parser , symbols from the input are shifted on to the
stack.
If a prefix of the symbols on top of the stack matches the RHS of a grammar rule
which is the correct rule to use with in the current context, then the parser reduces
the RHS of the rule to its LHS, replacing the RHS symbols on top of the stack
with non terminal occurring on the LHS of the rule.
This shift reduce process continues until the parser terminates reporting either
success or failure
It terminates with success when the input is legal and is accepted by the parser. it
terminates with failure if an error is detected in the input
The operation of the parser is controlled by a couple of tables:
ACTION table:-
The action table is a table with rows indexed by states and columns indexed by
terminal symbols. when the parser is in some state s and the current look ahead
terminal is t, the action taken by the parser depends on the contents of action[s]
[t],which contain 4 different kinds of entries:
Shift‘s’: Shift state‘s’ on to the parse stack
Reduce ‘r’: Reduce by rule ‘r’. This is explained in more detail below
Accept: Terminate the parse with success, accepting the input.
Error: Signal a parse error
GOTO Table:
The goto table is a table with rows indexed by states and columns indexed by non
terminal symbols. when the parser is in state s immediately after reducing by rule
N, then next state to enter is given by goto[s][N].
PROGRAM:
/*Program to implement Shift reduce parsing*/
#include<stdio.h>
#include<string.h>
void error();
void shift(char);
void reduce(char);
int top=0,k;
char stk[10];
char str[10];
int i,p;
void main()
{
char a[6][10]={"E+E","E*E","E-E","E/E","i"};
printf("Enter the string");
printf("\n E->E+E/E*E/E-E/E/E/i \n");
scanf("%s",str);
strcat(str,"$");
stk[top++]='$';
printf("\n stack input maintaining \n");
printf("-----------------------------");
do
{
printf("\n %s \t",stk);
for(i=p;str[i]!='\0';i++)
printf("%c",str[i]);
switch(str[p])
{
case '+': shift(str[p]);
break;
case '-': shift(str[p]);
break;
case '*': shift(str[p]);
break;
case '/': shift(str[p]);
break;
case 'i': shift(str[p]);
reduce('E');
break;
case '$': if(str[p+1]==NULL)
break;
else
error();
default: error();
}
}
while(str[p++]!='$');
if(stk[top-1]=='E' &&stk[top-2]=='E' && stk[--p]=='$')
error();
if(stk[--top]=='E' && str[--p]=='$')
{
printf("\n $%c\t",stk[top]);
for(i=p;str[i]!='\0';i++)
printf("%c",str[i]);
printf("\n string is valid \n");
}
else
error();
}
void error()
{
printf("\n string is invalid \n");
}
OUTPUT:
ubuntu@ubuntu:~/$ cd cd
ubuntu@ubuntu:~/cd$ cc srp.c
ubuntu@ubuntu:~/cd$ ./a.out
Enter the string
E->E+E/E*E/E-E/E/E/i
i+i
stack input maintaining
-----------------------------------------
$ i+i$
$E +i$
$E+ i$
$E+E $
$E $
string is valid
ubuntu@ubuntu:~/cd$ ./a.out
Enter the string
E->E+E/E*E/E-E/E/E/i
e
DESCRIPTION:
A YACC source program has three parts as follows:
Declarations
%%
translation rules
%%
supporting C routines
Declarations Section: This section contains entries that:
i. Include standard I/O header file.
ii. Define global variables.
iii. Define the list rule as the place to start processing.
iv. Define the tokens used by the parser. v. Define the operators and their precedence.
Rules Section: The rules section defines the rules that parse the input stream. Each rule of a
grammar production and the associated semantic action.
Programs Section: The programs section contains the following subroutines. Because these
subroutines are included in this file, it is not necessary to use the yacc library when processing
this file.
Main- The required main program that calls the yyparse subroutine to start the program.
yywrap -The wrap-up subroutine that returns a value of 1 when the end of input occurs. The
calc.lex file contains include statements for standard input and output, as programmar file
information if we use the -d flag with the yacc command. The y.tab.h file contains definitions for
the tokens that the parser program uses.
calc.lex contains the rules to generate these tokens from the input stream.
PROGRAM:
//Implementation of calculator using LEX and YACC
LEX PART:
%{
#include<stdio.h>
#include "y.tab.h"
extern int yylval;
%}
%%
[0-9]+ { yylval=atoi(yytext);
return NUMBER;
}
[\t] ;
[\n] return 0;
. return yytext[0];
%%
int yywrap()
{
return 1;
}
YACC PART:
%{
#include<stdio.h>
int flag=0;
%}
%token NUMBER
%left '+' '-'
%left '*' '/' '%'
%left '(' ')'
%%
ArithmeticExpression: E{ printf("\nResult=%d\n",$$);
return 0;
};
E: E'+'E {$$=$1+$3;}
|E'-'E {$$=$1-$3;}
|E'*'E {$$=$1*$3;}
|E'/'E {$$=$1/$3;}
|E'%'E {$$=$1%$3;}
|'('E')' {$$=$2;}
| NUMBER {$$=$1;}
;
%%
void main()
{
void yyerror()
{
printf("\nEntered arithmetic expression is Invalid\n\n");
flag=1;
}
OUTPUT:
1. Enter Any Arithmetic Expression which can have operations Addition, Subtraction,
Multiplication, Divison, Modulus and Round brackets:
5*6(1+2)
Result = 90
Entered arithmetic expression is Valid
2. Enter Any Arithmetic Expression which can have operations Addition, Subtraction,
Multiplication, Divison, Modulus and Round brackets:
5*6(1+2
Entered arithmetic expression is Invalid
EXERCISE – 12:
Generate YACC specification for a few syntactic categories.
DESCRIPTION:
A YACC source program has three parts as follows:
Declarations
%%
translation rules
%%
supporting C routines
Declarations Section: This section contains entries that:
i. Include standard I/O header file.
ii. Define global variables.
iii. Define the list rule as the place to start processing.
iv. Define the tokens used by the parser. v. Define the operators and their precedence.
Rules Section: The rules section defines the rules that parse the input stream. Each rule of a
grammar production and the associated semantic action.
Programs Section: The programs section contains the following subroutines. Because these
subroutines are included in this file, it is not necessary to use the yacc library when processing
this file.
PROGRAM:
%{
#include “y.tab.h”
%}
%%
“=” {printf(“\n Operator is EQUAL”);}
“+” {printf(“\n Operator is PLUS”);}
“-“ {printf(“\n Operator is MINUS”);}
“/” {printf(“\n Operator is DIVISION”);}
“*” {printf(“\n Operator is MULTIPLICATION”);}
[a-z A-Z]*[0-9]* {
printf(“\n Identifier is %s”,yytext);
return ID;
}
return yytext[0];
\n return 0;
%%
int yywrap()
{
return 1;
}
%{
#include
%}
%%
statement: A’=’E
E{
printf(“\n Valid arithmetic expression”);
$$ = $1;
};
E: E’+’ID
| E’-’ID
| E’*’ID
| E’/’ID
| ID
;
%%
extern FILE *yyin;
main()
{
do
{
yyparse();
}while(!feof(yyin));
}
yyerror(char*s)
{
}
OUTPUT:
[root@localhost]# lex arith_id.1
[root@localhost]# yacc –d arith_id.y
[root@localhost]# gcc lex.yy.c y.tab.c
[root@localhost]# ./a.out
x=a+b;
Identifier is x
Operator is EQUAL
Identifier is a
Operator is PLUS
Identifier is b
(b) Program to recognize a valid variable which starts with a letter followed by any
number of letters or digits.
%{
#include "y.tab.h"
%}
%%
"int " {return INT;}
"float" {return FLOAT;}
"double" {return DOUBLE;}
[a-zA-Z]*[0-9]*{
printf("\nIdentifier is %s",yytext);
return ID;
}
return yytext[0];
\n return 0;
int yywrap()
{
return 1;
}
OUTPUT:
[root@localhost]# lex variable_test.I
[root@localhost]# yacc –d variable_test.y
[root@localhost]# gcc lex.yy.c y.tab.c
[root@localhost]# ./a.out
int a,b;
Identifier is a
Identifier is b
[root@localhost]#
EXERCISE – 13:
Write a C program for generating the three address code of a given expression/statement.
DESCRIPTION:
Three address code is a type of intermediate code which is easy to generate and can be easily
converted to machine code. It makes use of at most three addresses and one operator to
represent an expression and the value computed at each instruction is stored in temporary
variable generated by compiler. The compiler decides the order of operation given by three
address code.
General representation –
a = b op c
Where a, b or c represents operands like names, constants or compiler generated temporaries
and op represents the operator
Example: a * – (b + c)
Three address code statements are
t1=b+c
t2=unary minus t1
t3=a*t2
PROGRAM:
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<string.h>
struct three
{
char data[10],temp[7];
}s[30];
void main()
{
char d1[7],d2[7]="t";
int i=0,j=1,len=0;
FILE *f1,*f2;
clrscr();
f1=fopen("sum.txt","r");
f2=fopen("out.txt","w");
while(fscanf(f1,"%s",s[len].data)!=EOF)
len++;
itoa(j,d1,7);
strcat(d2,d1);
strcpy(s[j].temp,d2);
strcpy(d1,"");
strcpy(d2,"t");
if(!strcmp(s[3].data,"+"))
{
fprintf(f2,"%s=%s+%s",s[j].temp,s[i+2].data,s[i+4].data);
j++;
}
else if(!strcmp(s[3].data,"-"))
{
fprintf(f2,"%s=%s-%s",s[j].temp,s[i+2].data,s[i+4].data);
j++;
}
for(i=4;i<len-2;i+=2)
{
itoa(j,d1,7);
strcat(d2,d1);
strcpy(s[j].temp,d2);
if(!strcmp(s[i+1].data,"+"))
fprintf(f2,"\n%s=%s+%s",s[j].temp,s[j-1].temp,s[i+2].data);
else if(!strcmp(s[i+1].data,"-"))
fprintf(f2,"\n%s=%s-%s",s[j].temp,s[j-1].temp,s[i+2].data);
strcpy(d1,"");
strcpy(d2,"t");
j++;
}
fprintf(f2,"\n%s=%s",s[0].data,s[j-1].temp);
fclose(f1);
fclose(f2);
getch();
}
INPUT: sum.txt
OUTPUT : out.txt
t1=in1+in2
t2=t1+in3
t3=t2-in4
out=t3
EXERCISE -14:
DESCRIPTION:
The algorithm takes a sequence of three-address statements as input. For each three address
statement of the form a:= b op c perform the various actions. These are as follows:
1. Invoke a function getreg() to find out the location L where the result of computation b op
c should be stored.
2. Consult the address description for y to determine y'. If the value of y currently in
memory and register both then prefer the register y' . If the value of y is not already in L
then generate the instruction MOV y' , L to place a copy of y in L.
3. Generate the instruction OP z' , L where z' is used to show the current location of z. if z
is in both then prefer a register to a memory location. Update the address descriptor of x
to indicate that x is in location L. If x is in L then update its descriptor and remove x from
all other descriptor.
4. If the current value of y or z have no next uses or not live on exit from the block or in
register then alter the register descriptor to indicate that after execution of x : = y op z
those register will no longer contain y or z.
PROGRAM:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<ctype.h>
#include<process.h>
typedef struct
{
char var[10];
int alive;
}
regist;
regist preg[10];
void substring(char exp[],int st,int end)
{
int i,j=0;
char dup[10]="";
for(i=st;i<end;i++)
dup[j++]=exp[i];
dup[j]='0';
strcpy(exp,dup);
}
int getregister(char var[])
{
int i;
for(i=0;i<10;i++)
{
if(preg[i].alive==0)
{
strcpy(preg[i].var,var);
break;
}
}
return(i);
}
void getvar(char exp[],char v[])
{
int i,j=0;
char var[10]="";
for(i=0;exp[i]!='\0';i++)
if(isalpha(exp[i]))
var[j++]=exp[i];
else
break;
strcpy(v,var);
}
void main()
{
char basic[10][10],var[10][10],fstr[10],op;
int i,j,k,reg,vc,flag=0;
clrscr();
printf("\nEnter the Three Address Code:\n");
for(i=0;;i++)
{
gets(basic[i]);
if(strcmp(basic[i],"exit")==0)
break;
}
printf("\nThe Equivalent Assembly Code is:\n");
for(j=0;j<i;j++)
{
getvar(basic[j],var[vc++]);
strcpy(fstr,var[vc-1]);
substring(basic[j],strlen(var[vc-1])+1,strlen(basic[j]));
getvar(basic[j],var[vc++]);
reg=getregister(var[vc-1]);
if(preg[reg].alive==0)
{
printf("\nMov R%d,%s",reg,var[vc-1]);
preg[reg].alive=1;
}
op=basic[j][strlen(var[vc-1])];
substring(basic[j],strlen(var[vc-1])+1,strlen(basic[j]));
getvar(basic[j],var[vc++]);
switch(op)
{
case '+': printf("\nAdd"); break;
case '-': printf("\nSub"); break;
case '*': printf("\nMul"); break;
case '/': printf("\nDiv"); break;
}
flag=1;
for(k=0;k<=reg;k++)
{
if(strcmp(preg[k].var,var[vc-1])==0)
{
printf("R%d, R%d",k,reg);
preg[k].alive=0;
flag=0;
break;
}
}
if(flag)
{
printf(" %s,R%d",var[vc-1],reg);
printf("\nMov %s,R%d",fstr,reg);
}
strcpy(preg[reg].var,var[vc-3]);
getch();
}
}
OUTPUT:
Enter the Three Address Code:
a=b+c
c=a*c
exit