27
Role of data structure in compiler design Mithiun kumar gupta [email protected] Reg. no. – 11102557 Roll. No. – RD1107A13 1. ABSTRACT:- Most programmers write software in a high-level language, such as Pascal, C, C++, java, Cobol and others. Many are not aware of the fact that they are really making use of a sophisticated program called a compiler that bridges the gap between their chosen language and a computer architecture. Some have no concept at all, or a very poor grasp of the computer's instruction set, memory organization, and other details that make their software work. A compiler therefore provides a valuable form of information hiding. Most programmers don't need to know anything about the details of translation and execution, only the properties claimed to be supported by the high-level language and its libraries. Compilers design are a valuable and reliable tool for the most part, but no software is perfect. If a bug arises in code, it's sometimes useful to be able to trace it down into the assembler/micro instruction level. High performance may require certain low-level

Role of Data Structure in Compiler Design

Embed Size (px)

Citation preview

Page 1: Role of Data Structure in Compiler Design

Role of data structure in compiler design

Mithiun kumar gupta

[email protected]

Reg. no. – 11102557

Roll. No. – RD1107A13

1. ABSTRACT:-

Most programmers write software

in a high-level language, such as Pascal, C,

C++, java, Cobol and others. Many are not

aware of the fact that they are really

making use of a sophisticated program

called a compiler that bridges the gap

between their chosen language and a

computer architecture. Some have no

concept at all, or a very poor grasp of the

computer's instruction set, memory

organization, and other details that make

their software work. A compiler therefore

provides a valuable form of information

hiding. Most programmers don't need to

know anything about the details of

translation and execution, only the

properties claimed to be supported by the

high-level language and its libraries.

Compilers design are a valuable and

reliable tool for the most part, but no

software is perfect. If a bug arises in code,

it's sometimes useful to be able to trace it

down into the assembler/micro instruction

level. High performance may require

certain low-level operations. Finally,

knowing something about the strategy

used by a compiler helps the engineer

understand the high-level language at a

deeper level.

2. INTRODUCTION OF

COMPILER DESIGN

A compiler is a computer program

(or set of programs) that transforms source

code written in a programming language

(the source language) into another

computer language (the target language,

often having a binary form known as

object code). The most common reason for

wanting to transform source code is to

create an executable program.

The name "compiler" is primarily

used for programs that translate source

code from a high-level programming

language to a lower level language (e.g.,

assembly language or machine code). If

the compiled program can run on a

computer whose CPU or operating system

is different from the one on which the

compiler runs, the compiler is known as

Page 2: Role of Data Structure in Compiler Design

across-compiler. A program that translates

from a low level language to a higher level

one is called a decompiles. A program that

translates between high-level languages is

usually called a language translator, source

to source translator, or language converter.

A language rewriter is usually a program

that translates the form of expressions

without a change of language.

A compiler is likely to perform

many or all of the following operations:

lexical analysis, pre-processing, parsing,

semantic analysis (Syntax-directed

translation), code generating, and code

optimization.

Program faults caused by incorrect

compiler behaviour can be very difficult to

track down and work around; therefore,

compiler implementers invest a lot of time

ensuring the correctness of their software.

Compiler design is focusing on

more low-level and systems aspects rather

than high-level questions such as

polymorphic type inference or separate

compilation. You will be building several

complete end-to-end compilers for

successively more complex languages,

culminating in a mildly optimizing

compiler for a safe variant of the C

programming language to x86-64

assembly language. For the last project

you will have the opportunity to optimize

more aggressively, to implement a garbage

collector, or retarget the compiler to an

abstract machine.

3. REQUIREMENT OF

COMPILER DESIGN

There are many requirement related

to compiler as follow:-

CORRECTNESS:-

Correctness is absolutely

paramount. A buggy compiler is next to

useless in practice. Since we cannot

formally prove the correctness of your

compilers, we use extensive testing. This

testing is end-to-end, verifying the

correctness of the generated code on

sample inputs. We also verify that your

compiler rejects programs as expected

when the input is not well-formed

(lexically, syntactically, or with respect to

the static semantics), and that the

generated code raises an exception as

expected if the language specification

Page 3: Role of Data Structure in Compiler Design

prescribes this. We go so far as to test that

your generated code fails to terminate

(with a time-out) when the source program

should diverge. Emphasis on correctness

means that we very carefully define the

semantics of the source language. The

semantics of the target language is given

by the GNU assembler on the lab

machines together with the semantics of

the actually machine. Unlike C, we try to

make sure that as little as possible about

the source language remains undefined.

EFFICIENCY: -

In a production compiler,

efficiency of the generated code and also

efficiency of the compiler itself are

important considerations. In this course,

we set very lax targets for both,

emphasizing correctness instead. In one of

the later labs in the course, you will have

the opportunity to optimize the generated

code. The early emphasis on correctness

has consequences for your approach to the

design of the implementation. Modularity

and simplicity of the code are important

for two reasons: first, your code is much

more likely to be correct, and, second, you

will be able to respond to changes in the

source language specification from lab to

lab much more easily.

INTEROPERABI LITY: -

Programs do not run in isolation,

but are linked with library code before

they are executed, or will be called as a

library from other code. This puts some

additional requirements on the compiler,

which must respect certain interface

specifications. This means that you will

have to respect calling conventions early

on (for example, properly save caller-save

registers) and data layout conventions

later, when code will be calling library

functions.

Usability:-

A compiler interacts with the

programmer primarily when there are

errors in the program. As such, it should

give helpful error messages. Also,

compilers may be instructed to generate

debug information together with

executable code in order help users debug

runtime errors in their program.

In this course, we will not formally

evaluate the quality or detail of your error

messages, although you should strive to

achieve at least a minimum standard so

that you can use your own compiler

effectively.

Retarget ability:-

At the outset, we think of a

compiler of going from one source

language to one target language. In

practice, compilers may be required to

Page 4: Role of Data Structure in Compiler Design

generate more than one target from a given

source (for example, x86-64 and ARM

code), sometimes at very different levels

of abstraction (for example, x86-64

assembly or LLVM intermediate code).

THE STRUCTURE OF COMPILER

DESIGN

Compilers bridge source programs

in high-level languages with the

underlying hardware. A compiler

requires;-

1) determining the correctness of the

syntax of programs,

2) generating correct and efficient object

code,

3) run-time organization, and

4) formatting output according to

assembler and/or linker conventions.

A compiler consists of three main

parts: the frontend, the middle-end, and the

backend.

The front end: -

It checks whether the program is

correctly written in terms of the

programming language syntax and

semantics. Here legal and illegal programs

are recognized. Errors are reported, if any,

in a useful way. Type checking is also

performed by collecting type information.

The frontend then generates an

intermediate representation or IR of the

source code for processing by the middle-

end.

The middle end: -

It is indicate where optimization

takes place. Typical transformations for

optimization are removal of useless or

unreachable code, discovery and

propagation of constant values, relocation

of computation to a less frequently

executed place (e.g., out of a loop), or

specialization of computation based on the

context. The middle-end generates another

IR for the following backend. Most

optimization efforts are focused on this

part.

The back end:- is responsible for

translating the IR from the middle-end into

assembly code. The target instruction(s)

are chosen for each IR instruction.

Register allocation assigns processor

registers for the program variables where

possible. The backend utilizes the

hardware by figuring out how to keep

parallel execution units busy, filling delay

slots, and so on. Although most algorithms

for optimization are in NP, heuristic

techniques are well-developed.

Page 5: Role of Data Structure in Compiler Design

Figure of structure of compiler design

We have seen different applications

of template meta-programming such as

static data structures, algorithms, design

patterns, Reflection, expression templates,

and number theory. Compile Time Data

Structure is, in fact, not a new concept in

C++. Further information about it can be

found in the References. Here, we are

going to study the Linked List as an

example of a compile time data structure,

and will try to implement it with template

meta-programming.

Template meta-programming is

usually difficult to understand at first,

especially for those who are not familiar

with it. Therefore, we will discuss the run-

time counterpart at the same time. We use

a naming convention “List” for all the

runtime programs to distinguish it with the

compile-time version. Although we can

implement the run-time programs in more

than one way, in the compile-time world,

we have only recursion to do everything

including looping; therefore, we are going

to use recursion in the run-time version to

compile Time Linked List 

If we are going to make a single

linked list, then our structureof linked list

would be something like this:

/////////////////////////////////////////////////////////// //

Node of Runtime Link List 

/////////////////////////////////////////////////////////// 

struct ListNode

{

int value;ListNode* next;

};

Here is the compile time version of this

structure:

/////////////////////////////////////////////////////////// //

Termination of Link List 

/////////////////////////////////////////////////////////// 

struct End

{

};

/////////////////////////////////////////////////////////// //

Node of Static Link List 

/////////////////////////////////////////////////////////// 

template <int iData, typename Type>

struct Node

{

enum { value = iData };

typedef Type Next;};

Here, we need one more structure

to indicate the termination condition of the

linked list. You can also call it is an end

marker. In the runtime version, we don’t

need it because in that case, we simply

Page 6: Role of Data Structure in Compiler Design

check the value of the next filed in the

node. If its value is NULL, then it means

that it is the last node of the linked list

However, in the case of template

meta-programming, we have to do

template specialization (or partial template

specialization) to stop the recursion. We

can do template specialization for any

specific type or for any specific value.

Here, we can’t do template specialization

on a value, because the second template

parameter is a type. Therefore, we have to

create a new type to stop the recursive

instantiation of the template. The name of

the end marker can be anything, and it can

store whatever you like it to. In our

example, it would be sufficient to make an

empty structure to create a new type as an

end marker.

Now, let’s try to implement a few

auxiliary functions that work on lists. Here

is our first function to insert data in the

linked list. We explicitly made its name

look similar to the member function of the

STL list class, because we are going to

implement a few more STL algorithms.

Here is the simplest

implementation for inserting items in the

runtime single linked list.

/////////////////////////////////////////////////////////// 

// Insert item into Runtime Link List 

/////////////////////////////////////////////////////////// 

void ListPushBack(ListNode** pHead, int value)

{

if(*pHead==NULL)

{

*pHead = new ListNode();

(*pHead)->value = value;

}

Else

{

ListPushBack(&(*pHead)->next, value);

}

}

The name of the linked list is

prefixed with “List” to distinguish it from

the compile time version.

Interestingly, the compile time

version of the same function doesn’t use

recursion, and its implementation is very

easy

/////////////////////////////////////////////////////////// 

// Insert item into Static Link List 

/////////////////////////////////////////////////////////// 

template <int iData, typename Type>

struct PushBack

{

typedef Node<iData, Type> staticList;

};

And, here is the usage of this function at

compile time:

typedef PushBack<2, End>::staticList node1;

typedef PushBack<4, node1>::staticList node2;

typedef PushBack<6, node2>::staticList node3;

typedef PushBack<8, node3>::staticList node4;

typedef PushBack<10, node4>::staticList node5;

typedef PushBack<12, node5>::staticList myList;

Although we can create a static linked list

like this:

typedef Node<-15, Node<15, Node<18, Node<13, End>

> > >myList;

Page 7: Role of Data Structure in Compiler Design

the above method to create a static

linked list has a few advantages, which we

will see when we implement a few STL

algorithms in the compile-time version.

Now, let’s implement a few more

STL list algorithms at compile time. But,

first take a look at its runtime version to

better understand template meta-

programming.

/////////////////////////////////////////////////////////// 

// Structure to calculate the length of

Runtime Link List 

/////////////////////////////////////////////////////////// 

int ListSize(ListNode* pHead)

{

if (pHead == NULL)

return 0;

else

return 1 + ListSize(pHead->next);

}

This function is quite simple, and

uses tail recursion for optimization. The

compiler can optimize tail recursion with

looping to avoid any runtime stack

overhead. Here is the compile-time version

of the same function:

/////////////////////////////////////////////////////////// 

// Structure to calculate the length of Static

Link List 

/////////////////////////////////////////////////////////// 

template <typename T>

struct Size;

template <int iData, typename Type>

struct Size<Node<iData, Type> >

{

enum { value = 1 + Size<Type>::value };

};

template <>

struct Size<End>

{enum { value = 0 };

};

Although the STL list class doesn’t

have an at() function, because list doesn’t

have a random access iterator, we are

trying to implement this function for the

linked list. Because we can’t access any

item of the linked list randomly, it is a

linear time function, not a constant time

one just like the at() function of the vector.

Here is the simple run-time

implementation of the at() function on a

single linked list with linear complexity:

/////////////////////////////////////////////////////////// 

// Structure to find item from specific

location from RuntimeLink List 

/////////////////////////////////////////////////////////// 

int ListAt(ListNode* pHead, int iPos)

{

static int iIndex = 0;

++iIndex;

if (iIndex == iPos)

return pHead->value;

else if (pHead->next == NULL)

return -1;

else

return ListAt(pHead->next, iPos);

}

The code presented here is just a

proof of concept, not a production quality

code. One major problem with this

function is the return code. If the input

position is greater than the length of the

Page 8: Role of Data Structure in Compiler Design

linked list, like the length of the linked list

is 4, but we are trying to access 6th

element, then this function returns-1. This

code is misleading, because -1 can be a

data in the linked list at a given position,

so it would be impossible to differentiate

whether it is an error code or the actual

data at the given position.

This one is a much better

implementation than the previous one:

///////////////////////////////////////////////////////////

 // Structure to find item from specific

location from Runtime Link List 

/////////////////////////////////////////////////////////// 

bool ListAt(ListNode* pHead, int iPos, int* iVal)

{

static int iIndex = 0;

++iIndex;

if (iIndex == iPos)

{

*iVal = pHead->value;return true;

}

else if (pHead->next == NULL)

{return false; }

Else

{

return ListAt(pHead->next, iPos, iVal);

}

}

This function returns the value at a

specific location by parameter. If the user

passes a position that is greater than the

length of the linked list, then it will return

false; otherwise, it stores the value at the

parameter and returns true.

Here is the usage of this function:

if (pHead != NULL)

{

int val;

 if (ListAt(pHead, 3, &mp;val))

{std::cout << val << std::endl;}

}

Here is the compile time version of the

same function:

///////////////////////////////////////////////////////////

 // Structure to find item from specific

location from Static Link List 

/////////////////////////////////////////////////////////// 

template <int iIndex, typename T, int iStart = 0>struct

At;

template <int iIndex, int iData, typename Type, int

iStart>struct At<iIndex, Node<iData, Type>, iStart>

{

enum { value = iIndex == iStart ?iData : At<iIndex,

Type, (iStart + 1)>::value };

};

template <int iIndex, int iData, int iStart>struct

At<iIndex, Node<iData, End>, iStart>

{

enum { value = iIndex == iStart ? iData : -1 };

};

This program has the same

problem that it returns -1 when the item is

not found. In template meta-programming,

we can’t return a value by parameter just

like its run-time equivalent. The solution is

to introduce one more enum variable

inside the structure to store whether the

item was found or not.

Here is the next version of the

same program:

///////////////////////////////////////////////////////////

Page 9: Role of Data Structure in Compiler Design

 // Structure to find item from specific

location from Static Link List 

/////////////////////////////////////////////////////////// 

template <int iIndex, typename T, int iStart = 0>struct

At;

template <int iIndex, int iData, typename Type, int

iStart>struct At<iIndex, Node<iData, Type>, iStart>

{

enum { value = iIndex == iStart ?iData : At<iIndex,

Type, (iStart + 1)>::value };

enum { found = iIndex == iStart ? 1 :At<iIndex, Type,

(iStart + 1)>::found };

};

template <int iIndex, int iData, int iStart>struct

At<iIndex, Node<iData, End>, iStart>

{

enum { value = iIndex == iStart ? iData : -1 };

enum { found = iIndex == iStart ? 1 : 0 };

};

Although the value variable still

stores -1 when an item not found in the

linked list, if we use the other variable, i.e.,

the “found” variable, then we can ignore

this value.

Here is the usage of this algorithm :

if (At<8, myList>::found == 1)

{

std::cout << At<8, myList>::value <<

std::endl;

}

4. Compile Time Algorithms

In this section, we are going to

study different STL algorithms and their

working with the STL list class, and how

to implement those at compile time using

template meta-programming. From now,

instead of using creating our own single

linked list and implementing all the related

functions ourselves, we are going to use

the STL list class.

4.1. Find Algorithm

The Find algorithm returns the first

occurrence of the specified value in the

given range. If it couldn’t find the

specified value, then it returns the end

iterator, i.e., one past the last element

given a range.

Here is a simple usage of the Find

algorithm onan STL list:

std::list<int>lst;

lst.push_back(7);

lst.push_back(14);

lst.push_back(21);

lst.push_back(28);

lst.push_back(35);

std::list<int>::iterator iter_ = std::find(lst.begin(),

lst.end(), 7);

if (iter_ != lst.end())

std::cout << *iter_ << std::endl;

else

std::cout << "Not found" << std::endl;

Here is the closest implementation

of the Find algorithm in template meta-

programming:

///////////////////////////////////////////////////////////

 // Structure to find the location of specific

item in Static Link List 

/////////////////////////////////////////////////////////// 

template <typename TBegin,typename TEnd,int

iFindData,int iStart = 0>struct Find;

template <int iData,typename TBegin,typename TEnd,int

iFindData,int iStart>

Page 10: Role of Data Structure in Compiler Design

struct Find<Node<iData, TBegin>, TEnd, iFindData,

iStart>

{

enum { value = iFindData == iData ?

iStart :Find<TBegin, TEnd, iFindData, (iStart +

1)>::value };

};

template <int iData,typename TEnd,int iFindData,int

iStart>

struct Find<Node<iData, TEnd>, TEnd, iFindData,

iStart>

{

enum { value = iFindData == iData ? iStart : -1 };

};

template <typename TEnd, int iFindData>struct

Find<TEnd, TEnd, iFindData>

{

enum { value = -1 };

};

This implementation returns the

position of the specified value, if found in

the given range of a single linked list;

otherwise, it returns -1.

Here is the usage of the above

implementation:

typedef PushBack<7, End>::staticList node1;

typedef PushBack<14, node1>::staticList node2;

typedef PushBack<21, node2>::staticList node3;

typedef PushBack<28, node3>::staticList node4;

typedef PushBack<35, node4>::staticList

myList;std::cout << Find<myList, End, 7>::value <<

std::endl;

We can even pass the range, just

like in the STL algorithms, to find

elements in between a given range.

std::cout << Find<node3, node1, 7>::value << std::endl;

 

This is one of the reasons to use the

static version of the Push Back

implementation rather than create a whole

static linked list in one statement. By using

the compile time version of the Push Back

implementation, we are able to get the

iterator equivalent in the compile time

world.

4.2. Count Algorithm

This algorithm returns the numbers

of items in a given range that has the given

value.

Here is a sample to demonstrate

this:

std::list<int>

lst;lst.push_back(7);

lst.push_back(14);

lst.push_back(7);

lst.push_back(14);

lst.push_back(21);

lst.push_back(7);

std::cout << std::count(lst.begin(), lst.end(), 7)

<< std::endl;

The output of this program is 3,

because 7 exists three times inthe given

range. Here is a similar algorithm

implemented atcompile time:

///////////////////////////////////////////////////////////

 // Count Algorithm. Returns the number

of elements in StaticLink List 

/////////////////////////////////////////////////////////// 

template <typename TBegin,

typename TEnd,

int iVal>struct Count;

template <int iData,

typename TBegin,

Page 11: Role of Data Structure in Compiler Design

typename TEnd,

int iVal>struct Count<Node<iData, TBegin>, TEnd,

iVal>

{

enum { value = (iData == iVal ? 1 : 0) +

Count<TBegin, TEnd, iVal>::value };

};

template <int iData, typename TEnd, int iVal>struct

Count<Node<iData, TEnd>, TEnd, iVal>

{

enum { value = iData == iVal ? 1 : 0 };

};

template <typename TEnd, int iVal>struct Count<TEnd,

TEnd, iVal>

{

enum { value = 0 };

};

This implementation is very similar

to the “Find” algorithm, and its usage is

also similar to that. Here is the closest

implementation of the compile-time

version of the same code we saw in this

section using the STL “count” algorithm.

typedef PushBack<7, End>::staticList node1;

typedef PushBack<14, node1>::staticList

node2;

typedef PushBack<7, node2>::staticList node3;

typedef PushBack<14, node3>::staticList

node4;

typedef PushBack<21, node4>::staticList

node5;

typedef PushBack<7, node5>::staticList

myList;

std::cout << Count<myList, End, 7>::value <<

std::endl;

Just like the “Find” algorithm, we

can also count the number of specified

items in a given range rather than

searching in the complete static linked list.

4.3. Find If Algorithm

If we want to find whether a linked

list contains an item that is greater than 10

but less than 20, then we couldn’t do it

with the Find algorithm. Here is the

solution to this problem, the Find If

algorithm. This algorithm returns the

iterator of the element in the linked list that

satisfies the condition in the given

predicate. The predicate can be a simple

function or a function object, i.e., a class

with an overloaded () operator, that returns

true. In that function (or function object),

we can check any condition we want. Let’s

take a look at the run-time version to better

understand it before going to the compile

time version.

std::list<int>lst;

lst.push_back(7);

lst.push_back(14);

lst.push_back(7);

lst.push_back(14);

lst.push_back(21);

lst.push_back(7);

std::list<int>::iterator iter_

=std::find_if(lst.begin(), lst.end(), MyPredicate());

if (iter_ != lst.end())

std::cout << *iter_ << std::endl;

else

std::cout << "Not found" << std::endl;

Here, we pass MyPridicate as a

function object, and here is the

implementation of it:

struct MyPredicate

{

bool operator()(int val)

{

Page 12: Role of Data Structure in Compiler Design

return val > 10 && val < 20 ? true : false;

}

};

Implementing the compile time

version of The Find If algorithm is not

very difficult, and its looks very similar to

the Find algorithm, except one addition to

the predicate type parameter. Here is the

compile time version of theFind If

algorithm:

///////////////////////////////////////////////////////////

 // Find If Algorithm.

// Locate the specific items in Static Link

List that satisfy thepredicate

/////////////////////////////////////////////////////////// 

template <typename TBegin,

typename TEnd,

template <int iData> class Predicate,

int iStart = 0>

struct FindIf;

template <int iData,typename TBegin,

typename TEnd,

template <int iData> class Predicate,

int iStart>

struct FindIf<Node<iData, TBegin>,

TEnd, Predicate, iStart>

{

enum { value = Predicate<iData>::value

== 1 ? iStart :FindIf<TBegin, TEnd,

Predicate, (iStart+1)>::value };

};

template <int iData,typename TEnd,

template <int iData>

class Predicate,int iStart>struct

FindIf<Node<iData, TEnd>, TEnd,

Predicate, iStart>

{

enum { value = Predicate<iData>::value

== 1 ? iStart : -1 };

};

template <typename TEnd,

template <int iData> class Predicate>struct

FindIf<TEnd, TEnd, Predicate>

{enum { value = -1 };

};

Here we use template-template

parameter to make it easy tocall and

similar to STL algorithm. Here is the usage

of this algorithm.

typedef PushBack<7, End>::staticList node1;

typedef PushBack<14, node1>::staticList node2;

typedef PushBack<7, node2>::staticList node3;

typedef PushBack<14, node3>::staticList node4;

typedef PushBack<21, node4>::staticList node5;

typedef PushBack<7, node5>::staticList myList;std::cout

<< FindIf<myList, End, MyPredicate>::value

<<std::endl;

In compile time world, the

predicate is a structure (or class), not a

function or function object. Here is our

compile time version of the same predicate

we used earlier in this section for the run

time version:

template <int val>struct MyPredicate

{

enum { value = val > 10 && val < 20 ? 1 : 0 };

};

4.4. Count If Algorithm

Page 13: Role of Data Structure in Compiler Design

The “Count if” algorithm is a

cousin of the “Find If”; the only difference

is, it will return the number of elements in

the linked list that satisfies the condition

given in the predicate. Let’s take a look at

the run time version of this algorithm first.

std::list<int>lst;

lst.push_back(7);

lst.push_back(14);

lst.push_back(7);

lst.push_back(14);

lst.push_back(21);

lst.push_back(7);

std::cout << std::count_if(lst.begin(), lst.end(),

MyPredicate())<< std::endl;

And, we use the same predicate that we

made earlier:

struct MyPredicate{bool operator()(int val)

{

  return val > 10 && val < 20 ? true : false;

}

};

The output of this program is 2,

because there are two elements in the list

which are greater than 10 and less than 20.

Now, let’s take a look at the compile time

version of the same algorithm.

///////////////////////////////////////////////////////////

 // Count If Algorithm.

// Returns the number of elements in Static

Link List 

// that satisfy the predicate condition

/////////////////////////////////////////////////////////// 

template <typename TBegin, typename TEnd,template

<int iData> class Predicate>struct CountIf;

template <int iData, typename TBegin, typename

TEnd,template <int iData> class Predicate>struct

CountIf<Node<iData, TBegin>, TEnd, Predicate>

{

enum { value = (Predicate<iData>::value)

+CountIf<TBegin, TEnd, Predicate>::value };

};

template <int iData, typename TEnd,template <int

iData> class Predicate>struct CountIf<Node<iData,

TEnd>, TEnd, Predicate>

{

enum { value = Predicate<iData>::value };

};

template <typename TEnd,template <int iData> class

Prediate>struct CountIf<TEnd, TEnd, Prediate>

{enum { value = 0 };

};

This implementation is very similar

to the “Find If” algorithm. The only

difference is, here, we are actually count

the number of elements that satisfy the

condition given in the predicate rather than

find it. Here is the usage of this algorithm:

typedef PushBack<7, End>::staticList node1;

typedef PushBack<14, node1>::staticList

node2;

typedef PushBack<7, node2>::staticList node3;

typedef PushBack<14, node3>::staticList

node4;

typedef PushBack<21, node4>::staticList

node5;

typedef PushBack<7, node5>::staticList

myList;std::cout << CountIf<myList, End,

MyPredicate>::value <<std::endl;

4.5. Min, Max Algorithm

Although the STL version of the

min and max algorithms don’t work on a

range, but for consistency, we

Page 14: Role of Data Structure in Compiler Design

implemented both algorithms in a similar

way, i.e., you can find the maximum or

minimum value in a static linked list in a

given range.

Here are the simple implementations of

both the algorithms:

///////////////////////////////////////////////////////////

 // Structure to find the Maximum value in

the Link List 

/////////////////////////////////////////////////////////// 

template <typename TBegin, typename TEnd>struct

Max;

template <int iData, typename TBegin, typename

TEnd>struct Max<Node<iData, TBegin>, TEnd >

{

enum { value = iData > Max<TBegin, TEnd>::value ?

iData : Max<TBegin, TEnd>::value };

};

template <int iData, typename TEnd>struct

Max<Node<iData, TEnd>, TEnd>

{

enum { value = iData };

};

/////////////////////////////////////////////////////////// 

// Structure to find the Minimum value in

the Link List 

/////////////////////////////////////////////////////////// 

template <typename TBegin, typename TEnd>struct

Min;

template <int iData, typename TBegin, typename

TEnd>struct Min<Node<iData, TBegin>, TEnd>

{

enum { value = iData < Min<TBegin, TEnd>::value ?

iData : Min<TBegin, TEnd>::value };

};

template <int iData, typename TEnd>struct

Min<Node<iData, TEnd>, TEnd>

{

enum { value = iData };

};

And, here is a simple usage of

these algorithms:

std::cout << Max<myList, End>::value <<

std::endl;

std::cout << Min<myList, End>::value <<

std::endl;

If we use the same static linked list

that we created in the previous section,

then we will get 21 and 7 as the output as

the maximum and minimum values in the

static linked list, respectively. Just like the

other algorithms, here, we can also specify

the range to get the maximum or minimum

value in a given range, not in the complete

static linked list.

4.6. For Each Algorithm

Till now, all the algorithms we

discussed were just getting information

from the linked list. None of the above

algorithms changed any values in the

linked list. This is because once you have

some value, then it is very difficult, if not

impossible, to change the value we

assigned at compile time. Now, we are

going to discuss an algorithm that

performs some operation on the values of

the linked list, but here, we explicitly

restrict ourselves to not change any value

in the linked list. Let’s take a look at this

simple example of the “For Each”

Page 15: Role of Data Structure in Compiler Design

algorithm to print the elements of the

linked list:

std::for_each(lst.begin(),

lst.end(),PrintFunctionObject());

Here, “PrintFunctionObject” is our function

object to do the actual work. Here is a simple

implementation of this predicate:

struct PrintFunctionObject

{

void operator()(int val)

{std::cout << val << std::endl;}

};

Remember, it can also be a simple function

instead of a function object. Here is the

compile time equivalent version of the

same algorithm:

///////////////////////////////////////////////////////////

 // For each algorithm. Apply some action

on all items of the list 

/////////////////////////////////////////////////////////// 

template <typename TBegin, typename TEnd,template

<typename T> class Function>struct ForEach;

template <int iData, typename TBegin, typename

TEnd,template <typename T> class Function>struct

ForEach<Node<iData, TBegin>, TEnd, Function>

{

void operator ()()

{

Function<TBegin>()(iData);

ForEach<TBegin, TEnd, Function>()();

}

};

template <int iData, typename TEnd,template <typename

T> class Function>struct ForEach<Node<iData, TEnd>,

TEnd, Function>

{

void operator ()(){Function<TEnd>()(iData);

}

} ;

template <typename TEnd,template <typename T> class

Function>struct ForEach<TEnd, TEnd, Function>

{void operator ()(){}

};

If you have taken a close look at

this implementation, then you may have

already noticed that this implementation is

quite different from all of those we

discussed earlier. In this implementation,

there isn’t any “enum” variable; on the

contrary, here we have the overload ()

operator. This is because when we have to

perform some action, we have to call some

function to do the actual work. This

program is actually a mixture of run-time

and compile-time programming. All of the

programs we discussed earlier were

completely resolved by the compiler at the

time of compiling and the actual result

stored in the executable. However, in this

case, although we are doing compile time

computation during compilation using

Recursion, the actual function call will still

execute when you run this program. This is

the only way to perform some IO

operations while doing template meta-

programming, i.e., using functions that

will execute at runtime. Here, we are not

only limited to performing IO operations

but can do a few more interesting things,

like create a class using the new operator

or a set of class hierarchies, as described in

“Modern C++ Design”[1]. Here is the

function object for this compile time

Page 16: Role of Data Structure in Compiler Design

implementation of the “For Each”

algorithm.

template <typename T>struct

PrintFunctonObject

{

void operator ()(int iData)

{std::cout << iData << std::endl;}

};

The usage of this algorithm is also

slightly different. Here, we actually want

to execute some code at run-time;

therefore, we have to create an object of

the “ForEach” structure. Here is one

possible way to execute this algorithm

using an anonymous object:

ACKNOWLEGEMENT

Firstly I want to say to thanks of

my subject teacher and education

management, because they think able me

of this topic of term paper and give the

golden opportunity to prepare of term

paper. M y t e r m p a p e r t o p i c i s

“ R o l e o f D a t a Structure in Compiler

Design”.  This topic is related to my

subject Data Structure; this topic help

know how can compile the software’s.

This topic is clear my more concept. I wish

to express to my gratitude to all the people

involved in the writing of this project,

specially “Sandeep Sharma” sir who was

very generous in sharing their time and

knowledge with me. I thank to “LPU” Wi-

Fi; they are give fully support.

REFERENCES:

(1) http://www.engr.sjsu.edu/

wbarrett/Parser/CompDesign.pdf

(2) http://www.capsl.udel.edu/

conferences/open64/2008/Papers/

111.pdf

(3) http://en.wikipedia.org/wiki/

Compiler

(4) Modern C++ Design Generic Programming.

(5) C++ Template Metaprogramming: Concepts.

(6) Reflection support by means of template Meta- programming , Guiseppe Attardi, Antonio Cisternino

(7) Static Data Structure: Reconciling Template Meta- programming and Generic Programming.

(8) Loops, Metaloops & C++ , Roshan Naik.6. Expression Templates, Veldhuizen, C++ Report 1995,Generative Programming – Methods, Tools and Applications7. Template Meta Programm