The CINT Interpreter Interface


CINT as embedded in ROOT can be used as command line interpreter and as macro processor, where macros are "small" (up to at least 60000 loc) C++ programs. Thanks to CINT the ROOT system can present the user a single language environment: C++ as implementation, macro and command line language.

The advantages of a single language model are clear. Especially when writing macros. Typically macros start as small prototypes that need frequent modification. While execution speed is not important a short edit-execute cycle is. However, once macros have grown to full programs and have become stable, the need for fast execution in production jobs becomes important. Thanks to the fact that the macros are in "standard" C++ we can simply compile and dynamically link the macros with the ROOT system and execute them at full speed.

First we will show how to use the ROOT/CINT command line interpreter and next how to execute C++ macros. Along the way we'll describe the very few extensions made to CINT's C++ to facilitate a smooth integration with ROOT.

CINT as Command Line Interpreter

Lets run the program myroot that we created in "CINT as Dictionary Generator" and see how we can create and manipulate MyClass objects via the interpreter (for brevity's sake some non essential output has been removed):

$ myroot
  *******************************************
  *                                         *
  *        W E L C O M E  to  R O O T       *
  *                                         *
  *   Version   0.90/12  18 January 1997    *
  *                                         *
  *  You are welcome to visit our Web site  *
  *          http://root.cern.ch            *
  *                                         *
  *******************************************

CINT/ROOT C/C++ Interpreter version 5.11.29, Dec 5 1996
Type ? for help. Commands must be C++ statements.
Enclose multiple statements between { }.

root [0] MyClass a
root [1] a.Print()
fX = -1, fY = -1
root [2] a.SetX(10)
root [3] a.SetY(11)
root [4] a.Print()
fX = 10, fY = 11
root [5] .g
...
...
0x4045c7f8 class MyClass a , size=8
  0x0        private: float fX //x position in centimeters
  0x0        private: float fY //y position in centimeters
root [6] .class MyClass
===========================================================================
class MyClass
 size=0x8
 (tagnum=438,voffset=-1,isabstract=0,parent=-1,gcomp=0,=~cd=0)
List of base class--------------------------------------------------------
List of member variable---------------------------------------------------
Defined in MyClass
0x0        private: float fX //x position in centimeters
0x0        private: float fY //y position in centimeters
List of member function---------------------------------------------------
Defined in MyClass
filename       line:size busy function type and name
(compiled)        0:0    0 public: class MyClass MyClass(void);
(compiled)        0:0    0 public: void Print(void);
(compiled)        0:0    0 public: void SetX(float x);
(compiled)        0:0    0 public: void SetY(float y);
(compiled)        0:0    0 public: class MyClass MyClass(class MyClass&);
(compiled)        0:0    0 public: void ~MyClass(void);
root [7] .q

In the above example we first create an object a of class MyClass. Next we call the Print() method (and notice that the constructor has been executed correctly), set some variables and call again the Print() method. Everything works as expected. Notice that when issueing single line commands we may omit the trailing ";".

Next we execute a "raw" interpreter command .g to list all global symbols currently defined in the interpreter followed by the command .class classname to show the definition of MyClass as known to the interpreter. We exit the session by typing .q. Here we notice that all interpreter commands start with a ".", except for the "?" command which you can use to get a list of all available interpreter commands.

The ROOT command line interface supports emacs style command line editing. Also the command history is saved between sessions.

Now lets execute a multi line command. We start again myroot and type:

root [0] {
end with '}'> MyClass a;
end with '}'> for (int i = 0; i < 5; i++) {
end with '}'>    a.SetX(i);
end with '}'>    a.SetY(i+1);
end with '}'>    a.Print();
end with '}'> }
end with '}'> }
fX = 0, fY = 1
fX = 1, fY = 2
fX = 2, fY = 3
fX = 3, fY = 4
fX = 4, fY = 5
root [1] .q

A multi line command starts always with a "{" and ends with a "}". As long as your are in multi line input mode you are prompted with "end with '}'>". After typing the closing "}" the command will be executed. Notice that now you have to terminate every line, like in C++, with a ";". Inserting many lines in this way requires typing without making any errors. There is no way to correct a once entered line. Therefore it is much easier to put such a sequence of commands in a macro file.

CINT as Macro Processor

CINT macro files contain pure C++ code. They can contain a simple sequence of statements like in the multi command line example given above, but also arbitrarily complex class and function definitions.

Lets start with a macro containing a simple list of statements (like the multi command line example given in the previous section). This type of macro must start with a "{" and end with a "}". Assume the file is called macro1.C:

//--------------------------------------------------
{
#include <iostream.h>

   cout << " Hello" << endl;
   float x = 3.;
   float y = 5.;
   int   i = 101;
   cout << " x = " << x << " y = " << y << " i = " << i << endl;
   return 0;
}
//--------------------------------------------------

To execute the stream of statements in macro1.C do:

root [1] .x macro1.C       // loads the contents of file macro1.C and
                           // executes all statements in the global scope

One can re-execute the statements by re-issueing ".x macro1.C" (since there is no function entry point).

Now copy file macro1.C to macro2.C and add a function statement. Like this:

//--------------------------------------------------
#include <iostream.h>

int main()
{
   cout << " Hello" << endl;
   float x = 3.;
   float y = 5.;
   int   i= 101;
   cout << " x = " << x << " y = " << y << " i = " << i << endl;
   return 0;
}
//--------------------------------------------------

Notice that no surrounding "{" and "}" are required in this case.

To execute function main() in macro2.C do:

root [1] .L macro2.C       // load the contents of file macro2.C in memory
root [2] main()            // execute entry point main()
 Hello
 x = 3 y = 5 i = 101
(int)0
root [3] main()            // execute main() again
 Hello
 x = 3 y = 5 i = 101
(int)0
root [4] .func             // list all functions known by CINT
filename       line:size busy function type and name
...
...
macro2.C          4:9   0 public: int main();

The last command shows that main() has been loaded from file macro2.C, that the function main() starts on line 4 and is 9 lines long. Notice that once a function has been loaded it becomes part of the system just like a compiled function.

Now we copy file macro2.C to macro3.C and change the function name from main() to macro3(int j = 10):

//--------------------------------------------------
#include <iostream.h>

int macro3(int j = 10)
{
   cout << " Hello" << endl;
   float x = 3.;
   float y = 5.;
   int   i = j;
   cout << " x = " << x << " y = " << y << " i = " << i << endl;
   return 0;
}
//--------------------------------------------------

To execute macro3() in macro3.C type:

root [1] .x macro3.C(8)    // loads the contents of file macro3.C and
                           // executes entry point macro3(8)

Note that the above only works when the filename (minus extension) and function entry point are both the same. This shortcut is mainly interesting to run large one-off macros where one wants to pass arguments at the same time (instead of doing the ".L" step first). Function macro3() can still be executed multiple times:

root [2] macro3()
 Hello
 x = 3 y = 5 i = 10
(int)0
root [3] macro3(33)
 Hello
 x = 3 y = 5 i = 33
(int)0

A Macro Containing a Class Definition

You can define new classes in a macro. However, note that a class defined in a macro cannot derive from a compiled class, bu it can have members that are compiled classes.

As an example of a macro containing class definitions we take the small class MyClass that we used in the " CINT as Dictionary Generator" chapter and we introduce a subclass Child:

//--------------------------------------------------
#include <iostream.h>

class MyClass {

private:
   float   fX;     //x position in centimeters
   float   fY;     //y position in centimeters

public:
   MyClass() { fX = fY = -1; }
   virtual void Print() const;
   void         SetX(float x) { fX = x; }
   void         SetY(float y) { fY = y; }
};

void MyClass::Print() const
{
   cout << "fX = " << fX << ", fY = " << fY << endl;
}

class Child : public MyClass {
public:
   void Print() const;
};

void Child::Print() const
{
   cout << "This is Child::Print()" << endl;
   MyClass::Print();
}
//--------------------------------------------------

The only changes we made was to put MyClass.h and the MyClass.C files together in file macro4.C and add the Child class. To make things a little less trivial we made MyClass::Print() virtual and overrode it in the Child class.

To execute macro4.C do:

root [0] .L macro4.C
root [1] MyClass *a = new Child
root [2] a.Print()
This is Child::Print()
fX = -1, fY = -1
root [3] a.SetX(10)
root [4] a.SetY(12)
root [5] a.Print()
This is Child::Print()
fX = 10, fY = 12
root [6] .class MyClass
===========================================================================
class MyClass
 size=0x8 FILE:macro4.C LINE:3
 (tagnum=459,voffset=-1,isabstract=0,parent=-1,gcomp=0,=~cd=1)
List of base class--------------------------------------------------------
List of member variable---------------------------------------------------
Defined in MyClass
0x0        private: float fX
0x4        private: float fY
List of member function---------------------------------------------------
Defined in MyClass
filename       line:size busy function type and name
macro4.C         16:5    0 public: class MyClass MyClass(void);
macro4.C         22:4    0 public: void Print(void);
macro4.C         12:1    0 public: void SetX(float x);
macro4.C         13:1    0 public: void SetY(float y);
root [7] .q

As you can see an interpreted class behaves just like a compiled class (e.g. like MyClass in myroot).

Reseting the Interpreter Environment

Loading files and executing functions will in general result in many objects being created on the interpreter's internal stack. Once in a while it might be necessary to reset the interpreter environment. There are basically two way to reset the environment:

The first method clears the complete interpreter environment, while the second method clears the environment to the status just before executing the last macro. Therefore multi command line macros often start with the statement gROOT.Reset() (where gROOT is a pointer to the global ROOT object, see also TROOT).

Debugging Macros

A powerful feature of CINT is the ablity to debug interpreted methods and functions by means of setting breakpoints and being able to single step through the code and print variable values on the way. Assume we have macro4.C still loaded, we can then do:

root [1] .b Child::Print
Break point set to line 26 macro4.C
root [2] a.Print()

26   Child::Print() const
27   {
28      cout << "This is Child::Print()" << endl;
FILE:macro4.C LINE:28 cint> .s

311  operator<<(ostream& ostr,G__CINT_ENDL& i) {return(endl(ostr));
FILE:iostream.h LINE:311 cint> .s
}
This is Child::Print()

29      MyClass::Print();
FILE:macro4.C LINE:29 cint> .s

16   MyClass::Print() const
17   {
18      cout << "fX = " << fX << ", fY = " << fY << endl;
FILE:macro4.C LINE:18 cint> .p fX
(float)1.000000000000e+01
FILE:macro4.C LINE:18 cint> .s

311  operator<<(ostream& ostr,G__CINT_ENDL& i) {return(endl(ostr));
FILE:iostream.h LINE:311 cint> .s
}
fX = 10, fY = 12

19   }

30   }

2    }
root [3] .q

In the above debug session we first set a breakpoint on the Child::Print() method. Then we execute the method. When we hit the breakpoint CINT returns a prompt and we can issue any of the debug commands (for a full list of debug commands, type a ? at the ROOT prompt). In the above example we single step till we are in MyClass::Print() where we examine the value of fX. We can only set breakpoints on or in functions, therefore macros without function statement can not be debugged (like simple multi command line macros). In such cases just wrap the sequence of statements in a function.

ROOT/CINT Extensions to C++

In the next example we demonstrate three of the most important extensions ROOT/CINT makes to C++. Start basic root or myroot in the directory root/tutorials that comes with the RDK (make sure the file hsimple.root is there):

root [1] f = new TFile("hsimple.root")
(class TFile*)0x4045e690
root [2] f.ls()
TFile**         hsimple.root
 TFile*         hsimple.root
  KEY: TH1F     hpx;1   This is the px distribution
  KEY: TH2F     hpxpy;1 py ps px
  KEY: THProfile        hprof;1 Profile of pz versus px
  KEY: TNtuple  ntuple;1        Demo ntuple
root [3] hpx.Draw()
NULL
Warning in <MakeDefCanvas>: creating a default canvas with name c1
root [4] .q

The first command shows the first extension; the declaration of "f" may be omitted when "new" is used. CINT will correctly create "f" as object of class "TFile".

The second extension is shown in the second command. Although "f" is a pointer to "TFile" we don't have to use the pointer dereferencing syntax "->" but can use the simple "." notation (since every object created by CINT is a heap object so CINT does not need the distinction between heap and stack based objects).

The third extension is more important. In case CINT can not find an object being referenced it will ask ROOT to search for an object with an identical name in the search path defined by TROOT::FindObject(). If ROOT finds the object it returns CINT a pointer to this object and a pointer to its class definition and CINT will execute the requested member function. This shortcut is quite natural for an interactive system and saves a lot of typing, e.g.:

root [4] TH1 *hpx = (TH1*)gROOT.FindObject("hpx")
root [5] hpx.Draw()

Of course when writing large macros, it is best to stay away from these shortcuts since otherwise you will later have problems compiling your macros using a real C++ compiler.


Last update 30/4/97 by FR