0% found this document useful (0 votes)
7 views

Pranav Compiler Design Lab File

Compiler design

Uploaded by

Pranav Coder
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views

Pranav Compiler Design Lab File

Compiler design

Uploaded by

Pranav Coder
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

SAGE University Indore

LAB MANUAL
Subject code :- ACTDCTOC001P
SUBJECT NAME :- COMPILER DESIGN LAB
SEMESTER: VI SEM III YEAR
Session: - Dec - may 2023

Institute of Advance Computing

Submitted to :- Submitted by:-


Prof. Nirupama Tiwari Name:- Pranav Patidar
Enrollment No :- 21ADV3CSE0117
INDEX
S.no Name of the Experiment Date of Page no. Remark
Experiment
1.
WAP to identify keywords.

2.
WAP to identify keyword and store them in symbol
table.

3.
Develop a lexical analyzer to recognize a few patterns.

4.
WAP to design three address code.

5.
WAP to develop an operator precedence parser.

6.
Develop a recursive descent parser.

7.
Write a program for generating for various intermediate
code forms Polish notation.

8.
Write a program to simulate Heap storage allocation
strategy.

9.
Given any intermediate code form, implement code
optimization techniques.
Experiment 1
WAP to identify keywords.

Keyword is a predefined or reserved word which is available in C++ library


with a fixed meaning and used to perform an internal operation. C++
Language supports more than 64 keywords.

Every Keyword exists in lower case letters like auto, break, case, const,
continue, int etc.

32 Keywords in C++ Language which is also available in the C


language.

auto double int struct

break else long switch

case enum register typedef

char extern return union

const float short unsigned

continue for signed void

default goto sizeof volatile

do if static while
Example
#include <stdio.h>

#include <string.h>

int main() {

char keyword[32][10]={

"auto","double","int","struct","break","else","long",

"switch","case","enum","register","typedef","char",

"extern","return","union","const","float","short",

"unsigned","continue","for","signed","void","default",

"goto","sizeof","voltile","do","if","static","while"

};

char str[]="which";

int flag=0,i;

for(i = 0; i < 32; i++) {

if(strcmp(str,keyword[i])==0) {

flag=1;

if(flag==1)

printf("%s is a keyword",str);

else

printf("%s is not a keyword",str);

Output:
which is a keyword
Experiment 2
WAP to identify keyword and store them in symbol table.

Certainly! Below is a C program that reads a C source code file, identifies keywords, and
stores them in a symbol table:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <ctype.h>

// Structure to represent a keyword entry in the symbol table

typedef struct {

char keyword[20];

int frequency;

} KeywordEntry;

// Function to check if a string is a keyword

int isKeyword(char *str) {

char *keywords[] = {"auto", "break", "case", "char", "const", "continue", "default", "do",

"double", "else", "enum", "extern", "float", "for", "goto", "if",

"int", "long", "register", "return", "short", "signed", "sizeof", "static",

"struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while"};

int i;

for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {

if (strcmp(str, keywords[i]) == 0) {

return 1;

}
}

return 0;

int main() {

FILE *fp;

char filename[100], word[100];

KeywordEntry symbolTable[100]; // Symbol table to store keywords

int numKeywords = 0;

printf("Enter the name of the C file: ");

scanf("%s", filename);

// Open the file

fp = fopen(filename, "r");

if (fp == NULL) {

printf("Could not open file %s\n", filename);

return 1;

// Read words from the file and check if they are keywords

while (fscanf(fp, "%s", word) != EOF) {

// Convert word to lowercase

for (int i = 0; word[i]; i++) {

word[i] = tolower(word[i]);

}
// Remove punctuations

int len = strlen(word);

if (ispunct(word[len - 1])) {

word[len - 1] = '\0';

// Check if word is a keyword

if (isKeyword(word)) {

// Check if the keyword is already in the symbol table

int found = 0;

for (int i = 0; i < numKeywords; i++) {

if (strcmp(symbolTable[i].keyword, word) == 0) {

symbolTable[i].frequency++;

found = 1;

break;

// If the keyword is not in the symbol table, add it

if (!found) {

strcpy(symbolTable[numKeywords].keyword, word);

symbolTable[numKeywords].frequency = 1;

numKeywords++;

// Close the file


fclose(fp);

// Print the symbol table

printf("Symbol Table:\n");

printf("Keyword\t\tFrequency\n");

for (int i = 0; i < numKeywords; i++) {

printf("%-10s\t%d\n", symbolTable[i].keyword, symbolTable[i].frequency);

return 0;

```

This program reads a C source code file provided by the user, identifies the keywords present
in the file, and stores them in a symbol table along with their frequencies. It then prints out
the symbol table at the end. Please note that this program assumes that the source code file
provided by the user is syntactically correct.
Experiment 3
Develop a lexical analyzer to recognize a few patterns.

PROGRAM CODE:

//Develop a lexical analyzer to recognize a few patterns in C.

#include<string.h>

#include<ctype.h>

#include<stdio.h>

#include<stdlib.h>

void keyword(char str[10])

if(strcmp("for",str)==0||strcmp("while",str)==0||strcmp("do",str)==0||strcmp("int",str)==0||str
cmp("float",str)==0||strcmp("char",str)==0||strcmp("double",str)==0||strcmp("printf",str)==0||
strcmp("switch",str)==0||strcmp("case",str)==0)

printf("\n%s is a keyword",str);

else

printf("\n%s is an identifier",str);

void main()

FILE *f1,*f2,*f3;

char c,str[10],st1[10];

int num[100],lineno=0,tokenvalue=0,i=0,j=0,k=0;

f1=fopen("input","r");

f2=fopen("identifier","w");

f3=fopen("specialchar","w");

while((c=getc(f1))!=EOF)

{
if(isdigit(c))

tokenvalue=c-'0';

c=getc(f1);

while(isdigit(c))

tokenvalue*=10+c-'0';

c=getc(f1);

num[i++]=tokenvalue;

ungetc(c,f1);

else

if(isalpha(c))

putc(c,f2);

c=getc(f1);

while(isdigit(c)||isalpha(c)||c=='_'||c=='$')

putc(c,f2);

c=getc(f1);

putc(' ',f2);

ungetc(c,f1);

else

if(c==' '||c=='\t')
printf(" ");

else

if(c=='\n')

lineno++;

else

putc(c,f3);

fclose(f2);

fclose(f3);

fclose(f1);

printf("\n the no's in the program are:");

for(j=0;j<i;j++)

printf("\t%d",num[j]);

printf("\n");

f2=fopen("identifier","r");

k=0;

printf("the keywords and identifier are:");

while((c=getc(f2))!=EOF)

if(c!=' ')

str[k++]=c;

else

str[k]='\0';

keyword(str);

k=0;

fclose(f2);
f3=fopen("specialchar","r");

printf("\n Special Characters are");

while((c=getc(f3))!=EOF)

printf("\t%c",c);

printf("\n");

fclose(f3);

printf("Total no of lines are:%d",lineno);

OUTPUT:
Experiment 4

WAP to design three address code.

Three-address code is a representation of intermediate code that uses at most three


addresses or operands. Each instruction in three-address code typically performs a
simple operation with two input operands and one output operand. Here's a simple C
program that generates three-address code for basic arithmetic expressions:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

// Structure to represent a three-address code instruction

typedef struct {

char op[10]; // Operation (e.g., "+", "-", "*", "/")

char arg1[10]; // First operand

char arg2[10]; // Second operand

char result[10];// Result variable

} ThreeAddressCode;

// Function to generate three-address code for arithmetic expressions

ThreeAddressCode* generateThreeAddressCode(char* expr) {

// Initialize variables

ThreeAddressCode* code =
(ThreeAddressCode*)malloc(sizeof(ThreeAddressCode));

char op[10], arg1[10], arg2[10], result[10];

int i = 0, j = 0;

// Read the first operand

while (expr[i] != '+' && expr[i] != '-' && expr[i] != '*' && expr[i] != '/') {
arg1[j++] = expr[i++];

arg1[j] = '\0';

j = 0;

// Read the operator

op[0] = expr[i++];

op[1] = '\0';

// Read the second operand

while (expr[i] != '\0') {

arg2[j++] = expr[i++];

arg2[j] = '\0';

// Generate result variable

strcpy(result, "t1");

// Store values in the three-address code instruction

strcpy(code->op, op);

strcpy(code->arg1, arg1);

strcpy(code->arg2, arg2);

strcpy(code->result, result);

return code;

}
int main() {

char expr[100];

printf("Enter an arithmetic expression (+, -, *, /): ");

scanf("%s", expr);

// Generate three-address code for the expression

ThreeAddressCode* code = generateThreeAddressCode(expr);

// Print the generated three-address code

printf("\nThree-Address Code:\n");

printf("%s = %s %s %s\n", code->result, code->arg1, code->op, code->arg2);

// Free dynamically allocated memory

free(code);

return 0;

This program prompts the user to enter a simple arithmetic expression containing
addition, subtraction, multiplication, or division. It then generates the corresponding
three-address code and prints it out. For example, if the user enters "a + b", the
program will output "t1 = a + b", where "t1" is the result variable. Similarly, if the user
enters "x * y", the output will be "t1 = x * y".
Experiment 5

WAP to develop an operator precedence parser.

Below is the source code for C Program to input values into an array and display them which is
successfully compiled and run on Windows System to produce desired output as shown below :

Operator precedence & associativity table

Operator precedence & associativity are listed in the following table and this table is summarized
in decreasing Order of priority i.e topmost operator has highest priority and bottommost operator
has Lowest Priority.

OPERATOR DESCRIPTION ASSOCIATIVITY

Parentheses (function call) (see Note 1)


()
Brackets (array subscript)
[ ].
Member selection via object name left-to-right
->
Member selection via pointer
++ –
Postfix increment/decrement (see Note 2)

++ – Prefix increment/decrement

+– Unary plus/minus right-to-left

!~ Logical negation/bitwise complement


(type) Cast (convert value to temporary value of type)

* Dereference

& Address (of operand)

sizeof Determine size in bytes on this implementation

* / % Multiplication/division/modulus left-to-right

+ – Addition/subtraction left-to-right

<< >> Bitwise shift left, Bitwise shift right left-to-right

< <= Relational less than/less than or equal to


left-to-right
> >= Relational greater than/greater than or equal to

== != Relational is equal to/is not equal to left-to-right

& Bitwise AND left-to-right

^ Bitwise exclusive OR left-to-right

| Bitwise inclusive OR left-to-right


&& Logical AND left-to-right

|| Logical OR left-to-right

?: Ternary conditional right-to-left

= Assignment

+= -= Addition/subtraction assignment

*= /= Multiplication/division assignment
right-to-left
%= &= Modulus/bitwise AND assignment

^= |= Bitwise exclusive/inclusive OR assignment

<<= >>= Bitwise shift left/right assignment

, Comma (separate expressions) left-to-right

SOURCE CODE : :
#include <stdio.h>

int main() {

int a = 20;
int b = 10;
int c = 15;
int d = 5;
int e;

e = (a + b) * c / d; // ( 30 * 15 ) / 5
printf("Value of (a + b) * c / d is : %d\n", e );
e = ((a + b) * c) / d; // (30 * 15 ) / 5
printf("Value of ((a + b) * c) / d is : %d\n" , e );

e = (a + b) * (c / d); // (30) * (15/5)


printf("Value of (a + b) * (c / d) is : %d\n", e );

e = a + (b * c) / d; // 20 + (150/5)
printf("Value of a + (b * c) / d is : %d\n" , e );

return 0;
}
OUTPUT : :
Value of (a + b) * c / d is : 90
Value of ((a + b) * c) / d is : 90
Value of (a + b) * (c / d) is : 90
Value of a + (b * c) / d is : 50
Experiment 6

Develop a recursive descent parser.

What is a Recursive Descent Parser?


Continuative Descent Top-down parsers, like Parser, construct the parse tree from
the root to the leaf. It recognizes the input using recursive functions and may or may
not employ backtracking. Predictive parsing is another name for the recursive
descent parser variant that does not require backtracking.

Backtracking:

You can follow the several steps to utilize backtracking:

1. It repeatedly scans the input until we locate the right path.

2. The following qualities of the grammar must be present in order to create a


recursive descent parser:

3. It shouldn't be allowed to repeat.

4. It needs to be left-factored. Alternatives shouldn't share a prefix.

5. Recursion should be possible in languages.

6. Backtracking will be required by the recursive descent parser if the grammar


is not left-factored.

Example

fore removing left recursion er removing left recursion

E+T|T T E'

T*F|F + T E' | e

( E ) | id F T'

* F T' | e

( E ) | id
1. **Here e is Epsilon

Now, we are going to create a separate program for each variable in the recursive
descent parser.

Program:
1. #include <stdio.h>
2. #include <string.h>
3.
4. #define SUCCESS 1
5. #define FAILED 0
6.
7. int E(), Edash(), T(), Tdash(), F();
8.
9. const char *cursor;
10. char string[64];
11.
12. int main()
13. {
14. puts("Enter the string");
15. // scanf("%s", string);
16. sscanf("i+(i+i)*i", "%s", string);
17. cursor = string;
18. puts("");
19. puts("Input Action");
20. puts("--------------------------------");
21.
22. if (E() && *cursor == '\0') {
23. puts("--------------------------------");
24. puts("String is successfully parsed");
25. return 0;
26. } else {
27. puts("--------------------------------");
28. puts("Error in parsing String");
29. return 1;
30. }
31. }
32.
33. int E()
34. {
35. printf("%-16s E -> T E'\n", cursor);
36. if (T()) {
37. if (Edash())
38. return SUCCESS;
39. else
40. return FAILED;
41. } else
42. return FAILED;
43. }
44.
45. int Edash()
46. {
47. if (*cursor == '+') {
48. printf("%-16s E' -> + T E'\n", cursor);
49. cursor++;
50. if (T()) {
51. if (Edash())
52. return SUCCESS;
53. else
54. return FAILED;
55. } else
56. return FAILED;
57. } else {
58. printf("%-16s E' -> $\n", cursor);
59. return SUCCESS;
60. }
61. }
62.
63. int T()
64. {
65. printf("%-16s T -> F T'\n", cursor);
66. if (F()) {
67. if (Tdash())
68. return SUCCESS;
69. else
70. return FAILED;
71. } else
72. return FAILED;
73. }
74.
75. int Tdash()
76. {
77. if (*cursor == '*') {
78. printf("%-16s T' -> * F T'\n", cursor);
79. cursor++;
80. if (F()) {
81. if (Tdash())
82. return SUCCESS;
83. else
84. return FAILED;
85. } else
86. return FAILED;
87. } else {
88. printf("%-16s T' -> $\n", cursor);
89. return SUCCESS;
90. }
91. }
92.
93. int F()
94. {
95. if (*cursor == '(') {
96. printf("%-16s F -> ( E )\n", cursor);
97. cursor++;
98. if (E()) {
99. if (*cursor == ')') {
100. cursor++;
101. return SUCCESS;
102. } else
103. return FAILED;
104. } else
105. return FAILED;
106. } else if (*cursor == 'i') {
107. cursor++;
108. printf("%-16s F ->i\n", cursor);
109. return SUCCESS;
110. } else
111. return FAILED;
112. }

Output:

Enter the string

Input Action
--------------------------------
i+(i+i)*i E -> T E'
i+(i+i)*i T -> F T'
+(i+i)*i F ->i
+(i+i)*i T' -> $
+(i+i)*i E' -> + T E'
(i+i)*i T -> F T'
(i+i)*i F -> ( E )
i+i)*i E -> T E'
i+i)*i T -> F T'
+i)*i F ->i
+i)*i T' -> $
+i)*i E' -> + T E'
i)*i T -> F T'
)*i F ->i
)*i T' -> $
)*i E' -> $
*i T' -> * F T'
F ->i
T' -> $
E' -> $
--------------------------------
String is successfully parsed
Experiment 7

Write a program for generating for various intermediate code forms Polish notation.

Certainly! Here's a C program that generates Polish notation (also known as prefix notation) for
arithmetic expressions:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Function to check if a character is an operator


int isOperator(char ch) {
return (ch == '+' || ch == '-' || ch == '*' || ch == '/');
}

// Function to get the precedence of an operator


int getPrecedence(char op) {
if (op == '+' || op == '-') {
return 1;
} else if (op == '*' || op == '/') {
return 2;
} else {
return 0; // Default precedence for non-operators
}
}

// Function to convert an infix expression to Polish notation


void infixToPrefix(char *infix, char *prefix) {
char operatorStack[100];
int operatorTop = -1;
int prefixIndex = 0;

// Reverse the infix expression


strrev(infix);

// Process each character in the reversed infix expression


for (int i = 0; infix[i] != '\0'; i++) {
if (isdigit(infix[i])) {
// If the character is a digit, copy it to the prefix expression
prefix[prefixIndex++] = infix[i];
} else if (isOperator(infix[i])) {
// If the character is an operator
while (operatorTop >= 0 && getPrecedence(operatorStack[operatorTop]) >
getPrecedence(infix[i])) {
// Pop operators with higher precedence from the stack and append them to the prefix
expression
prefix[prefixIndex++] = operatorStack[operatorTop--];
}
// Push the current operator onto the stack
operatorStack[++operatorTop] = infix[i];
} else if (infix[i] == ')') {
// If the character is a closing parenthesis, push it onto the stack
operatorStack[++operatorTop] = infix[i];
} else if (infix[i] == '(') {
// If the character is an opening parenthesis, pop operators from the stack
// and append them to the prefix expression until a matching closing parenthesis is found
while (operatorTop >= 0 && operatorStack[operatorTop] != ')') {
prefix[prefixIndex++] = operatorStack[operatorTop--];
}
// Discard the matching closing parenthesis
operatorTop--;
}
}

// Append any remaining operators from the stack to the prefix expression
while (operatorTop >= 0) {
prefix[prefixIndex++] = operatorStack[operatorTop--];
}

// Null-terminate the prefix expression


prefix[prefixIndex] = '\0';

// Reverse the prefix expression to obtain the final result


strrev(prefix);
}

int main() {
char infix[100];
char prefix[100];

printf("Enter an infix expression: ");


scanf("%s", infix);

// Convert the infix expression to prefix notation


infixToPrefix(infix, prefix);

// Print the prefix notation


printf("Prefix notation: %s\n", prefix);

return 0;
}
OUTPUT:

Enter an infix expression: a+b


Prefix notation: +

This program prompts the user to enter an infix expression and then converts it to Polish notation (prefix
notation). It supports addition, subtraction, multiplication, and division operators, as well as parentheses
for grouping. The resulting prefix notation is then printed to the console.
Experiment 8

Write a program to simulate Heap storage allocation strategy.

Heap Allocation

Heap allocation is used where the Stack allocation lacks if we want to


retain the values of the local variable after the activation record ends,
which we cannot do in stack allocation, here LIFO scheme does not work
for the allocation and de-allocation of the activation record. Heap is the
most flexible storage allocation strategy we can dynamically allocate and
de-allocate local variables whenever the user wants according to the user
needs at run-time. The variables in heap allocation can be changed
according to the user’s requirement. C, C++, Python, and Java all of these
support Heap Allocation.

The heap
The heap is a region of memory used for dynamic memory allocation, allowing
for the allocation and deallocation of memory at runtime. Unlike the stack,
memory allocated on the heap persists until explicitly freed
by the programmer. The heap provides flexibility for managing large data
structures, arrays, and dynamic memory requirements.

Heap Usage Example


1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main() {
5 int* arr = (int*)malloc(5 * sizeof(int)); //
6 allocate memory on the heap
7 if (arr != NULL) {
8 for (int i = 0; i < 5; i++) {
9 arr[i] = i + 1; // assign values to the array
1 }
0 for (int i = 0; i < 5; i++) {
1 printf("Value at index %d: %d\n", i, arr[i]);
1 }
1 free(arr); // deallocate the memory
2 }
1 return 0;
3}
6

This program simulates heap storage allocation using a first-fit strategy. It initializes the heap with a
single memory block representing the entire heap size. Memory allocation is performed using the
`allocate` function, which searches for the first free memory block that can accommodate the requested
size. Memory deallocation is performed using the `deallocate` function, which marks the memory block
as free and coalesces adjacent free memory blocks. The state of the heap is printed before and after
memory allocation and deallocation to visualize the changes.
Experiment 9

Given any intermediate code form, implement code optimization techniques.

Optimizing intermediate code involves transforming it to improve efficiency without changing its
behavior. There are various optimization techniques, and the choice depends on the specific
characteristics of the code. Below, I'll provide a simple example of code optimization using constant
folding:

Consider the following intermediate code representing arithmetic operations:

```
1. t1 = 5 + 3
2. t2 = t1 * 2
3. t3 = t2 - 10
4. t4 = t3 / 4
```

We can optimize this code by performing constant folding, which involves evaluating constant
expressions at compile-time rather than at runtime. In this case, we can evaluate the constant expressions
and replace them with their results:

```
1. t1 = 8
2. t2 = t1 * 2
3. t3 = t2 - 10
4. t4 = t3 / 4
```

Here's a C program that implements this optimization:

```c
#include <stdio.h>
#include <stdlib.h>

// Structure to represent an intermediate code instruction


typedef struct {
char result[10]; // Result variable
char op; // Operator
char arg1[10]; // First operand
char arg2[10]; // Second operand
} IntermediateCode;

// Function to perform constant folding optimization


void constantFolding(IntermediateCode *code, int numInstructions) {
for (int i = 0; i < numInstructions; i++) {
// Check if both operands are constants
if (isdigit(code[i].arg1[0]) && isdigit(code[i].arg2[0])) {
int operand1 = atoi(code[i].arg1);
int operand2 = atoi(code[i].arg2);
int result;
// Perform the operation based on the operator
switch (code[i].op) {
case '+':
result = operand1 + operand2;
break;
case '-':
result = operand1 - operand2;
break;
case '*':
result = operand1 * operand2;
break;
case '/':
if (operand2 != 0) {
result = operand1 / operand2;
} else {
printf("Error: Division by zero\n");
exit(1);
}
break;
default:
printf("Error: Invalid operator\n");
exit(1);
}
// Update the instruction with the result
sprintf(code[i].result, "%d", result);
code[i].op = '\0';
strcpy(code[i].arg1, "");
strcpy(code[i].arg2, "");
}
}
}

// Function to print the optimized intermediate code


void printOptimizedCode(IntermediateCode *code, int numInstructions) {
printf("Optimized Intermediate Code:\n");
for (int i = 0; i < numInstructions; i++) {
if (code[i].op == '\0') {
printf("%s = %s\n", code[i].result, code[i].result);
} else {
printf("%s = %s %c %s\n", code[i].result, code[i].arg1, code[i].op, code[i].arg2);
}
}
}

int main() {
// Sample intermediate code
IntermediateCode code[] = {
{"t1", '+', "5", "3"},
{"t2", '*', "t1", "2"},
{"t3", '-', "t2", "10"},
{"t4", '/', "t3", "4"}
};
int numInstructions = sizeof(code) / sizeof(code[0]);

// Perform constant folding optimization


constantFolding(code, numInstructions);

// Print the optimized intermediate code


printOptimizedCode(code, numInstructions);

return 0;
}
```

This program takes an array of intermediate code instructions as input, performs constant folding
optimization, and prints the optimized intermediate code. In this example, constant expressions like "5 +
3" are evaluated at compile-time, resulting in optimized code.

You might also like