ATCD Unit-5
ATCD Unit-5
Run-Time Environments: Stack Allocation of Space, Access to Nonlocal Data on the Stack, Heap
Management
Syntax-Directed Translation (SDT) is a method used in compiler design to convert source code
into another form while analyzing its structure. SDT is the action of translating a high-level
language program into an intermediate language or machine language according to the semantic
rules imposed by SDDs.
Semantic actions in SDT, act in coordination with the parsing process in order to provide the
translation of the input code.
These actions are declarative and they are triggered during the parsing phase of the message to
yield the result.
The general process of SDT involves constructing a parse tree or syntax tree, then computing
the values of attributes by visiting its nodes in a specific order. However, in many cases,
translation can be performed directly during parsing, without explicitly building the tree.
Syntax Directed Definitions (SDD) are formal methods of attaching semantic information to the
syntactic structure of a programming language. SDDs improve the means of context-free high-
level by instances, adding a semantic rule set for every production of the grammar.
The rules described in these definitions state how to derive values such as types, memory
locations, or fragments of code from the structure of an input object.
Syntax-Directed Definitions (SDDs)
Components of SDDs:
Types of SDDs
1. Synthesized Attributes: Computed from the attributes of the child nodes in the parse tree.
2. Inherited Attributes: Computed from the attributes of the parent node or sibling nodes in
the parse tree.
Applications of SDDs
1. Compiler Design: SDDs are used to specify the semantics of programming languages and
generate compilers.
2. Parser Generation: SDDs can be used to generate parsers that compute attribute values
during parsing.
3. Semantic Analysis: SDDs can be used to perform semantic analysis, such as type checking
and scoping.
Example of SDD: Consider a simple arithmetic expression grammar with the following
production rules:
E -> E + T
E -> T
T -> T * F
T -> F
F -> id
An SDD for this grammar might define synthesized attributes for the value of each expression,
with semantic actions to compute the values:
2.Topological Sort:
A topological sort of the dependency graph can be used to determine a valid evaluation order
for the semantic actions. This ensures that the attributes are evaluated in an order that respects
the dependencies between them.
In this case, a bottom-up evaluation order would first evaluate the attributes of E and T and then
compute the value of E using the semantic action.
By determining a valid evaluation order, we can ensure that the semantic actions are executed
correctly and the attribute values are computed consistently.
SDD: Specifies the values of attributes by SDT: Embeds program fragments (also
associating semantic rules with the called semantic actions) within
productions. production bodies.
Syntax Directed Definition Syntax Directed Translation
Examples of Attributes:
Data types of variables
Line numbers for error handling
Instruction details for code generation
Types of Attributes:
1. Synthesized Attributes
Defined by a semantic rule associated with the production at node N in the parse tree.
Computed only using the attribute values of the children and the node itself.
Mostly used in bottom-up evaluation.
2. Inherited Attributes
Defined by a semantic rule associated with the parent production of node N.
Computed using the attribute values of the parent, siblings, and the node itself.
Used in top-down evaluation.
Differences between Synthesized and Inherited Attributes
The production must have non- The production must have non-terminal as a
2. terminal as its head. symbol in its body.
7.
Attribute Grammars:
An Attributed Grammar is a special type of grammar used in compiler design to add extra
information (attributes) to syntax rules. This helps in semantic analysis, such as type
checking, variable classification, and ensuring correctness in programming languages.
Think of it like a regular grammar with extra labels that help check things like variable
types, correctness of expressions, and rule enforcement.
L1.in := L.in
L → L1 , id addtype (id.entry, L.in) (Passes type info to child
and updates symbol table)
… …
Separation of concerns: SDT separates the translation process from the parsing process,
making it easier to modify and maintain the compiler. It also separates the translation
concerns from the parsing concerns, allowing for more modular and extensible compiler
designs.
Efficient code generation: SDT enables the generation of efficient code by optimizing the
translation process. It allows for the use of techniques such as intermediate code generation
and code optimization.
Inflexibility: SDT can be inflexible in situations where the translation rules are complex
and cannot be easily expressed using grammar rules.
Limited error recovery: SDT is limited in its ability to recover from errors during the
translation process. This can result in poor error messages and may make it difficult to
locate and fix errors in the input program.
Annotated Parse Tree: Annotated Parse tree contains the values and
attributes at each node.
1. Dependency Graphs:
A node in the dependency graph corresponds to the node of the parse tree for
each attribute.
Edges (first node from the second node)of the dependency graph represent that
the attribute of the first node is evaluated before the attribute of the second node.
The dependency graph provides the evaluation order of attributes of the nodes of
the parse tree. An edge( i.e. first node to the second node) in the dependency
graph represents that the attribute of the second node is dependent on the attribute
of the first node for further evaluation. This order of evaluation gives a linear
order called topological order.
There is no way to evaluate SDD on a parse tree when there is a cycle present in
the graph and due to the cycle, no topological order exists.
Production Table
3. A1 ⇢ B A1.syn = B.syn
o If the compiler directly translates source code into the machine code
without generating intermediate code then a full native compiler is
required for each new machine.
o The intermediate code keeps the analysis portion same for all the
compilers that's why it doesn't need a full compiler for every unique
machine.
o Intermediate code generator receives input from its predecessor phase
and semantic analyzer phase. It takes input in the form of an annotated
syntax tree.
o Using the intermediate code, the second phase of the compiler synthesis
phase is changed according to the target machine.
Intermediate representation
Intermediate code can be represented in two ways:
Abstract syntax trees are more compact than a parse tree and can be
easily used by a compiler.
Postfix Notation
o Postfix notation is the useful form of intermediate code if the given
language is expressions.
o Postfix notation is also called as 'suffix notation' and 'reverse polish'.
o Postfix notation is a linear representation of a syntax tree.
o In the postfix notation, any expression can be written unambiguously
without parentheses.
o The ordinary (infix) way of writing the sum of x and y is with operator in
the middle: x * y. But in the postfix notation, we place the operator at the
right end as xy *.
o In postfix notation, the operator follows the operand.
Example
Production
1. E → E1 op E2
2. E → (E1)
3. E → id
E.code = E1.code
E.code = id print id
Postfix Translation
In a production A → α, the translation rule of A.CODE consists of the concatenation of the
CODE translations of the non-terminals in α in the same order as the non-terminals appear in
α.
1. S → while M1 E do M2 S1
Can be factored as:
1. S → C S1
2. C → W E do
3. W → while
A suitable transition scheme would be
C → W E do C W E do
1. S for L = E1 step E2 to E3 do S1
Can be factored as
1. F → for L
2. T → F = E1 by E2 to E3 do
3. S → T S1
Example
GivenExpression:
a := (-c * b) + (-c * d)
Three-address code is as follows:
t1 := -c
t2 := b*t1
t3 := -c
t4 := d * t3
t5 := t2 + t4
a := t5
t is used as registers in the target program.
The three address code can be represented in two
forms: quadruples and triples.
Example 2,: Write three address codes for the following code
for(i = 1; i<=10; i++)
{
a[i] = x * 5;
}
Applications
Optimization.
Code generation.
Debugging.
Language translation.
https://www.geeksforgeeks.org/three-address-code-compiler/
E.val (Synthesized):
The value of the expression E. It is calculated by adding the values of E1 and E2 (E.val =
E1.val + E2.val).
E1.env (Inherited):
The environment (e.g., symbol table) in which E1 is evaluated. It is inherited from the head
E (E1.env := E.env).
E2.env (Inherited):
The environment in which E2 is evaluated. It is also inherited from E (E2.env := E.env).
Run-Time Environments: Stack Allocation of Space, Access to Nonlocal Data on the Stack, Heap
Management
A translation needs to relate the static source text of a program to the
dynamic actions that must occur at runtime to implement the program.
The program consists of names for procedures, identifiers, etc., that
require mapping with the actual memory location at runtime. Runtime
environment is a state of the target machine, which may include software
libraries, environment variables, etc., to provide services to the processes
running in the system.
Int n;
readarray();
quicksort(1,n);
}
quicksort(int m, int n) {
Int i= partition(m,n);
quicksort(m,i-1);
quicksort(i+1,n);
}
The activation tree for this program will be:
First main function as the root then main calls readarray and quicksort.
Quicksort in turn calls partition and quicksort again. The flow of control in
a program corresponds to a pre-order depth-first traversal of the activation
tree which starts at the root.
#include <iostream>
using namespace std;
int main()
{
int a = 10, b = 20;
swap(a, b);
cout << a << " " << b << endl;
return 0;
}
// code added by raunakraj232
Call by Name In call by name the actual parameters are substituted for
formals in all the places formals occur in the procedure. It is also
referred as lazy evaluation because evaluation is done on parameters
only when needed.
Advantages:
Portability: A runtime environment can provide a layer of abstraction
between the compiled code and the operating system, making it easier to
port the program to different platforms.
Resource management: A runtime environment can manage system
resources, such as memory and CPU time, making it easier to avoid
memory leaks and other resource-related issues.
Dynamic memory allocation: A runtime environment can provide
dynamic memory allocation, allowing memory to be allocated and freed as
needed during program execution.
Garbage collection: A runtime environment can perform garbage
collection, automatically freeing memory that is no longer being used by
the program.
Exception handling: A runtime environment can provide exception
handling, allowing the program to gracefully handle errors and prevent
crashes.
Disadvantages:
Performance overhead: A runtime environment can add performance
overhead, as it requires additional processing and memory usage.
Platform dependency: Some runtime environments may be specific to
certain platforms, making it difficult to port programs to other platforms.
Debugging: Debugging can be more difficult in a runtime environment, as
the additional layer of abstraction can make it harder to trace program
execution.
Compatibility issues: Some runtime environments may not be
compatible with certain operating systems or hardware architectures,
which can limit their usefulness.
Versioning: Different versions of a runtime environment may have
different features or APIs, which can lead to versioning issues when
running programs compiled with different versions of the same runtime
environment.