Inferno  0.2
graph.cpp
Go to the documentation of this file.
00001 /*
00002  * graph.cpp
00003  *
00004  *  Created on: 18 Jul 2010
00005  *      Author: jgraley
00006  */
00007 
00008 #include "tree/cpptree.hpp"
00009 #include "helpers/transformation.hpp"
00010 #include "sr/search_replace.hpp"
00011 #include "sr/soft_patterns.hpp"
00012 #include "common/trace.hpp"
00013 #include "common/read_args.hpp"
00014 #include "graph.hpp"
00015 #include "steps/inferno_patterns.hpp"
00016 #include <inttypes.h>
00017 
00018 using namespace CPPTree;
00019 
00020 // Graph Documantation
00021 //
00022 // The shapes and contents of the displayed nodes is explained in comments in the function 
00023 // Graph::Name(), which may be found below. Ordinary tree nodes are always rectangles with
00024 // soft corners, so this function mainly deals with special nodes as used by Search and Replace.
00025 // The colours are defined in Graph::Colour, which categorises nodes (all nodes, including Special)
00026 // by whether they are derived from particular intermediate nodes, which are easily seen in
00027 // the code of the function.
00028 
00029 
00030 // TODO indicate pre-restriction by putting class name over the link. Only when type is not that
00031 // of the pointer, ie a non-trivial pre-estriction
00032 // TODO indicate Stuff restrictor by making it come out of the top of the circle (note that it will
00033 // be used on search, and usually search Stuff is coupled to replace Stuff, which will be below.
00034 // TODO force ranking to space out graph as
00035 // Primary: Stuff nodes, SearchReplace
00036 // Secondary: Normal nodes and special nodes that occupy space
00037 // Tertiary: CompareReplace and special nodes that do not occupy space
00038 
00039 #define FS_SMALL "12"
00040 #define FS_MIDDLE "14"
00041 #define FS_LARGE "16"
00042 #define NS_SMALL "0.4"
00043 //#define FONT "Arial"
00044 
00045 Graph::Graph( string of ) :
00046     outfile(of)
00047 {
00048 }
00049 
00050 void Graph::operator()( Transformation *root )
00051 {    
00052     string s;
00053     s += Header();
00054     s += MakeGraphTx(root); 
00055   s += Footer();
00056   Disburse( s );
00057 }
00058 
00059 string Graph::MakeGraphTx(Transformation *root)
00060 {
00061     string s;
00062     if( TransformationVector *tv = dynamic_cast<TransformationVector *>(root) )
00063     {
00064         FOREACH( shared_ptr<Transformation> t, *tv )
00065             s = MakeGraphTx( t.get() ) + s; // seem to have to pre-pend to get them appearing in the right order
00066     }
00067     else
00068     {
00069         unique_filter.Reset();
00070       s += UniqueWalk( root, Id(root), false );
00071         unique_filter.Reset();
00072       s += UniqueWalk( root, Id(root), true );
00073   }
00074   return s;
00075 }
00076 
00077 
00078 TreePtr<Node> Graph::operator()( TreePtr<Node> context, TreePtr<Node> root )
00079 {
00080   (void)context; // Not needed!!
00081 
00082   string s;
00083   s += Header();
00084     unique_filter.Reset();
00085   s += UniqueWalk( root, false );
00086     unique_filter.Reset();
00087   s += UniqueWalk( root, true );
00088   s += Footer();
00089   Disburse( s );
00090 
00091   return root; // no change
00092 }
00093 
00094 
00095 string Graph::Header()
00096 {
00097   string s;
00098   s += "digraph Inferno {\n"; // g is name of graph
00099   s += "graph [\n";
00100   s += "rankdir = \"LR\"\n"; // left-to-right looks more like source code
00101   s += "size = \"14,20\"\n"; // make it smaller
00102   //  s += "concentrate = \"true\"\n"; 
00103   s += "];\n";
00104   s += "node [\n";
00105 #ifdef FONT
00106     s += "fontname = \"" FONT "\"\n";
00107 #endif    
00108   s += "];\n";
00109   return s;
00110 }
00111 
00112 
00113 string Graph::Footer()
00114 {
00115   string s;
00116   s += "}\n";
00117   return s;
00118 }
00119 
00120 
00121 void Graph::Disburse( string s )
00122 {
00123   if( outfile.empty() )
00124   {
00125     puts( s.c_str() );
00126   }
00127   else
00128   {
00129     FILE *fp = fopen( outfile.c_str(), "wt" );
00130     ASSERT( fp )( "Cannot open output file \"%s\"", outfile.c_str() );
00131     fputs( s.c_str(), fp );
00132     fclose( fp );
00133   }
00134 }
00135 
00136 
00137 string Graph::UniqueWalk( TreePtr<Node> root, bool links_pass )
00138 {
00139   string s;
00140     TRACE("Graph plotter traversing intermediate %s pass\n", links_pass ? "links" : "nodes");
00141   ::UniqueWalk w( root );
00142   FOREACH( TreePtr<Node> n, w )
00143   {
00144     if( n )
00145       s += links_pass ? DoNodeLinks(n) : DoNode(n);
00146   }
00147   return s;
00148 }
00149 
00150 
00151 string Graph::UniqueWalk( Transformation *sr, string id, bool links_pass )
00152 {
00153   string s;
00154     s += links_pass ? DoTransformationLinks(sr, id) : DoTransformation(sr, id);
00155     
00156     struct : public Filter
00157     {
00158         virtual bool IsMatch( TreePtr<Node> context,
00159                               TreePtr<Node> root )
00160         {
00161             return !( !dynamic_pointer_cast<TransformOfBase>(root) && 
00162                       !dynamic_pointer_cast<BuildIdentifierBase>(root) && 
00163                       dynamic_cast<Transformation*>(root.get()) );
00164         }
00165     } no_tx_filter;
00166     
00167     vector<string> labels;
00168     vector< TreePtr<Node> > links;
00169     (void)sr->GetGraphInfo( &labels, &links );
00170     
00171     FOREACH( TreePtr<Node> pattern, links )
00172     {
00173         if( pattern )
00174         {
00175             TRACE("Walking transform pattern ")(*pattern)("\n");
00176             Walk w( pattern, &unique_filter, &no_tx_filter ); // return each node only once; do not recurse through transformations
00177             FOREACH( TreePtr<Node> n, w )
00178             {              
00179                 s += links_pass ? DoNodeLinks(n) : DoNode(n);
00180             }
00181         }
00182     }
00183   return s;
00184 }
00185 
00186 
00187 string Graph::DoTransformation( Transformation *sr,
00188                             string id )
00189 {    
00190     vector<string> labels;
00191     vector< TreePtr<Node> > links;
00192     sr->GetGraphInfo( &labels, &links );
00193         
00194     string name = sr->GetName(); 
00195     int n;
00196     for( n=0; n<name.size(); n++ )
00197     {
00198         if( name[n] == '<' )        
00199         {
00200             int nn;
00201             for( nn=n; nn<name.size(); nn++ )
00202             {            
00203                 if( name[nn] == '>' )        
00204                 {
00205                     name = name.substr( 0, n ) + name.substr( nn+1 );
00206                     break;
00207                 }
00208             }
00209         }
00210     }
00211         
00212     string s;
00213   s += id;
00214   s += " [\n";
00215 
00216     s += "label = \"<fixed> " + Sanitise( name );
00217   for( int i=0; i<labels.size(); i++ )        
00218     s += " | <" + labels[i] + "> " + labels[i];
00219   s += "\"\n";
00220 
00221   s += "shape = \"record\"\n"; // nodes can be split into fields
00222   s += "style = \"filled\"\n";
00223   s += "fontsize = \"" FS_MIDDLE "\"\n";
00224   s += "];\n";
00225 
00226   return s;
00227 }
00228 
00229 
00230 string Graph::DoTransformationLinks( Transformation *sr, string id )
00231 {
00232     vector<string> labels;
00233     vector< TreePtr<Node> > links;
00234     sr->GetGraphInfo( &labels, &links );
00235 
00236     string s;
00237     for( int i=0; i<labels.size(); i++ )        
00238         s += id + ":" + labels[i] + " -> " + Id(links[i].get()) + " [];\n";
00239 
00240     return s;
00241 }
00242 
00243 
00244 string Graph::Id( void *p )
00245 {
00246   char s[20];
00247   sprintf(s, "\"%p\"", p );
00248   return s;
00249 }
00250 
00251 
00252 string Graph::SeqField( int i, int j )
00253 {
00254   char s[20];
00255   sprintf( s, "port%c%d", 'a'+i, j );
00256   return s;
00257 }
00258 
00259 
00260 string Graph::Sanitise( string s, bool remove_tp )
00261 {
00262   string o;
00263   if( remove_tp ) // get rid of TreePtr<>
00264       s = s.substr( 8, s.size()-9 );
00265   int n = s.find("::");
00266   if( n != string::npos )
00267       s = s.substr( n+2 );
00268   for( int i=0; i<s.size(); i++ )
00269   {
00270     if( s[i] == '\"' )
00271     {
00272       o += "\\\"";
00273     }
00274     else if( s[i] == '<' )
00275     {
00276       o += "&lt;";
00277     }
00278     else if( s[i] == '>' )
00279     {
00280       o += "&gt;";
00281     }
00282     else
00283     {
00284       o += s[i];
00285     }
00286   }
00287   return o;
00288 }
00289 
00290 
00291 string Graph::Name( TreePtr<Node> sp, bool *bold, string *shape )   // TODO put stringize capabilities into the Property nodes as virtual methods
00292 {
00293     // This function does not deal directly with SearchReplace, CompareReplace, SlaveSearchReplace or 
00294     // SlaveCompareReplace. These appear in sharp-cornered rectangles, with the name at the top and the
00295     // member TreePtr names below. These may be some combination of search, compare, replace and through
00296     // and their links are approximately to the right.
00297   *bold=true;
00298   if( dynamic_pointer_cast<StarBase>(sp) )
00299   {
00300       // The Star node appears as a small circle with a * character inside it. * is chosen for its role in 
00301       // filename wildcarding, which is semantically equiviant only when used in a Sequence.
00302     *shape = "circle";
00303     return string("*");
00304   }
00305   else if( TreePtr<StuffBase> stuff = dynamic_pointer_cast<StuffBase>(sp) )
00306   {
00307       // The Stuff node appears as a small circle with a # character inside it. The terminus link emerges from the
00308       // right of the circle. If there is a recurse restriction the circle is egg-shaped and the restriction link 
00309       // emerges from the top of the egg shape. # is chosen (as is the name Stuff) for its similarity to * because
00310       // the nodes are both able to wildcard multiple nodes in the tree.
00311     if( stuff->recurse_restriction )
00312         *shape = "egg";
00313     else
00314           *shape = "circle";
00315     return string("#"); 
00316   }
00317   else if( dynamic_pointer_cast<AnyNodeBase>(sp) )
00318   {
00319       // The AnyNode node appears as a small circle with a ? sign in it. The terminus link emerges from the
00320       // right of the circle. ? implies the tendancy to match exactly one thing.
00321         *shape = "circle";
00322     return string("?"); 
00323   }
00324   else if( dynamic_pointer_cast<NotMatchBase>(sp) )
00325   {
00326       // The NotMatch node appears as a small circle with an ! character inside it. The affected subtree is 
00327       // on the right.
00328       // NOTE this and the next few special nodes are the nodes that control the action of the search engine in 
00329       // Inferno search/replace. They are not the nodes that represent the operations in the program being processed.
00330       // Those nodes would appear as rounded rectangles with the name at the top. The nmes may be found in
00331       // src/tree/operator_db.txt  
00332     *shape = "circle";
00333     return string("!");
00334   }
00335   else if( dynamic_pointer_cast<MatchAllBase>(sp) )
00336   {
00337       // The MatchAll node appears as a small circle with an & character inside it. The affected subtrees are 
00338       // on the right.
00339     *shape = "circle";
00340     return string("&"); // note & is a wildcard in dot but not handled properly, this becomes "& ". At least some of the time.
00341   }
00342   else if( dynamic_pointer_cast<MatchAnyBase>(sp) )
00343   {
00344       // The MatchAny node appears as a small circle with an | character inside it. The affected subtrees are 
00345       // on the right.
00346     *shape = "circle";
00347     return string("|");
00348   }
00349   else if( dynamic_pointer_cast<MatchOddBase>(sp) )
00350   {
00351       // The MatchOdd node appears as a small circle with an | character inside it. The affected subtrees are 
00352       // on the right.
00353     *shape = "circle";
00354     return string("^");
00355   }
00356     else if( shared_ptr<TransformOfBase> tob = dynamic_pointer_cast<TransformOfBase>(sp) )
00357     {
00358         // The TransformOf node appears as a slightly flattened hexagon, with the name of the specified 
00359         // kind of Transformation class inside it.
00360         *shape = "hexagon";
00361         return *(tob->transformation);
00362     }
00363     else if( dynamic_pointer_cast<PointerIsBase>(sp) )
00364     {
00365         // The TransformOf node appears as a slightly flattened hexagon, with the name of the specified 
00366         // kind of Transformation class inside it.
00367         *shape = "pentagon";
00368         return string("pointer is"); 
00369     }
00370   else if( shared_ptr<BuildIdentifierBase> smi = dynamic_pointer_cast<BuildIdentifierBase>(sp) )
00371   {
00372       // The BuildIdentifier node appears as a parallelogram (rectangle pushed to the side) with
00373       // the printf format string that controls the name of the generated identifier inside it.
00374       // TODO indicate whether it's building instance, label or type identifier
00375     *shape = "parallelogram";
00376     return smi->format;
00377   }
00378   else if( shared_ptr<IdentifierByNameBase> ibnb = dynamic_pointer_cast<IdentifierByNameBase>(sp) )
00379   {
00380       // The IdentifierByNameBase node appears as a trapezium (rectangle narrower at the top) with
00381       // the string that must be matched inside it.
00382       // TODO indicate whether it's matching instance, label or type identifier
00383     *shape = "trapezium";
00384     return ibnb->name;
00385   }
00386   else if( dynamic_pointer_cast<GreenGrassBase>(sp) )
00387   {
00388       // The GreenGrass node appears as a small circle containing four vertical line characters,
00389       // like this: ||||. These are meant to represent the blades of grass. It was late and I was
00390       // tired.
00391     *shape = "circle";
00392     return string("||||");
00393   }
00394     else if( dynamic_pointer_cast<OverlayBase>(sp) )
00395     {
00396         // The Overlay node is shown as a small triangle, with the through link on the right and the overlay link
00397         // coming out of the bottom.
00398         *shape = "triangle";
00399         return string(""); 
00400     }
00401     else if( dynamic_pointer_cast<InsertBase>(sp) )
00402     {
00403         // The Insert node is shown as a small triangle containing a + symbol, with the insert link
00404         // coming out of the bottom
00405         *shape = "triangle";
00406         return string("+"); 
00407     }
00408     else if( dynamic_pointer_cast<EraseBase>(sp) )
00409     {
00410         // The Erase node is shown as a small triangle containing a - symbol, with the erase link
00411         // coming out of the right
00412         *shape = "triangle";
00413         return string("-"); 
00414     }
00415   else
00416   {
00417       // All the other nodes are represented as a rectangle with curved corners. At the top of the rectangle, 
00418       // in large font, is the name of the node's type OR the identifier name if the node is a kind of 
00419       // SpecificIdentifier. All TreePtr<>, Sequence<> and Collection<> members are listed below in a 
00420       // smaller font. The name of the pointed-to type is given (not the member's name, Inferno cannot deduce
00421       // this). 
00422       // Collections appear once and are followed by {...} where the number of dots equals the number of 
00423       // elements in the Collection.
00424       // Sequences appear once for each element in the sequence. Each appearance is followed by [i] where
00425       // i is the index, starting from 0.
00426       // All child pointers emerge from *approximately* the right of the corresponding member name. I cannot
00427       // for the life of me get GraphViz to make the lines begin *on* the right edge of the rectangle. They 
00428       // always come from some way in from the right edge, and if they are angled up or down, they can appear
00429       // to be coming from the wrong place.      
00430     *bold = false;
00431     *shape = "plaintext";//"record";
00432     return sp->GetName();
00433   }
00434 }
00435 
00436 
00437 // Colours are GraphVis colours as listed at http://www.graphviz.org/doc/info/colors.html
00438 string Graph::Colour( TreePtr<Node> n )
00439 {
00440   if( dynamic_pointer_cast<Identifier>(n) )
00441     return "gray60";
00442   else if( dynamic_pointer_cast<Declaration>(n) )
00443     return "seagreen1";
00444   else if( dynamic_pointer_cast<MapOperand>(n) )
00445     return "goldenrod2";
00446   else if( dynamic_pointer_cast<Type>(n) )
00447     return "plum";
00448   else if( dynamic_pointer_cast<Literal>(n) )
00449     return "goldenrod2";
00450   else if( dynamic_pointer_cast<Expression>(n) )
00451     return "chocolate1";
00452   else if( dynamic_pointer_cast<Property>(n) )
00453     return "olivedrab3";
00454   else if( dynamic_pointer_cast<Statement>(n) )
00455     return "brown1";
00456   else if( dynamic_pointer_cast<Initialiser>(n) )
00457     return "olivedrab3";
00458   else if( dynamic_pointer_cast<Scope>(n) )
00459     return "cyan";
00460   else
00461     return "";
00462 }
00463 
00464 
00465 string Graph::HTMLLabel( string name, TreePtr<Node> n )
00466 {
00467   string s = "<<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\">\n";
00468   s += " <TR>\n";
00469   s += "  <TD><FONT POINT-SIZE=\"" FS_LARGE ".0\">" + Sanitise(name) + "</FONT></TD>\n";
00470   s += "  <TD></TD>\n";
00471   s += " </TR>\n";
00472   vector< Itemiser::Element * > members = n->Itemise();
00473   for( int i=0; i<members.size(); i++ )
00474   {
00475     if( SequenceInterface *seq = dynamic_cast<SequenceInterface *>(members[i]) )
00476     {
00477       for( int j=0; j<seq->size(); j++ )
00478       {
00479         char c[20];
00480         sprintf(c, "%d", j);
00481         s += " <TR>\n";
00482         s += "  <TD>" + Sanitise(*seq, true) + "[" + string(c) + "]</TD>\n";
00483         s += "  <TD PORT=\"" + SeqField( i, j ) + "\"></TD>\n";
00484         s += " </TR>\n";
00485       }
00486     }
00487     else if( TreePtrInterface *ptr = dynamic_cast<TreePtrInterface *>(members[i]) )
00488     {
00489       if( *ptr )
00490       {
00491         s += " <TR>\n";
00492         s += "  <TD>" + Sanitise(ptr->GetName(), true) + "</TD>\n";
00493         s += "  <TD PORT=\"" + SeqField( i ) + "\"></TD>\n";
00494         s += " </TR>\n";
00495        }
00496     }
00497     else if( CollectionInterface *col = dynamic_cast<CollectionInterface *>(members[i]) )
00498     {
00499       s += " <TR>\n";
00500       s += "  <TD>" + Sanitise(*col, true) + "{";
00501             for( int j=0; j<col->size(); j++ )
00502                 s += ".";
00503             s += "}</TD>\n";
00504       s += "  <TD PORT=\"" + SeqField( i ) + "\"></TD>\n";
00505       s += " </TR>\n";
00506     }
00507     else
00508     {
00509       ASSERT(0);
00510     }
00511   }
00512   s += "</TABLE>>\n";
00513   return s;
00514 }
00515 
00516 
00517 string Graph::SimpleLabel( string name, TreePtr<Node> n )
00518 {
00519   string s = "\"<fixed> " + Sanitise(name);
00520   vector< Itemiser::Element * > members = n->Itemise();
00521   for( int i=0; i<members.size(); i++ )
00522   {
00523     if( SequenceInterface *seq = dynamic_cast<SequenceInterface *>(members[i]) )
00524     {
00525       for( int j=0; j<seq->size(); j++ )
00526       {
00527         s += " | <" + SeqField( i, j ) + "> ";
00528       }
00529     }
00530     else  if( TreePtrInterface *ptr = dynamic_cast<TreePtrInterface *>(members[i]) )
00531     {
00532       s += " | <" + SeqField( i ) + "> ";
00533     }
00534     else // Collection
00535     {
00536       s += " | <" + SeqField( i ) + "> ";// \\{\\}";
00537     }
00538   }
00539   s += "\"\n";
00540   return s;
00541 }
00542 
00543 
00544 string Graph::DoNode( TreePtr<Node> n )
00545 {
00546     if( !dynamic_pointer_cast<TransformOfBase>(n) && !dynamic_pointer_cast<BuildIdentifierBase>(n) ) // ignire the fact that these also derive from Transformation
00547         if( Transformation *rsb = dynamic_cast<Transformation *>(n.get()) )
00548         return UniqueWalk( rsb, Id( n.get() ), false );
00549 
00550   string s;
00551   bool bold;
00552   string shape;
00553   s += Id(n.get()) + " [\n";
00554   string name = Name(n, &bold, &shape);
00555 
00556   s += "shape = \"" + shape + "\"\n";
00557   if(shape == "record" || shape == "plaintext")
00558   {
00559     s += "label = " + HTMLLabel( name, n );
00560     s += "style = \"rounded,filled\"\n";
00561     s += "fontsize = \"" FS_SMALL "\"\n";
00562   }
00563   else
00564   {
00565     s += "label = \"" + name + "\"\n";// TODO causes errors because links go to targets meant for records
00566     s += "style = \"filled\"\n";
00567 
00568     if( shape == "circle" || shape=="triangle" )
00569     {
00570       s += "fixedsize = true\n";
00571       s += "width = " NS_SMALL "\n";
00572       s += "height = " NS_SMALL "\n";
00573       s += "fontsize = \"" FS_LARGE "\"\n";
00574     }
00575     else
00576     {
00577       s += "fontsize = \"" FS_MIDDLE "\"\n";
00578     }
00579   }
00580 
00581   string c = Colour(n);
00582   if(c!="")
00583     s += "fillcolor = " + c + "\n";
00584 
00585   s += "];\n";
00586   return s;
00587 }
00588 
00589 
00590 string Graph::DoNodeLinks( TreePtr<Node> n )
00591 {
00592     if( !dynamic_pointer_cast<TransformOfBase>(n) && !dynamic_pointer_cast<BuildIdentifierBase>(n) ) // ignire the fact that these also derive from Transformation
00593         if( Transformation *rsb = dynamic_cast<Transformation *>(n.get()) )
00594             return UniqueWalk( rsb, Id( n.get() ), true );
00595 
00596     string s;
00597     TRACE("Itemising\n");
00598   vector< Itemiser::Element * > members = n->Itemise();
00599     TRACE("Doing links for ")(*n)(" size is %d\n", members.size() );
00600     for( int i=0; i<members.size(); i++ )
00601   {
00602     if( CollectionInterface *col = dynamic_cast<CollectionInterface *>(members[i]) )
00603     {
00604       FOREACH( const TreePtrInterface &p, *col )
00605         s += DoLink( n, SeqField(i), p, string(), &p );
00606     }
00607     else if( SequenceInterface *seq = dynamic_cast<SequenceInterface *>(members[i]) )
00608     {
00609       int j=0;
00610       FOREACH( const TreePtrInterface &p, *seq )
00611         s += DoLink( n, SeqField(i, j++), p, string(), &p );
00612     }
00613     else if( TreePtrInterface *ptr = dynamic_cast<TreePtrInterface *>(members[i]) )
00614     {
00615         TRACE("TreePtr %d is @%p\n", i, ptr );
00616       if( *ptr )
00617         s += DoLink( n, SeqField(i), *ptr, string(), ptr );
00618     }
00619     else
00620     {
00621       ASSERT(0)("got something from itemise that isnt a sequence, collection or a shared pointer");
00622     }
00623   }
00624   return s;
00625 }
00626 
00627 
00628 bool Graph::IsRecord( TreePtr<Node> n )
00629 {
00630   bool bold;
00631   string shape;
00632   bool fport, tport;
00633   Name( n, &bold, &shape );
00634   return shape=="record" || shape=="plaintext";
00635 }
00636 
00637 string Graph::DoLink( TreePtr<Node> from, string field, TreePtr<Node> to, string atts, const TreePtrInterface *ptr )
00638 {
00639   string s;
00640   s += Id(from.get());
00641   if( field != "" && IsRecord(from) )
00642   {
00643     s += ":" + field;
00644     atts = "dir = \"both\"\n";
00645         atts = "arrowtail = \"dot\"\n";
00646       //  atts = "sametail = \"" + field + "\"\n";
00647     if( ptr )                 // is normal tree link
00648         if( shared_ptr<SpecialBase> sbs = dynamic_pointer_cast<SpecialBase>(to) )   // is to a special node
00649             if( typeid( *ptr ) != typeid( *(sbs->GetPreRestrictionArchitype()) ) )          // pre-restrictor is nontrivial
00650       {
00651           atts += "label = \"" + (**(sbs->GetPreRestrictionArchitype())).GetName() + "\"\n";
00652       }
00653   }
00654     else if( dynamic_pointer_cast<StuffBase>(from) )
00655     {
00656         if( field == "porta0" )
00657             s+= ":n";
00658         else
00659             s+= ":e";
00660     }
00661     else if( dynamic_pointer_cast<OverlayBase>(from) )
00662     {
00663         if( field == "portb0" )
00664             s+= ":s";
00665         else
00666             s+= ":e";
00667     }
00668     else if( dynamic_pointer_cast<InsertBase>(from) )
00669     {
00670         s+= ":s";
00671     }
00672 
00673     else if( dynamic_pointer_cast<EraseBase>(from) )
00674     {
00675         s+= ":e";
00676     }
00677 
00678   s += " -> ";
00679   s += Id(to.get());
00680   s += " ["+atts+"];\n";
00681   return s;
00682 }
00683