Compiler Design Pyq

Download as pdf or txt
Download as pdf or txt
You are on page 1of 44

COMPILER DESIGN

Q1)
1) Draw a neat diagram and explain different phases of the compiler. Differentiate between
Interpreter, Compiler and Hybrid compiler

2) Define token, pattern, and lexeme with suitable examples. Find tokens, patterns and

Q
lexemes for the following C- program. Give reasonable attribute values for the tokens. int
main() { int a = 10, b = 20; printf (“Sum is : %d”, a+b); return (0) }

-
3) i) Explain regular expressions and regular definitions with examples.

ii) Write regular definitions for the following languages:

A
1. All strings of lowercase letters that contain the five vowels in order.
2. All strings of lowercase letters in which the letters are in ascending lexicographic
order.

H
4) Define Compiler? State some commonly used compiler constructions tools.

5) Explain how the assignment statement “position = initial + rate * 60” is grouped into the

P
lexemes and mapped into the tokens passed on the syntax analyzer

6) What are the contents of a symbol table? Explain the symbol table organisation for block

L
structured languages

Q2)

A
1) Explain the two buffer input scheme for scanning the source program in lexical analyzer?
How can the use of sentinels improve its performance?

2) i) Explain the role of finite automata in lexical analyzer and differentiate between NFA
and DFA. ii) Convert the following regular expression to NFA using Thompson
Construction algorithm. (a|b*ab)*

3) Convert the following NFA to DFA using subset construction algorithm. ((01| 0) 1*)*

4) Explain the concept of the transition diagram with an example transition diagram of relop. Write
important conventions about the transition diagram.

5) In lexical analysis, explain for example how tokens, patterns, and lexemes are related.

6) Explain the structure of the lexical analyzer generator. Show the construction of an NFA
from a LEX program.
Q3)

1) i) What is left recursion and left factoring? ii) Consider the CFG SSS+| SS* | a
a) Left factor is grammar. b) Eliminate the left recursion from the original grammar.
c) Is the resulting grammar suitable for top-down parsing?

2) What is the difference between recursive–descent parsing and predictive parsing?Draw

Q
and explain the model of non recursive predictive parser.

-
3) What is LL(1) grammar? Construct a LL(1) parsing table for the following grammar. Test
whether the following grammar is LL
(1). S → aAB|bA|є A → aAb |є B → bB |c

A
4) How Left Recursion is eliminated? Explain with algorithms and examples.

5) What is Shift-Reduce Parsing? Explain the configuration of shift reduce parser on input
id1*id2.

H
6) Construct a predictive parsing table for the grammar.
E-> E+T | T, T->T*F | F,F->(E) | id.

P
Q4)

L
1) Explain stack implementation of shift reduce parsing with their four possible
actions. Consider the following grammar S → TL T → int | float L → L,id | id
Parse the input string int id, id using shift – reduce parser.

A
2) i) Differentiate between LR(0) and LR(1) grammar with suitable examples.
ii)What is the handle? Consider the following grammar and show the handle of
each right sentential form for the string ( a ,( a , a )). S→ (L)|a L → L ,S |S

3) Show that the following grammar is LL(1) but not SLR(1). S→ AaAb | BbBa A →є
B→є

4) Difference between synthesized and Inherited attributes with suitable examples.


Also, define what is meant by an annotated parse tree.

5) Explain construction syntax trees for simple linear regression involving only
binary operators + and state - . State the use of Leaf and Node in this syntax
tree.

6) Explain the brief about Type Checking and Type Conversion.


Q5)

1) For the SDD given below, give annotated parse tree for the expression 3*5+4.

- Q
H A
2) List the issues in designing code generator. Generate code for the following
three-address statements assuming all variables are stored in memory locations. x = b *
cy=a+x

P
3) Define basic block and flow graph. Give basic blocks and flow graph for the following

L
sequence of three address code statements:

A
4) What is the purpose of code optimization? Explain the DAG representation of basic
blocks with examples.
5) Explain the Code Generation Algorithm with three address instructions. State the four
uses of registers.
6) What is the flow graph? Explain how a given program can be converted into a Flow
graph?

Q
ANSWERS:

-
Q1)
1)

A compiler is a specialized program that translates source code written in a high-level

A
programming language into machine code, bytecode, or another programming language. This
translation process allows the program to be executed by a computer. The compiler performs
several tasks, including lexical analysis, syntax analysis, semantic analysis, optimization, and
code generation.

PH
A L
Differentiation Between Interpreter, Compiler, and Hybrid Compiler

Interpreter:
● Definition: An interpreter directly executes instructions written in a programming or
scripting language without requiring them to have been compiled into machine language.
● Execution: Line-by-line execution.
● Speed: Slower execution compared to compiled programs because each instruction is
analyzed and executed at runtime.
● Error Detection: Errors are detected at runtime, making it easier to debug and test.
● Examples: Python, Ruby, JavaScript (in some implementations).

Q
Compiler:

-
● Definition: A compiler translates the entire source code of a program into machine code
or intermediate code before execution.
● Execution: Entire program is translated and optimized before execution.

A
● Speed: Faster execution compared to interpreted programs because the translation is
done beforehand.
● Error Detection: Errors are detected at compile time, which means all syntax and
semantic errors must be fixed before the program runs.

H
● Examples: C, C++, Rust.

Hybrid Compiler:

P
● Definition: A hybrid compiler combines both compilation and interpretation. The source
code is first compiled into an intermediate code, which is then interpreted or further
compiled just-in-time (JIT).

L
● Execution: Two-phase process involving both compilation and interpretation.
● Speed: Can offer a balance between speed and flexibility. The JIT compilation can
optimize frequently executed code at runtime.
● Error Detection: Errors can be caught both at compile time (syntax) and at runtime

A
(execution).
● Examples: Java (compiles to bytecode interpreted by the JVM), MATLAB.

Comparison Table:

Feature Interpreter Compiler Hybrid Compiler

Translation Line-by-line at Entire code before Initial compilation to


runtime execution intermediate code, then
interpretation/JIT

Execution Slower due to Faster due to Generally balanced, with


Speed runtime pre-compilation potential runtime optimizations
interpretation

Error At runtime At compile time Both at compile time and


Detection runtime
Usage Python, Ruby, C, C++, Rust Java (JVM), MATLAB
Examples JavaScript

Performance May be less Highly optimized Optimized through JIT


optimized through multiple compilation
passes

Q
This differentiation helps in understanding the appropriate use cases for each type of language
processing approach, depending on the requirements of speed, efficiency, and ease of

-
debugging.

A
2)

Definitions

H
1. Token:
○ Definition: A token is a category of lexical units in a programming language.
Tokens are the smallest units that are meaningful to the compiler.

P
○ Examples: Keywords, identifiers, operators, literals, and punctuation symbols.
2. Pattern:
○ Definition: A pattern is a rule that specifies the set of strings that a token can

L
match. It is a formal description of the form that tokens of a particular type can
take.
○ Examples: For identifiers, the pattern might be a sequence of letters followed by
optional digits; for numbers, it might be a sequence of digits.

A
3. Lexeme:
○ Definition: A lexeme is an actual sequence of characters in the source code that
matches a pattern for a token. It is an instance of a token.
○ Examples: For the identifier token, possible lexemes are main, a, b.

Example C Program
int main() {
int a = 10, b = 20;
printf("Sum is : %d", a+b);
return 0;
}

Tokens, Patterns, and Lexemes


Tokens:

● Keywords: int, return


● Identifiers: main, a, b, printf
● Operators: =, +
● Separators: ,, ;, (, ), {, }
Literals: 10, 20, "Sum is : %d"

Q

Patterns:

-
● Keyword: Specific reserved words defined by the language.
● Identifier: A sequence of letters followed by a sequence of letters or digits (e.g.,
[a-zA-Z_][a-zA-Z0-9_]*).

A
● Operator: Characters that represent operations (e.g., =, +).
● Separator: Characters that separate tokens (e.g., ,, ;, (, ), {, }).
● Literal: Numeric or string values (e.g., digits for numbers, characters enclosed in quotes

H
for strings).

Lexemes:

P
● int, main, (, ), {, int, a, =, 10, ,, b, =, 20, ;, printf, (, "Sum is : %d", ,, a, +,
b, ), ;, return, 0, }, ;

L
Tokens with Reasonable Attribute Values

1. int:

A
○ Token: KEYWORD
○ Attribute: type
2. main:
○ Token: IDENTIFIER
○ Attribute: function_name
3. (:
○ Token: LEFT_PARENTHESIS
○ Attribute: open_function_param
4. ):
○ Token: RIGHT_PARENTHESIS
○ Attribute: close_function_param
5. {:
○ Token: LEFT_BRACE
○ Attribute: open_block
6. int:
○ Token: KEYWORD
○ Attribute: type
7. a:
○ Token: IDENTIFIER
○ Attribute: variable_name
8. =:
○ Token: OPERATOR

Q
○ Attribute: assignment
9. 10:

-
○ Token: LITERAL
○ Attribute: integer_value
10. ,:
○ Token: SEPARATOR

A
○ Attribute: comma
11. b:
○ Token: IDENTIFIER

H
○ Attribute: variable_name
12. =:
○ Token: OPERATOR
○ Attribute: assignment

P
13. 20:
○ Token: LITERAL

L
○ Attribute: integer_value
14. ;:
○ Token: SEPARATOR
○ Attribute: semicolon

A
15. printf:
○ Token: IDENTIFIER
○ Attribute: function_name
16. (:
○ Token: LEFT_PARENTHESIS
○ Attribute: open_function_param
17. "Sum is : %d":
○ Token: STRING_LITERAL
○ Attribute: string_value
18. ,:
○ Token: SEPARATOR
○ Attribute: comma
19. a:
○ Token: IDENTIFIER
○ Attribute: variable_name
20. +:
○ Token: OPERATOR
○ Attribute: addition
21. b:
○ Token: IDENTIFIER
○ Attribute: variable_name

Q
22. ):
○ Token: RIGHT_PARENTHESIS

-
○ Attribute: close_function_param
23. ;:
○ Token: SEPARATOR
○ Attribute: semicolon

A
24. return:
○ Token: KEYWORD
○ Attribute: return_statement

H
25. 0:
○ Token: LITERAL
○ Attribute: integer_value
26. ):

P
○ Token: RIGHT_PARENTHESIS
○ Attribute: close_expression

L
27. ;:
○ Token: SEPARATOR
○ Attribute: semicolon
28. }:

A
○ Token: RIGHT_BRACE
○ Attribute: close_block

This breakdown provides a clear view of the tokens, their patterns, and the lexemes in the given
C program, along with reasonable attributes for each token type.

3)

i) Explanation of Regular Expressions and Regular Definitions

Regular Expressions: Regular expressions (regex or regexp) are sequences of characters that
define a search pattern, primarily used for string matching and manipulation. They are a
powerful tool in text processing, often utilized in search engines, text editors, and programming
languages.
Components of Regular Expressions:

1. Literals: Direct match to characters. For example, a matches the character 'a'.
2. Concatenation: Combining patterns. For example, abc matches the sequence 'abc'.
3. Alternation: Either-or choice. For example, a|b matches 'a' or 'b'.
4. Repetition:
○ *: Zero or more occurrences. For example, a* matches '', 'a', 'aa', etc.

Q
○ +: One or more occurrences. For example, a+ matches 'a', 'aa', etc.
?: Zero or one occurrence. For example, a? matches '' or 'a'.

-

○{m,n}: Between m and n occurrences. For example, a{2,3} matches 'aa' or
'aaa'.
5. Character Classes:

A
○ [abc]: Matches 'a', 'b', or 'c'.
○ [^abc]: Matches any character except 'a', 'b', or 'c'.
○ [a-z]: Matches any lowercase letter.
6. Anchors:

H
○ ^: Start of the string.
○ $: End of the string.
7. Groups and Ranges:

P
○ () defines a group.
○ | within a group for alternatives.

L
Example:

● Regular expression a(b|c)*d matches strings like 'ad', 'abd', 'acd', 'abbd', etc.

A
Regular Definitions: Regular definitions provide a more structured way to define a set of
regular expressions. They are often used to formally specify the tokens of a language.

Components:

1. Base cases: Single characters or predefined character sets (e.g., a, b, [a-z]).


2. Inductive cases: Formed using operations like concatenation, alternation, and repetition
on simpler regular expressions.

Example:

Regular definition for identifiers in many programming languages:


rust

letter -> [a-zA-Z]

digit -> [0-9]


identifier -> letter (letter | digit)*

ii) Regular Definitions for Specific Languages

All strings of lowercase letters that contain the five vowels in order:

Q
Regular Definition:
css

-
consonant -> [bcdfghjklmnpqrstvwxyz]

vowel_sequence -> a* e* i* o* u*

A
string_with_vowels_in_order -> (consonant | a)* a (consonant | e)* e
(consonant | i)* i (consonant | o)* o (consonant | u)* u (consonant)*

H
1. Explanation:
○ consonant defines all lowercase letters except vowels.
○ vowel_sequence ensures the presence of the vowels 'a', 'e', 'i', 'o', 'u' in order.

P
○ string_with_vowels_in_order allows any consonant or vowel before 'a',
any consonant or 'e' after 'a' but before 'e', and so on, ensuring vowels appear in
the order 'a', 'e', 'i', 'o', 'u'.

L
All strings of lowercase letters in which the letters are in ascending lexicographic order:
Regular Definition:
css

A
letter -> [a-z]

ordered_string -> a* b* c* d* e* f* g* h* i* j* k* l* m* n* o* p* q*
r* s* t* u* v* w* x* y* z*

2. Explanation:
○ letter defines all lowercase letters.
○ ordered_string ensures that each letter may appear zero or more times but
must appear in lexicographic order. For instance, all 'a's must appear before any
'b's, all 'b's must appear before any 'c's, and so on.

These regular definitions precisely capture the constraints specified for the languages.
4)

Definition of a Compiler

A compiler is a specialized software that translates source code written in a high-level


programming language into machine code, bytecode, or another target language. The purpose
of a compiler is to enable the execution of the source code on a computer or other

Q
programmable device. The compilation process involves several stages, including lexical
analysis, syntax analysis, semantic analysis, optimization, and code generation.

-
Commonly Used Compiler Construction Tools

1. Lexical Analyzers:

A
○ Flex (Fast Lexical Analyzer Generator): A tool that generates lexical analyzers,
which are programs that recognize lexical patterns in text. Flex reads a
specification of a lexical analyzer and generates C code to implement it.
2. Parser Generators:

H
○ Bison: A modern version of Yacc, providing more features and better error
handling capabilities.
3. Intermediate Code Generators:
○ LLVM (Low-Level Virtual Machine): A collection of modular and reusable

P
compiler and toolchain technologies. LLVM can generate intermediate
representations that can be further optimized and converted into machine code

L
for various architectures.
4. Optimization Tools:
○ GCC (GNU Compiler Collection): A widely used compiler system that supports
multiple programming languages. GCC includes a suite of optimization

A
techniques to improve the performance of the generated code.
5. Debugging and Profiling Tools:
○ GDB (GNU Debugger): A powerful debugger that allows developers to track and
control the execution of programs, set breakpoints, and inspect variables.
○ Valgrind: A programming tool used for memory debugging, memory leak
detection, and profiling. It helps ensure that programs use memory correctly and
efficiently.
6. Integrated Development Environments (IDEs):
○ Eclipse: An IDE that provides tools for coding, debugging, and compiling in
various programming languages.
○ Visual Studio: An IDE from Microsoft that supports multiple languages and
provides comprehensive tools for development, including a compiler and
debugger.

A compiler is essential for translating high-level programming languages into


machine-executable code, and various tools are available to aid in the construction and
optimization of compilers.
5)

In the lexical analysis phase of a compiler, the assignment statement "position = initial + rate *
60" is broken down into lexemes, which are the smallest meaningful units in the source code,
and then mapped into tokens. Tokens are categorized representations of lexemes that are
passed on to the syntax analyzer for further processing.

Q
Lexemes:

-
1. position: Identifier
2. =: Assignment operator
3. initial: Identifier
4. +: Addition operator

A
5. rate: Identifier
6. *****: Multiplication operator
7. 60: Numeric literal

H
Tokens:

1. Identifier: Represents variables or functions.


2. Assignment Operator: Represents the assignment operation.

P
3. Arithmetic Operators: Represent mathematical operations.
4. Numeric Literal: Represents numeric values.

L
Mapping into Tokens Passed to the Syntax Analyzer:

The tokens generated from the lexemes are passed on to the syntax analyzer, which checks

A
whether the sequence of tokens follows the grammar rules of the programming language. For
example, the sequence of tokens passed to the syntax analyzer might be:

scss

Copy code

IDENTIFIER(position) ASSIGN_OP IDENTIFIER(initial) ADD_OP


IDENTIFIER(rate) MUL_OP NUMBER(60)

Where:

● IDENTIFIER: Represents an identifier token.


● ASSIGN_OP: Represents the assignment operator token.
● ADD_OP: Represents the addition operator token.
● MUL_OP: Represents the multiplication operator token.
● NUMBER: Represents a numeric literal token.

The syntax analyzer will then analyze this sequence of tokens to ensure that it conforms to the
syntax rules of the programming language, such as the correct order of operators and
operands. If the sequence is syntactically correct, further analysis such as semantic analysis
and code generation can proceed. Otherwise, syntax errors will be reported.

Q
This process of lexical analysis and tokenization is crucial in the compilation process, as it lays
the foundation for subsequent phases and ensures that the source code is correctly interpreted

-
by the compiler.

A
6)

In compiler design, a symbol table is a critical data structure used to store information about
identifiers (symbols) encountered in the source code. It serves as a reference for the compiler
during various stages of compilation, including lexical analysis, semantic analysis, and code

H
generation. The symbol table maintains essential information about each identifier, facilitating
efficient processing of the program. Here are the typical contents of a symbol table:

1. Identifier Name: The name of the identifier, such as variable names, function names,

P
labels, etc.
2. Data Type Information: The data type associated with the identifier, including basic data

L
types (e.g., integer, float) or complex data types (e.g., arrays, structures, classes).
3. Scope Information: The scope in which the identifier is defined or accessible. This
information helps resolve identifier scope conflicts and supports scope-related
operations like variable shadowing.

A
4. Memory Location: The memory address or storage location where the identifier's value
is stored during program execution. This information is crucial for code generation and
optimization.
5. Attributes: Additional metadata associated with the identifier, which may include:
○ For functions: Return type, parameter types, number of parameters, etc.
○ For variables: Size, initialization status, visibility, etc.
○ For constants: Value, data type, etc.

Symbol Table Organization for Block-Structured Languages

Block-structured languages, like C, C++, Java, and many others, organize program code into
nested blocks or scopes. Each scope defines a distinct region of the program where identifiers
have local visibility and may shadow identifiers with the same name in outer scopes. Here's how
symbol tables are typically organized to handle block structure:

1. Global Symbol Table:


○ Contains entries for identifiers that are globally accessible throughout the
program.
○ Stores information about global variables, functions, constants, and other entities
defined outside any function or block.
○ Scope: Entire program.
2. Local Symbol Tables:
○ Each block or function scope has its own local symbol table.

Q
○ Created dynamically as new scopes are encountered during parsing.
○ Contains entries for identifiers declared within the corresponding block or

-
function.
○ Scope: Limited to the block or function in which it is defined.
3. Block-Scoped Symbol Tables:
○ For nested blocks within functions, additional symbol tables are created.

A
○ These symbol tables handle local variables, parameters, and other identifiers
within the nested blocks.
○ Scope: Limited to the corresponding nested block.
4. Hierarchical Structure:

H
○ Symbol tables are organized hierarchically, with each nested scope linked to its
enclosing scope.
○ Allows for efficient scope resolution and access to identifiers in nested scopes.

P
5. Scope Resolution:
○ During compilation, when an identifier is referenced, the compiler searches for it
in the current scope's symbol table first.

L
○ If not found, it traverses the hierarchy of symbol tables, moving outward to
enclosing scopes, until the identifier is found or the global scope is reached.
6. Dynamic Scope Management:
○ Symbol tables are pushed onto and popped off a stack as scopes are entered

A
and exited.
○ Ensures proper handling of variable shadowing and allows for efficient
management of nested scopes.

By organizing symbol tables in this manner, compilers for block-structured languages can
efficiently handle nested scopes, variable shadowing, and scope resolution during compilation,
ensuring correct interpretation and translation of source code into executable programs.

Q2)

1)

Two-Buffer Input Scheme in Lexical Analysis

The two-buffer input scheme is a technique used in lexical analysis to efficiently scan the source
program. It involves utilizing two input buffers to read characters from the input stream
alternately. This scheme enhances the performance and effectiveness of the lexical analyzer.
Here's how it works:

Process:

1. Buffer Switching: The lexical analyzer reads characters from one input buffer while
simultaneously filling the other buffer with new characters from the input stream.

Q
2. Token Extraction: As the lexical analyzer scans the characters in the current buffer, it
identifies lexemes and tokens, such as identifiers, keywords, operators, etc.

-
3. Continuous Operation: While one buffer is being processed, the other buffer continues
to receive input characters from the source program, ensuring uninterrupted scanning.

Benefits:

A
● Improved Efficiency: By reading from one buffer while filling the other, the lexical
analyzer can operate continuously without waiting for new input characters. This reduces
idle time and improves overall efficiency.
● Reduced Latency: The two-buffer scheme minimizes the delay between reading

H
characters and processing them, leading to faster scanning of the source program.
● Enhanced Throughput: With continuous operation, the lexical analyzer can maintain a
steady pace of token extraction, improving overall throughput.

P
Use of Sentinels:

L
● Sentinels are special characters appended to the end of each buffer to indicate the end
of the input stream.
● By using sentinels, the lexical analyzer can detect the end of input without needing
additional checks, thus improving performance.

A
● Sentinels ensure that the lexical analyzer can safely switch between buffers without
losing track of the input stream's boundaries.

Performance Improvement:

● The use of sentinels eliminates the need for explicit boundary checks when switching
between buffers, reducing the overhead associated with buffer management.
● Sentinels allow the lexical analyzer to detect the end of input more efficiently, minimizing
the risk of buffer overflow errors or incomplete tokenization.

In summary, the two-buffer input scheme, along with the use of sentinels, enhances the
efficiency and performance of the lexical analyzer by enabling continuous and uninterrupted
scanning of the source program. It reduces latency, improves throughput, and simplifies buffer
management, leading to more effective lexical analysis.
2)

To convert the given NFA to DFA using the subset construction algorithm, we'll follow these
steps:

1. Start with the initial state of the NFA.


2. Calculate the ε-closure of the initial state to obtain the initial state of the DFA.

Q
3. For each input symbol, calculate the set of states reachable from the current state of the
DFA.

-
4. Repeat steps 2 and 3 until all states of the DFA have been explored.
5. Define the transitions of the DFA based on the calculated sets of states.

Given NFA:

A
State 0: Initial state State 1: Accept state State 2: Accept state

Transitions:

H
● State 0: ε → {1, 2}
● State 1: 0 → {1}
● State 2: 1 → {2}

P
DFA Construction:

L
Step 1: Calculate ε-closure of initial state

● ε-closure({0}) = {0, 1, 2}

A
Step 2: Calculate transitions for input symbols

● From State {0, 1, 2}:


○ Input 0: Move to State {1}
○ Input 1: Move to State {2}
● From State {1}:
○ Input 0: No transition
○ Input 1: No transition
● From State {2}:
○ Input 0: No transition
○ Input 1: Move to State {2}

Step 3: Calculate ε-closure of new states

● ε-closure({1}) = {1}
● ε-closure({2}) = {2}

Step 4: Define DFA States and Transitions


State Input 0 Input 1

{0, 1, 2} {1} {2}

Q
{1} ∅ ∅

-
{2} ∅ {2}

A
{1} ∅ ∅

H
Final DFA:

P
Copy code

L
╔════════╗ 0 ╔════════╗ 1 ╔════════╗

║ {0,1,2}║ ──────► ║ {1} ║ ──────► ║ {2} ║

A
╚════════╝ ╚════════╝ ╚════════╝

│ ┌─────────────┐

│ 1 │ 1 ▼

└─────────────────────────►║ {2} ║

└─────────────┘

4)

1) Concept of Transition Diagram with Example

In lexical analysis, a transition diagram (also known as a state diagram or finite automaton) is a
graphical representation of the transitions between states in a finite automaton. It visually
depicts how the automaton moves from one state to another upon encountering input symbols.
Concept:

● States: Each circle or node in the diagram represents a state of the finite automaton.
States can be initial, accepting, or intermediate.
● Transitions: The arrows between states represent transitions triggered by input
symbols. Each transition is labeled with the input symbol that causes it.
● Start State: The initial state from which the automaton begins processing input.

Q
● Accepting States: States where the automaton accepts the input as a valid token or
pattern.

-
● Final States: States where the automaton finishes processing input, whether accepting
or rejecting it.

Example: Relational Operator (relop) Transition Diagram

A
┌─────────┐

H
┌────►│ Start │

│ └─────────┘

P

L
│ =, <, >, <=, >=, !=

A

┌───────┐ = ┌──────┐

│ 1 │◄───────────┤ 2 │

└───────┘ └──────┘

| │ │ │

| │<, >, <=, >=,!=│ │

│ └───────────────┘ │

│ = │

└─────────────────────┘
Important Conventions about Transition Diagrams:

● Direction of Arrows: Arrows indicate the direction of transitions, moving from one state
to another upon encountering specific input symbols.
● Labeling of Transitions: Each transition arrow is labeled with the input symbol that

Q
triggers it.
● Start State: A designated initial state from which the automaton begins processing input.

-
● Accepting States: States where the automaton accepts the input as a valid token or
pattern.
● Final States: States where the automaton finishes processing input, indicating the end
of tokenization.

A
● Self-Loops: Some states may have transitions to themselves, representing valid input
symbols that maintain the current state.

2) Relationship between Tokens, Patterns, and Lexemes in Lexical Analysis

H
In lexical analysis:

● Lexemes: These are the basic units of source code, such as identifiers, keywords,

P
operators, and literals.
● Patterns: Patterns define the structure and format of lexemes using regular expressions.
For example, a pattern for an identifier might be [a-zA-Z][a-zA-Z0-9]*, indicating

L
that it starts with a letter and can be followed by alphanumeric characters.
● Tokens: Tokens are instances of lexemes that match specific patterns. For example, if
the input stream contains the sequence of characters "if", it matches the pattern for the

A
keyword "if" and is recognized as a token of type "IF".

The relationship between these concepts is as follows:

● Lexemes are the actual entities in the source code.


● Patterns describe the syntax rules that define valid lexemes.
● Tokens are instances of lexemes that match specific patterns, identified during lexical
analysis.

In summary, lexemes are described by patterns, and tokens are instances of lexemes that
match those patterns.

3) Structure of Lexical Analyzer Generator and Construction of NFA from a


LEX Program

Structure of Lexical Analyzer Generator:

A lexical analyzer generator (e.g., Lex, Flex) typically consists of the following components:
1. Input Specification: Defines the lexical rules of the programming language using
regular expressions and associated actions.
2. Lexer Generator: Converts the input specification into code for a lexical analyzer.
3. Lexical Analyzer Code: Generated code implementing the lexical analyzer, typically
written in a programming language like C or C++.
4. Lexical Analysis Engine: Executes the generated lexical analyzer code to tokenize the
input stream based on the specified rules.

Q
Construction of NFA from a LEX Program:

-
1. Input Specification: The lexical rules of the programming language are specified using
regular expressions and associated actions in a LEX program.
2. Lexical Analyzer Generator: The LEX program is processed by the lexical analyzer

A
generator (e.g., Flex), which converts the regular expressions into a nondeterministic
finite automaton (NFA).
3. NFA Construction: The generator constructs the NFA by converting each regular
expression into a series of NFA fragments and combining them using operations like

H
concatenation, union, and closure.
4. NFA Execution: The resulting NFA is used to tokenize the input stream based on the
specified lexical rules, recognizing lexemes and generating corresponding tokens.

P
In summary, a lexical analyzer generator takes an input specification in the form of regular
expressions, converts it into a NFA, and generates code for a lexical analyzer based on the
NFA. This generated lexical analyzer is then used to tokenize the input stream according to the

L
specified lexical rules.

A
Q3)

i) Left Recursion and Left Factoring

i) Left Recursion:

● Left recursion occurs in a grammar when a non-terminal produces a sentential form that
starts with the same non-terminal.
● Example: A → Aα | β, where α is a string of symbols and β does not start with A.
● Left recursion can lead to issues in parsing algorithms like recursive descent.

ii) Left Factoring:

● Left factoring is a grammar transformation technique used to eliminate common prefixes


from the productions of a grammar.
● It involves rewriting productions to create a new non-terminal for the common prefixes.
● Left factoring helps to resolve ambiguities in the grammar and makes parsing more
efficient.

ii) Left Recursion Elimination

Given CFG: S → SSS+ | SS* | a

Q
a) Left Factor Grammar: The given grammar does not exhibit left factoring since there are no
common prefixes in its productions.

-
b) Elimination of Left Recursion: To eliminate left recursion, we rewrite the grammar as
follows:

A
S → aS'

S' → SS' | ε

H
c) Suitability for Top-Down Parsing: The resulting grammar after left recursion elimination is
suitable for top-down parsing because it does not contain left recursion, which can cause issues
in predictive parsing algorithms like recursive descent. The grammar is also suitable for LL(1)
parsing as it does not have any conflicts in the parsing table.

L P
2)

A
- Q
H A
P
NON PREDICTIVE PARSER DIAGRAM

3)

A L
LL(1) Grammar:An LL(1) grammar is a context-free grammar for which there exists a parsing
table such that, for each cell [A, a] in the table, there is at most one production A → α that can
be used for parsing when the non-terminal A is at the top of the stack and the input symbol a is
the next input symbol. Additionally, no two distinct productions for the same non-terminal A
should have the same FIRST set for the input symbol a.

Given Grammar:

S → aAB | bA | є

A → aAb | є

B → bB | c
Constructing LL(1) Parsing Table:

1. Calculate FIRST and FOLLOW sets for each non-terminal.


2. Fill the LL(1) parsing table with the appropriate productions based on FIRST and
FOLLOW sets.

FIRST and FOLLOW Sets:

Q
● FIRST(S) = {a, b, ε}
● FIRST(A) = {a, ε}

-
● FIRST(B) = {b, c}
● FOLLOW(S) = {$}
● FOLLOW(A) = {b, $}

A
● FOLLOW(B) = {$}

LL(1) Parsing Table:

markdown

H
Copy code

| a | b | c | $

P
-----------------------------

L
S | aAB | bA | є | є

-----------------------------

A
A | aAb | є | є | є

-----------------------------

B | є | bB | c | є

Testing for LL(1):

● Each cell of the parsing table contains at most one production.


● No two distinct productions for the same non-terminal have the same FIRST set for the
corresponding input symbol.
● The given grammar is LL(1) since it satisfies these conditions.
4)

Left recursion elimination is a technique used to transform a context-free grammar (CFG) by


removing left-recursive productions. Left recursion can cause issues in certain parsing
algorithms, particularly recursive descent parsers, as it can lead to infinite recursion. The
process involves identifying and modifying left-recursive productions in the grammar.

- Q
H A
Elimination of Left Recursion

L P
A
Left Recursion can be eliminated by introducing new non-terminal A such that.

This type of recursion is also called Immediate Left Recursion.


In Left Recursive Grammar, expansion of A will generate Aα, Aαα, Aααα at each
step, causing it to enter into an infinite loop

- Q
H A
L P
A
After left recursion elimination, the modified grammar is suitable for parsing algorithms that
cannot handle left recursion, such as recursive descent or LL(1) parsers.

5)

Shift-Reduce Parsing is a bottom-up parsing technique commonly used to construct a parse


tree or syntax tree for a given input string. In shift-reduce parsing, the parser shifts input
symbols onto a stack until it identifies a sequence of symbols that can be reduced to a
non-terminal according to the grammar's production rules. The process involves two main
actions: shifting and reducing.

Configuration of Shift-Reduce Parser on Input "id1*id2":


Let's illustrate the shift-reduce parsing process step by step for the input "id1*id2" using a simple
grammar:

Grammar:

E → E * E | id

Q
Initial Configuration:

● Input: "id1*id2$"

-
● Stack: [ ]

Step 1: Shift 'id' onto Stack

A
● Action: Shift
● Input: "1*id2$"
● Stack: [ id ]

H
Step 2: Reduce by 'id' Rule

● Action: Reduce by E → id
● Input: "1*id2$"

P
● Stack: [ E ]

Step 3: Shift '1' onto Stack

L
● Action: Shift
● Input: "*id2$"
● Stack: [ E, 1 ]

A
Step 4: Shift '*' onto Stack

● Action: Shift
● Input: "id2$"
● Stack: [ E, 1, * ]

Step 5: Shift 'id' onto Stack

● Action: Shift
● Input: "2$"
● Stack: [ E, 1, *, id ]

Step 6: Reduce by 'id' Rule

● Action: Reduce by E → id
● Input: "2$"
● Stack: [ E, 1, *, E ]
Step 7: Reduce by 'E * E' Rule

● Action: Reduce by E → E * E
● Input: "$"
● Stack: [ E ]

Step 8: Accept

Q
● Action: Accept
● Input: "$"

-
● Stack: [ E ]

Final Parse Tree:

A
*

/ \

H
id E

/ \

P
1 id

L
In this example, the shift-reduce parser successfully constructs the parse tree for the input
"id1*id2". It shifts input symbols onto the stack and then reduces them according to the

A
grammar's production rules until it accepts the input.

6)
GIVEN CODE:

E→E+T|T

T→T*F|F

F → (E) | id

- Q
First and Follow sets:

● FIRST(E) = { (, id }
● FIRST(T) = { (, id }

A
● FIRST(F) = { (, id }
● FOLLOW(E) = { $, ) }
● FOLLOW(T) = { +, $, ) }
● FOLLOW(F) = { *, +, $, ) }

H
Predictive Parsing Table:

L P
A
The predictive parsing table guides the parser on which production to choose based on the
current input symbol and the non-terminal at the top of the stack. It ensures that the parsing
process is deterministic and does not require backtracking.

Q4)

1) Shift-reduce parsing involves manipulating a stack to perform parsing actions. Here's


how a stack-based shift-reduce parser works along with the four possible actions: shift,
reduce, accept, and error.

Stack Implementation of Shift-Reduce Parsing:

1. Initialization:
○ Initialize an empty stack and push the start symbol onto it.
○ Initialize the input pointer to the first symbol of the input string.
2. Parsing Actions:
○ Repeat the following steps until the stack contains only the start symbol and the
input string is empty:
■ Read the current input symbol.
■ Determine the next action based on the top of the stack and the current
input symbol:
■ If the top of the stack is a terminal and matches the current input

Q
symbol, perform a shift action.
■ If the top of the stack is a non-terminal and the current input

-
symbol matches a production in the parsing table, perform a
reduce action.
■ If the top of the stack is the start symbol and the input string is
empty, perform an accept action.

A
■ If none of the above conditions are met, perform an error action.

Possible Actions:

H
1. Shift:
○ When a terminal is shifted onto the stack.
○ The current input symbol is pushed onto the stack, and the input pointer moves

P
to the next symbol.
2. Reduce:
○ When a sequence of symbols on top of the stack matches the right-hand side of

L
a production.
○ The symbols are replaced by the corresponding non-terminal on the left-hand
side of the production.
○ This operation simulates the application of a production rule.

A
3. Accept:
○ When the entire input string has been successfully parsed, and the stack
contains only the start symbol.
○ Indicates that the input string is syntactically correct according to the grammar.
4. Error:
○ When none of the other actions can be performed.
○ Indicates a syntax error in the input string.

Example: Parsing "int id, id"

Given Grammar:

S → TL

T → int | float

L → L, id | id
Initial Configuration:

● Stack: [ S ]
● Input: "int id, id$"

Q
Step 1: Shift "int" onto Stack

● Action: Shift

-
● Stack: [ S, int ]
● Input: "id, id$"

Step 2: Reduce by T → int

A
● Action: Reduce
● Stack: [ S, T ]
● Input: "id, id$"

H
Step 3: Shift "id" onto Stack

● Action: Shift

P
● Stack: [ S, T, id ]
● Input: ", id$"

L
Step 4: Reduce by L → id

● Action: Reduce
● Stack: [ S, T, L ]

A
● Input: ", id$"

Step 5: Shift "," onto Stack

● Action: Shift
● Stack: [ S, T, L, , ]
● Input: "id$"

Step 6: Shift "id" onto Stack

● Action: Shift
● Stack: [ S, T, L, , id ]
● Input: "$"

Step 7: Reduce by L → L, id

● Action: Reduce
● Stack: [ S, T, L, L ]
● Input: "$"

Step 8: Reduce by S → TL

● Action: Reduce
● Stack: [ S ]
● Input: "$"

Q
Step 9: Accept

-
● Action: Accept
● Stack: [ ]
● Input: "$"

A
The parsing process successfully completes, and the input string "int id, id" is accepted by the
parser.

H
2)

L P
ii) Handle in Parsing:
A
In LR parsing, a handle is a substring of the right sentential form that matches the right-hand
side of a production, and it can be reduced to the non-terminal on the left-hand side of that
production. The handle represents the part of the input that corresponds to the production being
applied during a reduction step.

Given Grammar:

css

Q
Copy code

S → (L) | a

-
L → L, S | S

A
Right Sentential Form for the String "( a ,( a , a ))":

(S (L (S (L (S (L (S (L (S a))))))))))

PH
A L
In each step of the parsing process, the handle corresponds to the right-hand side of the
production being applied during the reduction.

3)

To show that a grammar is LL(1) but not SLR(1), we need to construct the LL(1) parsing table
and the SLR(1) parsing table and demonstrate the conflicts in the SLR(1) parsing table.

Given Grammar:
S → AaAb | BbBa

A → є

B → є

LL(1) Parsing Table Construction:

Q
1. FIRST and FOLLOW sets:

-
○ FIRST(S) = { a, b }
○ FIRST(A) = { є }
○ FIRST(B) = { є }
○ FOLLOW(S) = { $ }

A
○ FOLLOW(A) = { a, b }
○ FOLLOW(B) = { a, b }
2. LL(1) Parsing Table:

PH
The LL(1) parsing table does not contain any conflicts, indicating that the grammar is

L
LL(1).

SLR(1) Parsing Table Construction:

A
1. Canonical LR(0) Collection:
○ Construct the LR(0) items for each production.
○ Create the LR(0) automaton and compute the closure and goto sets.
2. Compute Lookahead Symbols:
○ Calculate the lookahead symbols for each LR(1) item.
3. SLR(1) Parsing Table:
○ Construct the parsing table using the LR(1) items and lookahead symbols.

SLR(1) Parsing Table:


In the SLR(1) parsing table:
● State 0 has shift actions for terminals 'a' and 'b'.
● State 1 has reduce action (by production B → є) for terminals 'a' and 'b'.
● State 2 has an accept action for the end-of-input symbol '$'.

Conflict in SLR(1) Parsing Table:

Q
● There is a reduce-reduce conflict in state 1 for terminals 'a' and 'b'.

-
● Both productions A → є and B → є can be reduced in state 1, leading to a conflict.

Thus, the grammar is LL(1) but not SLR(1) because of the reduce-reduce conflict in the SLR(1)
parsing table.

A
4)

PH
A L
Annotated Parse Tree
An annotated parse tree is a regular parse tree where additional information, called attributes,
are attached to the nodes. These attributes represent the synthesized or inherited properties
associated with the corresponding non-terminal symbol.

Q
5)

-
a syntax tree (parse tree) represents the structure of an expression according to a grammar.
Here, we'll construct a syntax tree for a simple linear regression equation involving only addition
and subtraction ("+" and "-") operators.

A
Construction of Syntax Trees:

1. Tokenization:

H
○ Begin by tokenizing the input expression into operators (+, -) and operands
(variables or constants).
2. Parsing:
○ Parse the tokenized expression according to the rules of operator precedence

P
and associativity to construct the syntax tree.
○ Binary operators like addition (+) and subtraction (-) typically have left-to-right

L
associativity.
3. Building the Tree:
○ Construct the syntax tree recursively from the bottom up.
○ Each node represents an operator, and its children nodes represent its operands.

A
Use of Leaf and Node:

● Leaf:
○ Leaves of the syntax tree represent operands or terminal symbols of the
expression.
○ In the context of simple linear regression, leaves can represent variables
(features) or constants (numeric values).
○ For example, in the expression "x + y - 3", leaves would represent variables 'x'
and 'y', as well as the constant '3'.
● Node:
○ Nodes of the syntax tree represent operators or non-terminal symbols of the
expression.
○ Each node contains references to its children nodes, representing the operands
on which the operator operates.
○ In the context of simple linear regression, nodes would represent the addition (+)
and subtraction (-) operators.
○ Each node can have two children representing the left and right operands of the
operator.
○ For example, in the expression "x + y - 3", there would be nodes representing the
addition and subtraction operators.

Example Syntax Tree:

Q
Consider the expression "x + y - 3":

-
-

/\

A
+ 3

/\

x y

H
Leaf and Node Usage:

P
● Leaf: Represents a terminal symbol in the grammar, which cannot be further broken
down. In our case, Leaf nodes contain numeric constants or the variable x.

L
● Node: Represents an internal symbol in the grammar, indicating an operation being
performed. These nodes have child nodes that represent the operands involved in the
operation. In our example, Nodes contain the "+" and "-" operators, with Leaf nodes as

A
their children for constants and variables.

6)

In compiler design, both type checking and type conversion play crucial roles in ensuring the
correctness and efficiency of programs. Let's explore each concept briefly:

Type Checking:

Definition: Type checking is a process performed by compilers or interpreters to ensure that all
operations in a program are performed on data types that are compatible with the operations
being performed.

Objective:

● Detect and prevent type-related errors in a program.


● Ensure type safety and consistency throughout the program.
Tasks:

1. Determine Types: Analyze each expression, variable, or operation to determine its data
type.
2. Check Compatibility: Verify that operations and assignments involve compatible types.
3. Enforce Rules: Enforce type rules defined by the programming language, such as
arithmetic operations on numeric types only, or assignment compatibility.

Q
4. Detect Errors: Identify type-related errors such as type mismatch errors, undefined
operations, or incompatible function arguments.

-
Example:

int x = 5;

A
String y = "Hello";

int z = x + y; // Type mismatch error: incompatible types

H
Type Conversion (Type Casting):

P
Definition: Type conversion, also known as type casting, is the process of converting one data
type into another. It allows the programmer to explicitly change the type of a variable or

L
expression.

Objective:

A
● Facilitate compatibility between different data types.
● Enable operations that require operands of different types.

Types:

1. Implicit Conversion: Automatically performed by the compiler when compatible types


are used in an expression.
2. Explicit Conversion: Manually performed by the programmer using casting operators to
convert between incompatible types.

Example:

int x = 5;

double y = 3.14;

double z = x + y; // Implicit conversion: int is promoted to double


int a = (int) y; // Explicit conversion: double is casted to int

Use Cases:

● Converting between numeric types (e.g., int to double).

Q
● Converting between primitive types and object types (e.g., int to String).
● Ensuring compatibility in function calls or assignments.

-
Summary:

● Type Checking ensures consistency and safety by enforcing type rules and detecting

A
errors.
● Type Conversion facilitates compatibility between different data types, allowing for more
flexible and expressive programming.

H
Q5)

P
1)

Purpose of Code Optimization:

L
Code optimization is a crucial phase in the compilation process aimed at improving the
efficiency, speed, and resource utilization of the generated code while preserving its
functionality. The primary purposes of code optimization include:

A
1. Improving Performance:
○ Optimized code runs faster and consumes fewer resources, leading to
improved program performance. This is especially important in
resource-constrained environments such as embedded systems or mobile
devices.
2. Reducing Execution Time:
○ Optimization techniques such as loop unrolling, function inlining, and
constant folding can reduce the number of instructions executed, thus
decreasing program execution time.
3. Minimizing Memory Usage:
○ Optimization techniques like register allocation and memory optimization
help reduce memory usage, making the program more memory-efficient.
This is crucial in scenarios where memory resources are limited.
4. Enhancing Code Readability:
○ While optimizing, compilers often rearrange and simplify code, resulting in
cleaner and more readable code for developers. This aids in maintenance,
debugging, and understanding of the codebase.
5. Reducing Power Consumption:
○ Optimized code typically requires fewer instructions to execute, leading to
reduced power consumption. This is important for battery-powered devices
and energy-efficient computing, where power consumption is a critical

Q
factor.

-
DAG Representation of Basic Blocks:

A Directed Acyclic Graph (DAG) representation of basic blocks is a data structure used in
compiler optimization to analyze and optimize code at the basic block level. Here's how it

A
works:

1. Construction:
○ Each basic block in the code is represented as a node in the DAG.

H
○ The DAG captures the control flow between basic blocks using directed
edges.
2. Common Subexpression Elimination (CSE):
○ DAGs are particularly useful for detecting and eliminating common

P
subexpressions within basic blocks.
○ Common subexpressions are identified by nodes with the same operation

L
and operands.

Example: Consider the following basic block:


makefile

A
Copy code
t1 = a + b

t2 = c - d

t3 = t1 * t2

t4 = t1 * t2

The DAG representation would look like this:


+

/ \

a b

Q
\ /

-
-

/ \

A
c d

\ /

H
*

P
t1

3.

L
In this DAG representation:

● Each node represents an operation (e.g., addition, subtraction, multiplication).

A
● The edges represent data dependencies between the operations.
● Nodes with identical operations and operands indicate common subexpressions,
which can be optimized to reduce redundancy and improve performance.

2)

Code Generation Algorithm with Three Address Instructions:

The code generation algorithm with three-address instructions is a process used in


compiler design to translate high-level intermediate representations (IR) into low-level
assembly or machine code. Here's an overview of the algorithm:

1. Parse Intermediate Representation (IR):


○ Start with a high-level intermediate representation of the program, such as
an abstract syntax tree (AST) or quadruples.
○ Each statement in the IR represents an operation with operands.
2. Generate Three-Address Instructions:
○ Translate each statement in the IR into a sequence of three-address
instructions.
○ A three-address instruction typically consists of an operation, two
operands, and a result.
○ Ensure that each instruction has a unique label or address.

Q
Example: Consider the assignment statement x = a + b * c. It can be translated into

-
three-address instructions as follows:
makefile
Copy code
t1 = b * c

A
t2 = a + t1

x = t2

H
3.
4. Optimization:
○ Apply optimization techniques to improve the efficiency and performance

P
of the generated code.
○ Common optimizations include constant folding, common subexpression

L
elimination, and loop optimization.
5. Register Allocation:
○ Map temporary variables (operands) to physical registers.
○ Ensure that register usage is optimized to minimize memory accesses and

A
maximize performance.
6. Generate Assembly Code:
○ Translate the three-address instructions into assembly code compatible
with the target architecture.
○ Emit assembly code instructions for each three-address instruction.
7. Finalization:
○ Perform any final adjustments or transformations required for the target
platform.
○ Generate the final executable code or object files.

Four Uses of Registers:

Registers are small, fast storage locations within the CPU used to hold data temporarily
during program execution. They play a crucial role in optimizing code performance. Here
are four common uses of registers:

1. Temporary Storage:
○ Registers are often used to hold temporary variables and intermediate
results during computation.
○ Temporary storage in registers eliminates the need for frequent memory
accesses, which are slower than register accesses.
2. Operand Storage:
○ Registers hold operands for arithmetic and logical operations, such as
addition, subtraction, multiplication, and division.

Q
○ Storing operands in registers enables fast execution of operations by
minimizing memory accesses.

-
3. Address Calculation:
○ Registers are used to store memory addresses and pointers required for
accessing data in memory.
○ Register-based address calculation reduces memory latency and improves

A
memory access performance.
4. Function Parameters and Return Values:
○ Registers are used to pass function parameters between function calls and
return values from functions.

H
○ Using registers for parameter passing and return values reduces memory
traffic and improves function call performance.

Overall, efficient utilization of registers is essential for optimizing code performance and

P
minimizing execution time. Compiler optimization techniques often focus on effective
register allocation and management to enhance program efficiency.

L
3)

A
Flow Graph:

A flow graph, also known as a control flow graph (CFG), is a graphical representation of
the control flow within a program. It provides a visual representation of the sequence of
execution paths through the program's basic blocks and the relationships between them.
A flow graph consists of nodes representing basic blocks and directed edges
representing the flow of control between basic blocks.

Construction of Flow Graph:

To convert a given program into a flow graph, follow these steps:

1. Identify Basic Blocks:


○ Divide the program into basic blocks, where each block represents a
sequence of instructions with a single entry point and a single exit point.
○ A basic block typically starts with a leader statement and ends with a
control transfer statement (e.g., branch, jump, or return).
2. Construct Nodes and Edges:
○ Create a node for each basic block identified in the program.
○ Connect the nodes with directed edges to represent the flow of control
between basic blocks.
○ An edge from node A to node B indicates that control can transfer from A
to B.

Q
Example: Consider the following program:
arduino

-
Copy code
if (x > y) {

z = x + y;

A
} else {

z = x - y;

H
}

3. The flow graph would consist of two basic blocks: one for the if branch and one

P
for the else branch, connected by directed edges based on control flow.
In this flow graph:

L
○ Node 1 represents the if branch, starting with the comparison x > y and
ending with the assignment z = x + y.
○ Node 2 represents the else branch, starting with the negated comparison x
<= y and ending with the assignment z = x - y.

A
○ The directed edge from Node 1 to Node 2 indicates that control transfers
from the if branch to the else branch when the condition x <= y is false.
○ Control flow continues from Node 2 to subsequent basic blocks or nodes
based on the program's structure.

By constructing a flow graph, programmers and compiler designers can visualize the
control flow structure of the program, analyze its behavior, and apply optimization
techniques to improve its performance and efficiency.

You might also like