Next: , Previous: Symbolic functions, Up: Extending GiNaC


6.3 GiNaC's expression output system

GiNaC allows the output of expressions in a variety of different formats (see Input/output). This section will explain how expression output is implemented internally, and how to define your own output formats or change the output format of built-in algebraic objects. You will also want to read this section if you plan to write your own algebraic classes or functions.

All the different output formats are represented by a hierarchy of classes rooted in the print_context class, defined in the print.h header file:

print_dflt
the default output format
print_latex
output in LaTeX mathematical mode
print_tree
a dump of the internal expression structure (for debugging)
print_csrc
the base class for C source output
print_csrc_float
C source output using the float type
print_csrc_double
C source output using the double type
print_csrc_cl_N
C source output using CLN types

The print_context base class provides two public data members:

     class print_context
     {
         ...
     public:
         std::ostream & s;
         unsigned options;
     };

s is a reference to the stream to output to, while options holds flags and modifiers. Currently, there is only one flag defined: print_options::print_index_dimensions instructs the idx class to print the index dimension which is normally hidden.

When you write something like std::cout << e, where e is an object of class ex, GiNaC will construct an appropriate print_context object (of a class depending on the selected output format), fill in the s and options members, and call

     void ex::print(const print_context & c, unsigned level = 0) const;

which in turn forwards the call to the print() method of the top-level algebraic object contained in the expression.

Unlike other methods, GiNaC classes don't usually override their print() method to implement expression output. Instead, the default implementation basic::print(c, level) performs a run-time double dispatch to a function selected by the dynamic type of the object and the passed print_context. To this end, GiNaC maintains a separate method table for each class, similar to the virtual function table used for ordinary (single) virtual function dispatch.

The method table contains one slot for each possible print_context type, indexed by the (internally assigned) serial number of the type. Slots may be empty, in which case GiNaC will retry the method lookup with the print_context object's parent class, possibly repeating the process until it reaches the print_context base class. If there's still no method defined, the method table of the algebraic object's parent class is consulted, and so on, until a matching method is found (eventually it will reach the combination basic/print_context, which prints the object's class name enclosed in square brackets).

You can think of the print methods of all the different classes and output formats as being arranged in a two-dimensional matrix with one axis listing the algebraic classes and the other axis listing the print_context classes.

Subclasses of basic can, of course, also overload basic::print() to implement printing, but then they won't get any of the benefits of the double dispatch mechanism (such as the ability for derived classes to inherit only certain print methods from its parent, or the replacement of methods at run-time).

6.3.1 Print methods for classes

The method table for a class is set up either in the definition of the class, by passing the appropriate print_func<C>() option to GINAC_IMPLEMENT_REGISTERED_CLASS_OPT() (See Adding classes, for an example), or at run-time using set_print_func<T, C>(). The latter can also be used to override existing methods dynamically.

The argument to print_func<C>() and set_print_func<T, C>() can be a member function of the class (or one of its parent classes), a static member function, or an ordinary (global) C++ function. The C template parameter specifies the appropriate print_context type for which the method should be invoked, while, in the case of set_print_func<>(), the T parameter specifies the algebraic class (for print_func<>(), the class is the one being implemented by GINAC_IMPLEMENT_REGISTERED_CLASS_OPT).

For print methods that are member functions, their first argument must be of a type convertible to a const C &, and the second argument must be an unsigned.

For static members and global functions, the first argument must be of a type convertible to a const T &, the second argument must be of a type convertible to a const C &, and the third argument must be an unsigned. A global function will, of course, not have access to private and protected members of T.

The unsigned argument of the print methods (and of ex::print() and basic::print()) is used for proper parenthesizing of the output (and by print_tree for proper indentation). It can be used for similar purposes if you write your own output formats.

The explanations given above may seem complicated, but in practice it's really simple, as shown in the following example. Suppose that we want to display exponents in LaTeX output not as superscripts but with little upwards-pointing arrows. This can be achieved in the following way:

     void my_print_power_as_latex(const power & p,
                                  const print_latex & c,
                                  unsigned level)
     {
         // get the precedence of the 'power' class
         unsigned power_prec = p.precedence();
     
         // if the parent operator has the same or a higher precedence
         // we need parentheses around the power
         if (level >= power_prec)
             c.s << '(';
     
         // print the basis and exponent, each enclosed in braces, and
         // separated by an uparrow
         c.s << '{';
         p.op(0).print(c, power_prec);
         c.s << "}\\uparrow{";
         p.op(1).print(c, power_prec);
         c.s << '}';
     
         // don't forget the closing parenthesis
         if (level >= power_prec)
             c.s << ')';
     }
     
     int main()
     {
         // a sample expression
         symbol x("x"), y("y");
         ex e = -3*pow(x, 3)*pow(y, -2) + pow(x+y, 2) - 1;
     
         // switch to LaTeX mode
         cout << latex;
     
         // this prints "-1+{(y+x)}^{2}-3 \frac{x^{3}}{y^{2}}"
         cout << e << endl;
     
         // now we replace the method for the LaTeX output of powers with
         // our own one
         set_print_func<power, print_latex>(my_print_power_as_latex);
     
         // this prints "-1+{{(y+x)}}\uparrow{2}-3 \frac{{x}\uparrow{3}}{{y}
         //              \uparrow{2}}"
         cout << e << endl;
     }

Some notes:

It's not possible to restore a method table entry to its previous or default value. Once you have called set_print_func(), you can only override it with another call to set_print_func(), but you can't easily go back to the default behavior again (you can, of course, dig around in the GiNaC sources, find the method that is installed at startup (power::do_print_latex in this case), and set_print_func that one; that is, after you circumvent the C++ member access control...).

6.3.2 Print methods for functions

Symbolic functions employ a print method dispatch mechanism similar to the one used for classes. The methods are specified with print_func<C>() function options. If you don't specify any special print methods, the function will be printed with its name (or LaTeX name, if supplied), followed by a comma-separated list of arguments enclosed in parentheses.

For example, this is what GiNaC's ‘abs()’ function is defined like:

     static ex abs_eval(const ex & arg) { ... }
     static ex abs_evalf(const ex & arg) { ... }
     
     static void abs_print_latex(const ex & arg, const print_context & c)
     {
         c.s << "{|"; arg.print(c); c.s << "|}";
     }
     
     static void abs_print_csrc_float(const ex & arg, const print_context & c)
     {
         c.s << "fabs("; arg.print(c); c.s << ")";
     }
     
     REGISTER_FUNCTION(abs, eval_func(abs_eval).
                            evalf_func(abs_evalf).
                            print_func<print_latex>(abs_print_latex).
                            print_func<print_csrc_float>(abs_print_csrc_float).
                            print_func<print_csrc_double>(abs_print_csrc_float));

This will display ‘abs(x)’ as ‘|x|’ in LaTeX mode and fabs(x) in non-CLN C source output, but as abs(x) in all other formats.

There is currently no equivalent of set_print_func() for functions.

6.3.3 Adding new output formats

Creating a new output format involves subclassing print_context, which is somewhat similar to adding a new algebraic class (see Adding classes). There is a macro GINAC_DECLARE_PRINT_CONTEXT that needs to go into the class definition, and a corresponding macro GINAC_IMPLEMENT_PRINT_CONTEXT that has to appear at global scope. Every print_context class needs to provide a default constructor and a constructor from an std::ostream and an unsigned options value.

Here is an example for a user-defined print_context class:

     class print_myformat : public print_dflt
     {
         GINAC_DECLARE_PRINT_CONTEXT(print_myformat, print_dflt)
     public:
         print_myformat(std::ostream & os, unsigned opt = 0)
          : print_dflt(os, opt) {}
     };
     
     print_myformat::print_myformat() : print_dflt(std::cout) {}
     
     GINAC_IMPLEMENT_PRINT_CONTEXT(print_myformat, print_dflt)

That's all there is to it. None of the actual expression output logic is implemented in this class. It merely serves as a selector for choosing a particular format. The algorithms for printing expressions in the new format are implemented as print methods, as described above.

print_myformat is a subclass of print_dflt, so it behaves exactly like GiNaC's default output format:

     {
         symbol x("x");
         ex e = pow(x, 2) + 1;
     
         // this prints "1+x^2"
         cout << e << endl;
     
         // this also prints "1+x^2"
         e.print(print_myformat()); cout << endl;
     
         ...
     }

To fill print_myformat with life, we need to supply appropriate print methods with set_print_func(), like this:

     // This prints powers with '**' instead of '^'. See the LaTeX output
     // example above for explanations.
     void print_power_as_myformat(const power & p,
                                  const print_myformat & c,
                                  unsigned level)
     {
         unsigned power_prec = p.precedence();
         if (level >= power_prec)
             c.s << '(';
         p.op(0).print(c, power_prec);
         c.s << "**";
         p.op(1).print(c, power_prec);
         if (level >= power_prec)
             c.s << ')';
     }
     
     {
         ...
         // install a new print method for power objects
         set_print_func<power, print_myformat>(print_power_as_myformat);
     
         // now this prints "1+x**2"
         e.print(print_myformat()); cout << endl;
     
         // but the default format is still "1+x^2"
         cout << e << endl;
     }