rpcgen.cpp
上传用户:yhdzpy8989
上传日期:2007-06-13
资源大小:13604k
文件大小:22k
源码类别:

生物技术

开发平台:

C/C++

  1. /*
  2.  * ===========================================================================
  3.  * PRODUCTION $Log: rpcgen.cpp,v $
  4.  * PRODUCTION Revision 1000.3  2004/06/01 19:43:40  gouriano
  5.  * PRODUCTION PRODUCTION: UPGRADED [GCC34_MSVC7] Dev-tree R1.13
  6.  * PRODUCTION
  7.  * ===========================================================================
  8.  */
  9. /*  $Id: rpcgen.cpp,v 1000.3 2004/06/01 19:43:40 gouriano Exp $
  10. * ===========================================================================
  11. *
  12. *                            PUBLIC DOMAIN NOTICE
  13. *               National Center for Biotechnology Information
  14. *
  15. *  This software/database is a "United States Government Work" under the
  16. *  terms of the United States Copyright Act.  It was written as part of
  17. *  the author's official duties as a United States Government employee and
  18. *  thus cannot be copyrighted.  This software/database is freely available
  19. *  to the public for use. The National Library of Medicine and the U.S.
  20. *  Government have not placed any restriction on its use or reproduction.
  21. *
  22. *  Although all reasonable efforts have been taken to ensure the accuracy
  23. *  and reliability of the software and data, the NLM and the U.S.
  24. *  Government do not and cannot warrant the performance or results that
  25. *  may be obtained by using this software or data. The NLM and the U.S.
  26. *  Government disclaim all warranties, express or implied, including
  27. *  warranties of performance, merchantability or fitness for any particular
  28. *  purpose.
  29. *
  30. *  Please cite the author in any work or product based on this material.
  31. *
  32. * ===========================================================================
  33. *
  34. * Author:  Aaron Ucko, NCBI
  35. *
  36. * File Description:
  37. *   ASN.1/XML RPC client generator
  38. *
  39. * ===========================================================================
  40. */
  41. #include <ncbi_pch.hpp>
  42. #include <serial/datatool/exceptions.hpp>
  43. #include <serial/datatool/rpcgen.hpp>
  44. #include <serial/datatool/choicetype.hpp>
  45. #include <serial/datatool/classstr.hpp>
  46. #include <serial/datatool/code.hpp>
  47. #include <serial/datatool/generate.hpp>
  48. #include <serial/datatool/srcutil.hpp>
  49. #include <serial/datatool/statictype.hpp>
  50. #include <serial/datatool/stdstr.hpp>
  51. BEGIN_NCBI_SCOPE
  52. // Does all the actual work
  53. class CClientPseudoTypeStrings : public CClassTypeStrings
  54. {
  55. public:
  56.     CClientPseudoTypeStrings(const CClientPseudoDataType& source);
  57.     void GenerateClassCode(CClassCode& code, CNcbiOstream& getters,
  58.                            const string& methodPrefix, bool haveUserClass,
  59.                            const string& classPrefix) const;
  60. private:
  61.     const CClientPseudoDataType& m_Source;
  62. };
  63. static void s_SplitName(const string& name, string& type, string& field)
  64. {
  65.     for (SIZE_TYPE pos = name.find('.');  pos != NPOS;
  66.          pos = name.find('.', pos + 1)) {
  67.         if (islower(name[pos + 1])) {
  68.             type.assign(name, 0, pos);
  69.             field.assign(name, pos + 1, NPOS);
  70.             return;
  71.         }
  72.     }
  73.     type.assign(name);
  74.     field.erase();
  75. }
  76. static const CChoiceDataType* s_ChoiceType(const CDataType* dtype,
  77.                                            const string& element)
  78. {
  79.     vector<string> v;
  80.     if ( !element.empty() ) {
  81.         NStr::Tokenize(element, ".", v);
  82.     }
  83.     ITERATE (vector<string>, subelement, v) {
  84.         const CDataMemberContainerType* dct
  85.             = dynamic_cast<const CDataMemberContainerType*>(dtype);
  86.         if ( !dct ) {
  87.             NCBI_THROW(CDatatoolException, eInvalidData,
  88.                        dtype->GlobalName() + " is not a container type");
  89.         }
  90.         bool found = false;
  91.         ITERATE (CDataMemberContainerType::TMembers, it, dct->GetMembers()) {
  92.             if ((*it)->GetName() == *subelement) {
  93.                 found = true;
  94.                 dtype = (*it)->GetType()->Resolve();
  95.                 break;
  96.             }
  97.         }
  98.         if (!found) {
  99.             NCBI_THROW(CDatatoolException, eInvalidData,
  100.                        dtype->GlobalName() + " has no element " + *subelement);
  101.         }
  102.     }
  103.     const CChoiceDataType* choicetype
  104.         = dynamic_cast<const CChoiceDataType*>(dtype);
  105.     if ( !choicetype ) {
  106.         NCBI_THROW(CDatatoolException, eInvalidData,
  107.                    dtype->GlobalName() + " is not a choice type");
  108.     }
  109.     return choicetype;
  110. }
  111. static string s_SetterName(const string& element) {
  112.     if (element.empty()) {
  113.         return kEmptyStr;
  114.     }
  115.     SIZE_TYPE start = 0, dot;
  116.     string    result;
  117.     do {
  118.         dot = element.find('.', start);
  119.         result += ".Set" + Identifier(element.substr(start, dot - start))
  120.             + "()";
  121.         start = dot + 1;
  122.     } while (dot != NPOS);
  123.     return result;
  124. }
  125. CClientPseudoDataType::CClientPseudoDataType(const CCodeGenerator& generator,
  126.                                              const string& section_name,
  127.                                              const string& class_name)
  128.     : m_Generator(generator), m_SectionName(section_name),
  129.       m_ClassName(class_name)
  130. {
  131.     // Just take the first potential module; should normally give sane
  132.     // results.
  133.     SetParent(generator.GetMainModules().GetModuleSets().front()
  134.               ->GetModules().front().get(),
  135.               class_name);
  136.     s_SplitName(generator.GetConfig().Get(m_SectionName, "request"),
  137.                 m_RequestType, m_RequestElement);
  138.     s_SplitName(generator.GetConfig().Get(m_SectionName, "reply"),
  139.                 m_ReplyType, m_ReplyElement);
  140.     if (m_RequestType.empty()) {
  141.         NCBI_THROW(CDatatoolException, eInvalidData,
  142.                    "No request type supplied for " + m_ClassName);
  143.     } else if (m_ReplyType.empty()) {
  144.         NCBI_THROW(CDatatoolException, eInvalidData,
  145.                    "No reply type supplied for " + m_ClassName);
  146.     }
  147.     m_RequestDataType = m_Generator.ResolveMain(m_RequestType);
  148.     m_ReplyDataType = m_Generator.ResolveMain(m_ReplyType);
  149.     _ASSERT(m_RequestDataType  &&  m_ReplyDataType);
  150.     m_RequestChoiceType = s_ChoiceType(m_RequestDataType, m_RequestElement);
  151.     m_ReplyChoiceType   = s_ChoiceType(m_ReplyDataType,   m_ReplyElement);
  152. }
  153. AutoPtr<CTypeStrings>
  154. CClientPseudoDataType::GenerateCode(void) const
  155. {
  156.     return new CClientPseudoTypeStrings(*this);
  157. }
  158. CClientPseudoTypeStrings::CClientPseudoTypeStrings
  159. (const CClientPseudoDataType& source)
  160.     : CClassTypeStrings(kEmptyStr, source.m_ClassName), m_Source(source)
  161. {
  162.     // SetClassNamespace(generator.GetNamespace()); // not defined(!)
  163.     SetParentClass("CRPCClient<" + source.m_RequestDataType->ClassName()
  164.                    + ", " + source.m_ReplyDataType->ClassName() + '>',
  165.                    CNamespace::KNCBINamespace, "serial/rpcbase");
  166.     SetObject(true);
  167.     SetHaveUserClass(true);
  168.     SetHaveTypeInfo(false);
  169. }
  170. static string s_QualClassName(const CDataType* dt)
  171. {
  172.     _ASSERT(dt);
  173.     string result;
  174.     const CDataType* parent = dt->GetParentType();
  175.     if (parent) {
  176.         result = s_QualClassName(parent) + "::";
  177.     }
  178.     result += dt->ClassName();
  179.     return result;
  180. }
  181. void CClientPseudoTypeStrings::GenerateClassCode(CClassCode& code,
  182.                                                  CNcbiOstream& /* getters */,
  183.                                                  const string& /* methodPfx */,
  184.                                                  bool /* haveUserClass */,
  185.                                                  const string& /* classPfx */)
  186.     const
  187. {
  188.     const string&         sect_name  = m_Source.m_SectionName;
  189.     const string&         class_name = m_Source.m_ClassName;
  190.     string                class_base = class_name + "_Base";
  191.     const CCodeGenerator& generator  = m_Source.m_Generator;
  192.     string                treq       = class_base + "::TRequest";
  193.     string                trep       = class_base + "::TReply";
  194.     const CNamespace&     ns         = code.GetNamespace();
  195.     // Pull in the relevant headers, and add corresponding typedefs
  196.     code.HPPIncludes().insert(m_Source.m_RequestDataType->FileName());
  197.     code.ClassPublic() << "    typedef "
  198.                        << m_Source.m_RequestDataType->ClassName()
  199.                        << " TRequest;n";
  200.     code.HPPIncludes().insert(m_Source.m_ReplyDataType->FileName());
  201.     code.ClassPublic() << "    typedef "
  202.                        << m_Source.m_ReplyDataType->ClassName()
  203.                        << " TReply;n";
  204.     if ( !m_Source.m_RequestElement.empty() ) {
  205.         code.HPPIncludes().insert(m_Source.m_RequestChoiceType->FileName());
  206.         code.ClassPublic() << "    typedef "
  207.                            << s_QualClassName(m_Source.m_RequestChoiceType)
  208.                            << " TRequestChoice;n";
  209.         code.ClassPrivate() << "    TRequest m_DefaultRequest;nn";
  210.     } else {
  211.         code.ClassPublic() << "    typedef TRequest TRequestChoice;n";
  212.     }
  213.     {{
  214.         if ( !m_Source.m_ReplyElement.empty() ) {
  215.             code.HPPIncludes().insert(m_Source.m_ReplyChoiceType->FileName());
  216.             code.ClassPublic() << "    typedef "
  217.                                << s_QualClassName(m_Source.m_ReplyChoiceType)
  218.                                << " TReplyChoice;nn";
  219.         } else {
  220.             code.ClassPublic() << "    typedef TReply TReplyChoice;nn";
  221.         }
  222.         code.ClassPrivate()
  223.             << "    TReplyChoice& x_Choice(TReply& reply);n";
  224.         code.MethodStart(true)
  225.             << trep << "Choice& " << class_base << "::x_Choice(" << trep
  226.             << "& reply)n"
  227.             << "{n    return reply" << s_SetterName(m_Source.m_ReplyElement)
  228.             << ";n}nn";
  229.     }}
  230.     {{
  231.         // Figure out arguments to parent's constructor
  232.         string service = generator.GetConfig().Get(sect_name, "service");
  233.         string format  = generator.GetConfig().Get(sect_name, "serialformat");
  234.         string args;
  235.         if (service.empty()) {
  236.             ERR_POST(Warning << "No service name provided for " << class_name);
  237.             args = "kEmptyStr";
  238.         } else {
  239.             args = '"' + NStr::PrintableString(service) + '"';
  240.         }
  241.         if ( !format.empty() ) {
  242.             args += ", eSerial_" + format;
  243.         }
  244.         code.AddInitializer("Tparent", args);
  245.     }}
  246.     // This should just be a simple using-declaration, but that breaks
  247.     // on GCC 2.9x at least, even with a full parent class name :-/
  248.     code.ClassPublic()
  249.         // << "    using Tparent::Ask;n"
  250.         << "    virtual void Ask(const TRequest& request, TReply& reply);n"
  251.         << "    virtual void Ask(const TRequest& request, TReply& reply,n"
  252.         << "                     TReplyChoice::E_Choice wanted);nn";
  253.     // second version defined further down
  254.     code.MethodStart(true)
  255.         << "void " << class_base << "::Ask(const " << treq << "& request, "
  256.         << trep << "& reply)n"
  257.         << "{n    Tparent::Ask(request, reply);n}nnn";
  258.     // Add appropriate infrastructure if TRequest is not itself the choice
  259.     // (m_DefaultRequest declared earlier to reduce ugliness)
  260.     if ( !m_Source.m_RequestElement.empty() ) {
  261.         string setter = s_SetterName(m_Source.m_RequestElement);
  262.         code.ClassPublic()
  263.             << "n"
  264.             << "    virtual const TRequest& GetDefaultRequest(void) const;n"
  265.             << "    virtual TRequest&       SetDefaultRequest(void);n"
  266.             << "    virtual void            SetDefaultRequest(const TRequest& request);n"
  267.             << "n"
  268.             << "    virtual void Ask(const TRequestChoice& req, TReply& reply);n"
  269.             << "    virtual void Ask(const TRequestChoice& req, TReply& reply,n"
  270.             << "                     TReplyChoice::E_Choice wanted);nn";
  271.         // inline methods
  272.         code.MethodStart(true)
  273.             << "const " << treq << "& " << class_base
  274.             << "::GetDefaultRequest(void) constn"
  275.             << "{n    return m_DefaultRequest;n}nn";
  276.         code.MethodStart(true)
  277.             << treq << "& " << class_base << "::SetDefaultRequest(void)n"
  278.             << "{n    return m_DefaultRequest;n}nn";
  279.         code.MethodStart(true)
  280.             << "void " << class_base << "::SetDefaultRequest(const " << treq
  281.             << "& request)n"
  282.             << "{n    m_DefaultRequest.Assign(request);n}nnn";
  283.         code.MethodStart(false)
  284.             << "void " << class_base << "::Ask(const " << treq
  285.             << "Choice& req, " << trep << "& reply)n"
  286.             << "{n"
  287.             << "    TRequest request;n"
  288.             << "    request.Assign(m_DefaultRequest);n"
  289.             // We have to copy req because SetXxx() wants a non-const ref.
  290.             << "    request" << setter << ".Assign(req);n"
  291.             << "    Ask(request, reply);n"
  292.             << "}nnn";
  293.         code.MethodStart(false)
  294.             << "void " << class_base << "::Ask(const " << treq
  295.             << "Choice& req, " << trep << "& reply, " << trep
  296.             << "Choice::E_Choice wanted)n"
  297.             << "{n"
  298.             << "    TRequest request;n"
  299.             << "    request.Assign(m_DefaultRequest);n"
  300.             // We have to copy req because SetXxx() wants a non-const ref.
  301.             << "    request" << setter << ".Assign(req);n"
  302.             << "    Ask(request, reply, wanted);n"
  303.             << "}nnn";
  304.     }
  305.     // Scan choice types for interesting elements
  306.     typedef CChoiceDataType::TMembers       TChoices;
  307.     typedef map<string, const CDataMember*> TChoiceMap;
  308.     const TChoices& choices   = m_Source.m_RequestChoiceType->GetMembers();
  309.     TChoiceMap      reply_map;
  310.     bool            has_init  = false, has_fini = false, has_error = false;
  311.     ITERATE (TChoices, it, choices) {
  312.         const string& name = (*it)->GetName();
  313.         if (name == "init") {
  314.             if (dynamic_cast<const CNullDataType*>((*it)->GetType())) {
  315.                 has_init = true;
  316.             } else {
  317.                 CNcbiOstrstream oss;
  318.                 (*it)->GetType()->PrintASN(oss, 0);
  319.                 string type = CNcbiOstrstreamToString(oss);
  320.                 _ASSERT(type != "NULL");
  321.                 ERR_POST(Warning << "Ignoring non-null init (type " << type
  322.                          << ") in " << m_Source.m_RequestChoiceType);
  323.             }
  324.         } else if (name == "fini") {
  325.             if (dynamic_cast<const CNullDataType*>((*it)->GetType())) {
  326.                 has_fini = true;
  327.             } else {
  328.                 CNcbiOstrstream oss;
  329.                 (*it)->GetType()->PrintASN(oss, 0);
  330.                 string type = CNcbiOstrstreamToString(oss);
  331.                 _ASSERT(type != "NULL");
  332.                 ERR_POST(Warning << "Ignoring non-null fini (type " << type
  333.                          << ") in " << m_Source.m_RequestChoiceType);
  334.             }
  335.         }
  336.     }
  337.     ITERATE (TChoices, it, m_Source.m_ReplyChoiceType->GetMembers()) {
  338.         const string& name = (*it)->GetName();
  339.         reply_map[name] = it->get();
  340.         if (name == "error") {
  341.             has_error = true;
  342.         }
  343.     }
  344.     if (has_init) {
  345.         code.ClassProtected()
  346.             << "    void x_Connect(void);n";
  347.         code.MethodStart(false)
  348.             << "void " << class_base << "::x_Connect(void)n"
  349.             << "{n"
  350.             << "    Tparent::x_Connect();n"
  351.             << "    AskInit();n"
  352.             << "}nn";
  353.     }
  354.     if (has_fini) {
  355.         code.ClassProtected()
  356.             << "    void x_Disconnect(void);n";
  357.         code.MethodStart(false)
  358.             << "void " << class_base << "::x_Disconnect(void)n"
  359.             << "{n"
  360.             << "    AskFini();n" // ignore/downgrade errors?
  361.             << "    Tparent::x_Disconnect();n"
  362.             << "}nn";
  363.     }
  364.     // Make sure the reply's choice is correct -- rolled into Ask for
  365.     // maximum flexibility.  (Split out methods for the two error cases?)
  366.     code.MethodStart(false)
  367.         << "void " << class_base << "::Ask(const " << treq << "& request, "
  368.         << trep << "& reply, " << trep << "Choice::E_Choice wanted)n"
  369.         << "{n"
  370.         << "    Ask(request, reply);n"
  371.         << "    TReplyChoice& rc = x_Choice(reply);n"
  372.         << "    if (rc.Which() == wanted) {n"
  373.         << "        return; // okn";
  374.     if (has_error) {
  375.         code.Methods(false)
  376.             << "    } else if (rc.IsError()) {n"
  377.             << "        CNcbiOstrstream oss;n"
  378.             << "        oss << "" << class_name
  379.             << ": server error: " << rc.GetError();n"
  380.             << "        NCBI_THROW(CException, eUnknown, CNcbiOstrstreamToString(oss));n";
  381.     }
  382.     code.Methods(false)
  383.         << "    } else {n"
  384.         << "        rc.ThrowInvalidSelection(wanted);n"
  385.         << "    }n"
  386.         << "}nn";
  387.     // Finally, generate all the actual Ask* methods....
  388.     ITERATE (TChoices, it, choices) {
  389.         typedef AutoPtr<CTypeStrings> TTypeStr;
  390.         string name  = (*it)->GetName();
  391.         string reply = m_Source.m_Generator.GetConfig().Get(sect_name,
  392.                                                             "reply." + name);
  393.         if (reply.empty()) {
  394.             reply = name;
  395.         } else if (reply == "special") {
  396.             continue;
  397.         }
  398.         TChoiceMap::const_iterator rm = reply_map.find(reply);
  399.         if (rm == reply_map.end()) {
  400.             NCBI_THROW(CDatatoolException, eInvalidData,
  401.                        "Invalid reply type " + reply + " for " + name);
  402.         }
  403.         string           method   = "Ask" + Identifier(name);
  404.         const CDataType* req_type = (*it)->GetType()->Resolve();
  405.         string           req_class;
  406.         bool             null_req = false;
  407.         if (dynamic_cast<const CNullDataType*>(req_type)) {
  408.             req_class = "void";
  409.             null_req  = true;
  410.         } else if ( !req_type->GetParentType() ) {
  411.             req_class = req_type->ClassName();
  412.         } else {
  413.             TTypeStr typestr = req_type->GetFullCType();
  414.             typestr->GeneratePointerTypeCode(code);
  415.             req_class = typestr->GetCType(ns);
  416.         }
  417.         const CDataType* rep_type = rm->second->GetType()->Resolve();
  418.         string           rep_class;
  419.         bool             use_cref = false;
  420.         bool             null_rep = false;
  421.         if (dynamic_cast<const CNullDataType*>(rep_type)) {
  422.             rep_class = "void";
  423.             null_rep  = true;
  424.         } else if ( !rep_type->GetParentType()  &&  !rep_type->IsStdType() ) {
  425.             rep_class = ns.GetNamespaceRef(CNamespace::KNCBINamespace)
  426.                 + "CRef<" + rep_type->ClassName() + '>';
  427.             use_cref  = true;
  428.             code.CPPIncludes().insert(rep_type->FileName());
  429.         } else {
  430.             TTypeStr typestr = rep_type->GetFullCType();
  431.             typestr->GeneratePointerTypeCode(code);
  432.             rep_class = typestr->GetCType(ns);
  433.         }
  434.         code.ClassPublic()
  435.             << "    virtual " << rep_class << ' ' << method << "n";
  436.         if (null_req) {
  437.             code.ClassPublic() << "        (TReply* reply = 0);nn";
  438.         } else {
  439.             code.ClassPublic() << "        (const " << req_class
  440.             << "& req, TReply* reply = 0);nn";
  441.         }
  442.         code.MethodStart(false)
  443.             << rep_class << ' ' << class_base << "::" << method;
  444.         if (null_req) {
  445.             code.Methods(false) << '(' << trep << "* reply)n";
  446.         } else {
  447.             code.Methods(false)
  448.                 << "(const " << req_class << "& req, " << trep << "* reply)n";
  449.         }
  450.         code.Methods(false)
  451.             << "{n"
  452.             << "    TRequestChoice request;n"
  453.             << "    TReply         reply0;n";
  454.         if (null_req) {
  455.             code.Methods(false)
  456.                 << "    request.Set" << Identifier(name) << "();n";
  457.         } else {
  458.             code.Methods(false)
  459.                 << "    request.Set" << Identifier(name) << "(const_cast<"
  460.                 << req_class << "&>(req));n";
  461.         }
  462.         code.Methods(false)
  463.             << "    if ( !reply ) {n"
  464.             << "        reply = &reply0;n"
  465.             << "    }n"
  466.             << "    Ask(request, *reply, TReplyChoice::e_" << Identifier(reply)
  467.             << ");n";
  468.         if (null_rep) {
  469.             code.Methods(false) << "}nn";
  470.         } else if (use_cref) {
  471.             code.Methods(false)
  472.                 << "    return " << rep_class << "(&x_Choice(*reply).Set"
  473.                 << Identifier(reply) << "());n"
  474.                 << "}nn";
  475.         } else {
  476.             code.Methods(false)
  477.                 << "    return x_Choice(*reply).Get" << Identifier(reply)
  478.                 << "();n"
  479.                 << "}nn";
  480.         }
  481.     }
  482. }
  483. END_NCBI_SCOPE
  484. /*
  485. * ===========================================================================
  486. *
  487. * $Log: rpcgen.cpp,v $
  488. * Revision 1000.3  2004/06/01 19:43:40  gouriano
  489. * PRODUCTION: UPGRADED [GCC34_MSVC7] Dev-tree R1.13
  490. *
  491. * Revision 1.13  2004/05/17 21:03:14  gorelenk
  492. * Added include of PCH ncbi_pch.hpp
  493. *
  494. * Revision 1.12  2004/03/25 21:57:13  ucko
  495. * Allow request and reply elements to have anonymous (internal) types.
  496. *
  497. * Revision 1.11  2004/02/09 15:10:55  ucko
  498. * Make sure to qualify CRef with ncbi:: if necessary.
  499. *
  500. * Revision 1.10  2003/11/20 15:40:53  ucko
  501. * Update for new (saner) treatment of ASN.1 NULLs.
  502. *
  503. * Revision 1.9  2003/10/21 13:48:51  grichenk
  504. * Redesigned type aliases in serialization library.
  505. * Fixed the code (removed CRef-s, added explicit
  506. * initializers etc.)
  507. *
  508. * Revision 1.8  2003/04/08 20:40:08  ucko
  509. * Get client name(s) from [-]clients rather than hardcoding "client"
  510. *
  511. * Revision 1.7  2003/04/04 19:34:25  ucko
  512. * Let request and reply be deeply nested subelements.
  513. * Make s_* actually static.  (Oops.)
  514. *
  515. * Revision 1.6  2003/03/11 20:06:47  kuznets
  516. * iterate -> ITERATE
  517. *
  518. * Revision 1.5  2003/03/10 18:55:19  gouriano
  519. * use new structured exceptions (based on CException)
  520. *
  521. * Revision 1.4  2002/11/18 19:48:46  grichenk
  522. * Removed "const" from datatool-generated setters
  523. *
  524. * Revision 1.3  2002/11/14 16:36:54  ucko
  525. * Rework generated code, rolling x_CheckReply into (an overloaded
  526. * version of) Ask for increased flexibility.
  527. *
  528. * Revision 1.2  2002/11/13 19:55:11  ucko
  529. * Distinguish between named and anonymous types rather than between
  530. * heterogeneous containers and everything else -- fixes handling of aliases.
  531. *
  532. * Revision 1.1  2002/11/13 00:46:08  ucko
  533. * Add RPC client generator; CVS logs to end in generate.?pp
  534. *
  535. *
  536. * ===========================================================================
  537. */