EXP - 4 - To - 6CD - Lab Manual - ODD - 2024 - Removed
EXP - 4 - To - 6CD - Lab Manual - ODD - 2024 - Removed
Experiment No - 4
Aim: Introduction to YACC and generate Calculator Program
Skills:
• Understanding of YACC and its role in compiler construction
• Ability to write grammar rules and YAAC programs for a given language
• Ability to develop program using YACC
Objectives:
By the end of this experiment, the students should be able to:
➢ Understand the concept of YACC and its significance in compiler construction
➢ Write grammar rules for a given language
➢ Implement a calculator program using YACC
Theory:
YACC (Yet Another Compiler Compiler) is a tool that is used for generating parsers. It is used in
combination with Lex to generate compilers and interpreters. YACC takes a set of rules and
generates a parser that can recognize and process the input according to those rules.
The grammar rules that are defined using YACC are written in BNF (Backus-Naur Form) notation.
These rules describe the syntax of a programming language.
INPUT FILE:
→ The YACC input file is divided into three parts.
/* definitions */
....
%%
/* rules */
....
%%
/* auxiliary routines */
....
Definition Part:
→ The definition part includes information about the tokens used in the syntax definition.
Rule Part:
→ The rules part contains grammar definition in a modified BNF form. Actions is C code in { }
and can be embedded inside (Translation schemes).
The program for generating a calculator using YACC involves the following steps:
➢ Defining the grammar rules for the calculator program
➢ Writing the Lex code for tokenizing the input
➢ Writing the YACC code for parsing the input and generating the output
Program:
calculator.l
%option noyywrap
%%
%%
calculator.y
%{
#include <stdio.h>
#include <stdlib.h>
int yylex(void);
void yyerror();
%}
%token NUMBER
%%
lines:
Compiler Design (3170701)
| lines expr '\n' { printf("Result: %d\n", $2); }
;
expr:
expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr { $$ = $1 / $3; }
| NUMBER { $$ = $1; }
;
%%
#include "lex.yy.c"
int main() {
printf("Enter expressions (Ctrl+D to exit):\n");
yyparse();
return 0;
}
void yyerror()
{}
The provided code implements a basic calculator using Yacc and Lex. It parses and evaluates arithmetic
expressions, printing the results. The lexer (calculator.l) recognizes numbers, operators, and newlines,
while the parser (calculator.y) defines the grammar rules for expressions. The main function reads input
expressions from the user, parses and evaluates them, and prints the results. This code demonstrates a
simple example of using Yacc and Lex to build a calculator, which can be extended for more complex
functionality.
Quiz:
1. What is YACC?
2. What is the purpose of YACC?
3. What is the output of YACC?
4. What is a syntax analyzer?
5. What is the role of a lexical analyzer in YACC?
Compiler Design (3170701)
Experiment No - 5
Aim: Implement a program for constructing
a. LL(1) Parser
b. Predictive Parser
Skills:
• Understanding Parsers and its role in compiler construction
• Ability to write first and follow for given grammar
• Ability to develop LL(1) and predictive parser using top down parsing approach
Objectives:
By the end of this experiment, the students should be able to:
➢ Understand the concept parsers and its significance in compiler construction
➢ Write first and follow set for given grammar
➢ Implement a LL(1) and predictive grammar using top down parser
Software/Equipment: C compiler
Theory:
❖ LL(1) Parsing: Here the 1st L represents that the scanning of the Input will be done from
the Left to Right manner and the second L shows that in this parsing technique, we are
going to use the Left most Derivation Tree. And finally, the 1 represents the number of
look-ahead, which means how many symbols are you going to see when you want to make
a decision.
❖ Predictive Parser
Predictive parser is a recursive descent parser, which has the capability to predict which production
is to be used to replace the input string. The predictive parser does not suffer from backtracking.
To accomplish its tasks, the predictive parser uses a look-ahead pointer, which points to the next
input symbols. To make the parser back-tracking free, the predictive parser puts some constraints
on the grammar and accepts only a class of grammar known as LL(k) grammar.
Predictive parsing uses a stack and a parsing table to parse the input and generate a parse tree. Both
the stack and the input contains an end symbol $ to denote that the stack is empty and the input is
consumed. The parser refers to the parsing table to take any decision on the input and stack element
combination.
In recursive descent parsing, the parser may have more than one production to choose from for a
single instance of input, whereas in predictive parser, each step has at most one production to
choose. There might be instances where there is no production matching the input string, making
the parsing procedure to fail.
Program-1:
#include <stdio.h>
#include <string.h>
char input[100];
int pos = 0;
void S();
void A();
void error() {
printf("Error parsing input\n");
Compiler Design (3170701)
exit(1);
}
void S() {
if (input[pos] == 'a') {
match('a');
A();
match('d');
} else {
error();
}
}
void A() {
if (input[pos] == 'b') {
match('b');
A();
} else if (input[pos] == 'c') {
match('c');
} else {
error();
}
}
int main() {
printf("Enter string: ");
scanf("%s", input);
S();
if (input[pos] == '\0') {
printf("Input is valid\n");
} else {
printf("Error: Incomplete parse\n");
}
return 0;
}
Program -2:
#include <stdio.h>
#include <string.h>
int lookahead = 0;
char input[100];
void E() {
T();
E_prime();
}
void E_prime() {
if (input[lookahead] == '+') {
match('+');
T();
E_prime();
}
}
void T() {
F();
T_prime();
}
void T_prime() {
if (input[lookahead] == '*') {
match('*');
F();
T_prime();
}
}
void F() {
if (input[lookahead] == '(') {
match('(');
E();
match(')');
} else if (input[lookahead] == 'i') {
match('i');
} else {
printf("Syntax Error\n");
exit(1);
}
}
int main() {
printf("Enter input string: ");
scanf("%s", input);
E();
if (input[lookahead] == '\0')
printf("Input is valid\n");
Compiler Design (3170701)
else
printf("Syntax Error\n");
return 0;
}
Program -1:
The program is a simple checker to see if a string follows a specific pattern. The pattern is:
• The string must start with the letter 'a'.
• After 'a', there can be any number of the letter 'b'.
• Then, there must be the letter 'c'.
• Finally, the string must end with the letter 'd'.
So, valid strings include:
• "aAd"
• "abAd"
• "abbAd"
• "abcAd"
Invalid strings include:
• "ad"
• "abd"
• "acd"
• "bc"
• "abc"
The program checks the input string against this pattern and tells you if it's valid or not
Program-2:
The program is a simple checker to see if an arithmetic expression is valid. It checks if the expression
follows the rules of arithmetic, such as:
• Parentheses must be balanced.
• Operators must be used correctly (e.g., '+' between two numbers).
• Identifiers must be used correctly (e.g., 'i' can be used as a variable).
If the expression follows these rules, the program says it's valid. Otherwise, it says there's a syntax error.
Compiler Design (3170701)
Quiz:
1. What is a parser and state the Role of it?
2. Types of parsers? Examples to each.
3. What are the Tools available for implementation?
4. How do you calculate FIRST(),FOLLOW() sets used in Parsing Table construction?
5. Name the most powerful parser.
Compiler Design (3170701)
Experiment No - 06
Aim: Implement a program for constructing
a. Recursive Decent Parser (RDP)
b. LALR Parser
Skills:
• Understanding of RDP and bottom up parsers and its role in compiler construction
• Ability to write acceptance of string through RDP and parsing of string using LALR
parsers for a given grammar
• Ability to develop RDP and LALR parser using bottom up approach
Objectives:
By the end of this experiment, the students should be able to:
➢ Understand the RDP ,broad classification of bottom up parsers and its significance in
compiler construction
➢ Verifying whether the string is accepted for RDP, a given grammar is parsed using LR
parsers.
➢ Implement a RDP and LALR parser
Software/Equipment: C compiler
Theory:
Recursive Descent Parser uses the technique of Top-Down Parsing without backtracking. It can be
defined as a Parser that uses the various recursive procedure to process the input string with no
backtracking. It can be simply performed using a Recursive language. The first symbol of the string
of R.H.S of production will uniquely determine the correct alternative to choose.
The major approach of recursive-descent parsing is to relate each non-terminal with a procedure.
The objective of each procedure is to read a sequence of input characters that can be produced by
the corresponding non-terminal, and return a pointer to the root of the parse tree for the non-
terminal. The structure of the procedure is prescribed by the productions for the equivalent non-
terminal.
The recursive procedures can be simply to write and adequately effective if written in a language
that executes the procedure call effectively. There is a procedure for each non-terminal in the
grammar. It can consider a global variable lookahead, holding the current input token and a
procedure match (Expected Token) is the action of recognizing the next token in the parsing process
and advancing the input stream pointer, such that lookahead points to the next token to be parsed.
Match () is effectively a call to the lexical analyzer to get the next token.
For example, input stream is a + b$.
lookahead == a
Compiler Design (3170701)
match()
lookahead == +
match ()
lookahead == b
……………………….
……………………….
In this manner, parsing can be done.
LALR refers to the lookahead LR. To construct the LALR (1) parsing table, we use the
canonical collection of LR (1) items.
In the LALR (1) parsing, the LR (1) items which have same productions but different look
ahead are combined to form a single set of items
LALR (1) parsing is same as the CLR (1) parsing, only difference in the parsing table.
Example
S → AA
A → aA
A→b
Add Augment Production, insert '•' symbol at the first position for every production in G
and also add the look ahead.
S` → •S, $
S → •AA, $
A → •aA, a/b
A → •b, a/b
I0 State:
Add Augment production to the I0 State and Compute the ClosureL
I0 = Closure (S` → •S)
Add all productions starting with S in to I0 State because "•" is followed by the non-
terminal. So, the I0 State becomes
I0 = S` → •S, $
S → •AA, $
Add all productions starting with A in modified I0 State because "•" is followed by the
non-terminal. So, the I0 State becomes.
I0= S` → •S, $ S
→ •AA, $
A → •aA, a/b
A → •b, a/b
I1= Go to (I0, S) = closure (S` → S•, $) = S` → S•, $
I2= Go to (I0, A) = closure ( S → A•A, $ )
Add all productions starting with A in I2 State because "•" is followed by the non-
terminal. So, the I2 State becomes
I2= S → A•A, $
A → •aA, $
A → •b, $
I3= Go to (I0, a) = Closure ( A → a•A, a/b )
Add all productions starting with A in I3 State because "•" is followed by the non-
terminal. So, the I3 State becomes
Compiler Design (3170701)
I3= A → a•A, a/b
A → •aA, a/b
A → •b, a/b
Go to (I3, a) = Closure (A → a•A, a/b) = (same as I3)
Go to (I3, b) = Closure (A → b•, a/b) = (same as I4)
I4= Go to (I0, b) = closure ( A → b•, a/b) = A → b•, a/b
I5= Go to (I2, A) = Closure (S → AA•, $) =S → AA•, $
I6= Go to (I2, a) = Closure (A → a•A, $)
Add all productions starting with A in I6 State because "•" is followed by the non-
terminal. So, the I6 State becomes
I6 = A → a•A, $
A → •aA, $
A → •b, $
Go to (I6, a) = Closure (A → a•A, $) = (same as I6)
Go to (I6, b) = Closure (A → b•, $) = (same as I7)
I7= Go to (I2, b) = Closure (A → b•, $) = A → b•, $
I8= Go to (I3, A) = Closure (A → aA•, a/b) = A → aA•, a/b
I9= Go to (I6, A) = Closure (A → aA•, $) A → aA•, $
If we analyze then LR (0) items of I3 and I6 are same but they differ only in their
lookahead.
I3 = { A → a•A, a/b
A → •aA, a/b
A → •b, a/b
}
I6= { A → a•A, $
A → •aA, $
A → •b, $
}
Clearly I3 and I6 are same in their LR (0) items but differ in their lookahead, so we can
combine them and called as I36.
I36 = { A → a•A, a/b/$
A → •aA, a/b/$
A → •b, a/b/$
}
The I4 and I7 are same but they differ only in their look ahead, so we can combine them
and called as I47.
I47 = {A → b•, a/b/$}
The I8 and I9 are same but they differ only in their look ahead, so we can combine them
and called as I89.
I89 = {A → aA•, a/b/$}
Drawing DFA:
Program-1:
#include <stdio.h>
#include <string.h>
char input[100];
int lookahead = 0;
void S() {
if (input[lookahead] == 'a') {
match('a');
S();
match('b');
}
}
int main() {
printf("Enter the string: ");
scanf("%s", input);
S();
if (input[lookahead] == '\0')
printf("Accepted\n");
else
printf("Syntax Error\n");
return 0;
}
Program-2:
ex6p2.l
%option noyywrap
%%
%%
ex6p2.y
%{
#include <stdio.h>
#include <stdlib.h>
void yyerror();
int yylex(void);
%}
/* Token declarations */
%token NUMBER
/* Grammar rules */
%%
lines:
| lines expr '\n' { printf("Result: %d\n", $2); }
;
expr:
expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr { $$ = $1 / $3; }
| NUMBER { $$ = $1; }
;
%%
#include "lex.yy.c"
int main() {
printf("Enter an expression (Ctrl+D to exit):\n");
yyparse();
return 0;
}
void yyerror()
{}
Compiler Design (3170701)
Observations and Conclusion:
Program -1:
The program is a simple checker to see if a string follows a specific pattern. The pattern is:
• The string must start with the letter 'a'.
• Then, there can be any number of the letters 'a' and 'b', as long as there are always the same
number of each.
• Finally, the string must end with the letter 'b'.
So, valid strings include:
• "a"
• "ab"
• "aabbb"
• "aaabbbb"
Invalid strings include:
• "b"
• "abbb"
• "aabb"
• "aaabbbbbb"
The program checks the input string against this pattern and tells you if it's valid or not.
Program-2:
This program implements a simple calculator using LEX and YACC. The LEX file defines the
lexical rules for recognizing numbers and operators, while the YACC file specifies the grammar
rules for arithmetic expressions. The main function reads the input expression, parses it using
YACC, and prints the result. The yyerror function is a placeholder for handling syntax errors but is
currently empty. This program demonstrates the basic workflow of using LEX and YACC for
building a language parser and interpreter.
Compiler Design (3170701)
Quiz: