#ifndef LURCHPACKAGE #define LURCHPACKAGE #include #include #include #include "lob.h" #include "lobscript.h" #include "lenv.h" #include "flexibleevaluator.h" class QScriptProgram; /** \brief A Lurch Package functions like a Lurch library, but is compiled for speed * * Although all Lurch documents and libraries could be built on script code, it will be * useful to have the most frequently used tools built in C++ for two reasons. First, * they will be compiled, and thus much faster than script code. Second, they will be * subjected to the same rigorous testing to which the rest of Lurch is subjected. * Developers convert a library that should become C++ code into a LurchPackage. * This can be done in phases, as needed. The first phase is simplest, as documented * in \ref makepackageShellScript "the makepackage.sh section, below". * * Each LurchPackage subclass registers itself with the LurchPackage class and the QMetaType * system, providing the specific document URN that it implements. The idea is that a * LurchPackage is just like any other dependency, but instead of a document with scripts * in it, it is a virtual document. Thus it implements asDocument(), so that it can look * like a document from script land, but it also adds other features to the scripting * environment beyond simply appearing as a document. * * LurchEnvironments create their own instances of LurchPackage subclasses, as they * encounter documents that depend on the class's URN. Thus a LurchPackage object can assume * that it has been created to live inside one environment only; other LurchEnvironments will * create other instances of the same class if they need it. The important consequence of * this for designing subclasses of LurchPackage is that, if you're writing a package that * stores data about the currently loaded document, you only need to declare variables * and store data for one document; other instances of your class will exist for other * documents, and the creation of those instances and attaching them to other documents * will be handled by LurchEnvironment. * * LurchEnvironment guarantees three things in its relationship to LurchPackage subclasses. *
    *
  1. The virtual document provided by asDocument() will be scanned for auto-run scripts * just as if it were a real document. This enbles the simple conversion of scripts * to packages using \ref makepackageShellScript "the makepackage.sh script described * below". Such scanning takes place when the document is loaded, and again in a * fresh scripting environment if important changes take place that necessitate * it.
  2. *
  3. Immediately before any such scan of a LurchPackage's asDocument() representation, * there will be a call to that object's setup() routine. This enables any * script-land setup that needs to be done from the C++ side to happen before any * scripts in asDocument() are called.
  4. *
  5. Whenever a new document is loaded, after the load complete successfully, the * initialize() member of every package on which it depends is called. This is so * that any internal data the package wants to track about the document and/or its * dependencies can be initialized. If the document wants to track changes to the * document and update any internal data on an ongoing basis, it should use the * initialize() call to connect one of its slots to the environment's * documentModified() signal. (See initialize() documentation, below.)
  6. *
* * \anchor makepackageShellScript *

The makepackage.sh script

* Take your existing .lurch file, * and simply paste it into a giant C++ string inside a subclass definition, like this. * \code * doc = Lob::fromXML( * "\n" * " \n" * " ...\n" * " \n" * " \n" * " \n" * " ...\n" * " Make this an enormous C string containing\n" * " all the data in your existing .lurch file.\n" * " ...\n" * " \n" * "\n" * ).child().copy(); * * // ... * * Lob asDocument () const * { * return doc; * }; * \endcode * A LurchPackage created this way tells the world about itself solely through * its Lob form, which is what a .lurch file does anyway. So this is a simple wrapping * of a .lurch file in a LurchPackage, and thus is a very easy way to create a LurchPackage * from an existing library that is in a .lurch file. * In fact, there is a handy shell script * makepackage.sh that will do this conversion for you, reading in a .lurch file * and outputting a .h file and a .cpp file. * * \anchor howToWriteALurchPackage *

How to write a LurchPackage subclass

* * To subclass LurchPackage, you need to follow these steps. Failure to do so will result * in your package not being registered with the master list kept in static data structures * of the LurchPackage class, and thus not being visible to LurchEnvironment, documents, * scripts, etc. *
    *
  1. Subclass LurchPackage as usual, implemeting all pure virtual functions and * whatever other functions are necessary for your particular package. * (There are two virtual functions you do not need to implement, metaTypeID() * and classURN(); these will be implemented for you by the LURCH_PACKAGE macro, * described in the following step.) * Be sure to provide your package with an URN unique to your new class * in your implementation of getURN(). When implementing asDocument(), * a good place to start is with Lob::newDocument( getURN() ); thereafter, * comments or scripts can be added as children.
  2. *
  3. In your header file:
    * Ensure that your class is declared with the Q_OBJECT macro, and immediately * below it, also include the LURCH_PACKAGE macro, like this. * \code * class MyPackage : public LurchPackage * { * Q_OBJECT * LURCH_PACKAGE * ... * \endcode * This step ensures that static variables to hold metatype information are added * to your class. *
  4. *
  5. Also in your header file:
    * After your class's declaration, include a line like the following, as per * the * Qt documentation for this macro. * \code * Q_DECLARE_METATYPE( MyPackage ) * \endcode * This step ensures that your subclass is registered with the QMetaType system, * so that it can be constructed and deconstructed from just its name or id * at runtime. (This enables LurchPackage to function as a factory for objects of * your class.)
  6. *
  7. In your source file:
    * Include in your class's constructor an invocation of the * REGISTER_LURCH_PACKAGE() macro, called with two arguments, * the name of your class, and the URN it returns from getURN(). * Here is an example. * \code * MyPackage::MyPackage () * { * REGISTER_LURCH_PACKAGE( * MyPackage, * Lob::makeLurchURN( "My Package Name", "My Name", "en", "v1.0" ) ); * } * \endcode * This step ensures that the metatype data in your class is correctly * set up, and that the static variables in the LurchPackage class know about your * class, and how to ask the QMetaType system to construct instances of it. *
  8. *
*/ class LurchPackage : public QObject { Q_OBJECT public: /** \brief Constructor registers the package in the list of all instances * * Because this parent class constructor handles registering the package with the * global list stored in the static variable allInstances (and accessed with methods * like lookupPackage()), it is essential for descendants to remember to call the parent * class constructor. */ LurchPackage (); /** \brief Copy constructor necessary for QMetaType */ LurchPackage ( const LurchPackage& other ); /** \brief Destructor unregisters the package from the list of all instances * * \see LurchPackage() */ virtual ~LurchPackage (); /** \brief The representation of this package as a Lob * * This has three purposes; the third is the most significant. *
    *
  1. Every dependency of a document, whether it be another document stored on disk * as a .lurch file or a package built into Lurch, should be inspectable by scripts * at runtime. Just as scripts can call numDependencies() and * dependency(i) to fetch actual document dependencies, as Lob * objects that pass the Lob::isDocument() test, they should also be able to do * this for packages. Thus every package needs to represent itself as a Lob * that is a document. It can simply be an empty document with the appropriate * URN for the package, or just a document containing comments or documentation, * but it must be there. The default implementation provides an empty document * whose URN matches that given by getURN().
  2. *
  3. Developers creating packages usually do not wish to code every part of the package * in C++; sometimes it's more convenient to write some of the package in script. * The most common way to do this is to have a separate .js file containing * the script as a Qt resource, which you load with the source() function and pass * to evaluateInNamespace() during the setup() routine. However, * an alternative is to put one or more auto-run script Lobs inside the * document returned from this function. Because a LurchEnvironment treats the * document returned from this function just like any other dependency document, * running any of the auto-run scripts in it, that is a perfectly valid way to * do some or all of the setup this package requires. This is why * \ref makepackageShellScript "the makepackage.sh script described above" * is viable.
  4. *
  5. Developers using packages will expect to be able to open the package and learn some * basic facts about what it does, ideally with a help link to full documentation with * examples. Thus the document returned from this function can have content that * introduces the package and describes briefly what it does, and can have an attribute * that points to a help document, which will show up in the Help menu in interfaces * like Lurch. See how subclasses of LurchPackage use the static functions of * this class to create simple representations of themselves. *
* * Before a LurchEnvironment processes a LurchPackage as a document Lob, * running the auto-run scripts in it, it first calls its setup() function appropriately, * which is what makes packages more than just document Lobs. * * One convenient implementation of this method is in the way that * \ref makepackageShellScript "the makepackage.sh * script described above" does, by simply returning a Lob created from the full XML * code for a document. Another convenient implementation is to create a new document * using documentSkeleton(), and then appending blocks using addText(), addScript(), * describeLoadedScriptCode(), and describeSetupSlotsAsFunctions(). * * If the package is only going to be loaded as a dependency, creating all that visual detail * is just a waste of time. In fact, calls to addScript() are particularly costly (sometimes * even taking a full second for large scripts). For this reason, the asDocument() method * takes the \a forDisplay parameter. When it is true, the results of the call may be * displayed to the user on screen, and thus all visual detail should be included; otherwise, * it can be omitted. Descendants reimplementing asDocument() are encouraged to make use of * this parameter for efficiency reasons. It defaults to false because that is the most * common use case. * * \see LurchEnvironment, setup(), getURN() */ virtual Lob asDocument ( bool forDisplay = false ) const; /** \brief The URN for this package * * This is a pure virtual function because it must be unique for each package. * If asDocument() is implemented to create an entire document Lob with an URN, * then one simple implementation of this function is as follows. * \code * return asDocument().getURN(); * \endcode * However, note that the default implementation of asDocument() relies upon getURN()! * So you should only use the code above if you have reimplemented asDocument(), to * prevent infinite recursion. * * The routine Lob::makeLurchURN() will probably come in handy in implementing this * routine in any way other than the one just given. */ virtual QString getURN () const = 0; /** \brief Sets up a QScriptEnvironment with the routines and data this package provides * * This routine will be called by LurchEnvironment immediately before loading all * auto-run scripts in the result of asDocument(). This is the difference between a * .lurch file dependency and a package dependency; packages get to run this routine, * and use it to add C++-implemented functions to the script environment. * * Note that you can add functions or data or anything you like. * Note further that the setupSlotsAsFunctions() routine in this object is very useful * for making it easy to implement this function with minimal trouble. * * The default implementation of this function calls setupSlotsAsFunctions(), and * that's it. * * \param scope The QScriptValue into which any new data and/or functions should be * placed. Think of it as a namespace. Thus you should avoid making calls * like the following, which add data to the global object. * \code * scope.engine().globalObject().setProperty( "name", value ); * \endcode * Rather, try to use code like the following (unless of course you actually * want to add data/functions to the global namespace). * \code * scope.setProperty( "name", value ); * \endcode */ virtual void setup ( QScriptValue scope ); /** \brief Stores the pointer \a env in a member variable for later use * * This function does three things. *
    *
  1. If the member variable \a environment has a nonzero value, then all signals from * that object to this object are disconnected. This is convenient in case some * descendant class of this one listens to signals in the environment; they will * automatically be disconnected when a new environment is initialized, if that * class's implementation of initialize() remembers to call this version.
  2. *
  3. Stores the parameter \a env in a protected member variable, one thus * accessible to all subclasses.
  4. *
  5. Because the protected member variable is a QPointer, this means that if the * object to which \a env points is ever destroyed, that pointer will automatically * be set to NULL rather than become a dangling pointer.
  6. *
*/ void setEnvironment ( LurchEnvironment* env ); /** \brief Allows a LurchPackage to set up internal data computed from the environment * * A LurchPackage may have internal data stored about the state of the environment * (document and dependencies) to help speed up the functions it provides in script. * This function will be called by the LurchEnvironment whenever a new document is loaded * into the environment, and the document depends on this package. You can assume that * the call is taking place immediately after a successful load of the document, and * you should also assume that your class's data structures may still be filled with old * data from a previous call to initialize() on a previous document, and thus they need * to be cleared out before being filled with new data. * * It is guaranteed that both setEnvironment() and setup() have been called by the * LurchEnvironment on this object before this call to initialize(). Thus it is * guaranteed that the member variable \a environment is non-NULL and points to the * correct, existing LurchEnvironment object, and futhermore that any scripts run by * the setup() function are already complete. See the documentation for setEnvironment() * and setup() for more details on the useful consequences of these guarantees. * * In this function, if you want to establish an ongoing monitoring of the document's * changes, to keep your internal data structures constantly up-to-date with its state, * you should declare a slot such as this one * \code * public slots: * void documentWasModified ( const LobChange& change ); * \endcode * and maintain a single connection to the environment's documentModified() * signal in your initialize routine, like this. * \code * void initialize ( LurchEnvironment* env ) * { * connect( environment, SIGNAL(documentModified(const LobChange&)), * this, SLOT(documentWasModified(const LobChange&)) ); * } * \endcode * This is not the same thing as connecting to the document's modified() * signal, because LurchEnvironment::documentModified() filters out impermissible * changes before they even happen. * * \see LurchEnvironment::documentModified() */ virtual void initialize (); #if 0 // this used to make sense, but isn't needed anymore /** \brief Fetch the LurchPackage instance that corresponds to the given URN * * If there exists in memory a LurchPackage instance whose URN matches the given one, * then it is returned. Otherwise, NULL is returned. */ static LurchPackage* lookupPackage ( QString urn ); #endif /** \brief Compute a list of all URNs corresponding to all LurchPackage classes registered * * This list is complete if and only if users faithfully call the REGISTER_LURCH_PACKAGE * macro \ref howToWriteALurchPackage "as described above". */ static QStringList allURNs (); /** \brief The QMetaType ID of an individual subclass * * Because this function is only for subclasses, it is pure virtual. * Subclasses do not need to explicitly implement it; if they follow * \ref howToWriteALurchPackage "the procedure described above" when writing their * subclass, the REGISTER_LURCH_PACKAGE macro will implement it appropriately. */ virtual int metaTypeID () const = 0; /** \brief The URN of an individual subclass * * Because this function is only for subclasses, it is pure virtual. * Subclasses do not need to explicitly implement it; if they follow * \ref howToWriteALurchPackage "the procedure described above" when writing their * subclass, the REGISTER_LURCH_PACKAGE macro will implement it appropriately. */ virtual QString classURN () const = 0; /** \brief Construct an instance of the subclass whose URN is given * * URNs are unique to a subclass, and are bound to that subclass with a call to the * REGISTER_LURCH_PACKAGE macro \ref howToWriteALurchPackage "as described above". * This function uses the QMetaType system to construct an instance of the subclass * uniquely identified by the given URN. If the URN matches no subclass, NULL is * returned. */ static LurchPackage* create ( QString urn ); /** \brief Register a LurchPackage subclass's QMetaType ID, indexed by its URN * * An internal static map associates subclass URNs with QMetaType IDs. * This function adds a class's URN and ID to that map. Subclass authors should not * need to call this function directly, but just use the REGISTER_LURCH_PACKAGE * macro \ref howToWriteALurchPackage "as described above". */ static int registerSubclass ( QString urn, int id ); protected: /** \brief Search for slots that should be added to the script environment as functions * * This function looks through all slots in this object, and for each slot whose name * begins with script_, it adds that slot to the script environment * \a scope, after having stripped the script_ prefix from its name. * For instance, rather than creating a function outside your class with a signature * like the following, * \code * QScriptValue doFoo ( QScriptContext* context, QScriptEngine* engine ) * \endcode * and then having to construct an appropriate script function object in setup(), * as well as somehow track this particular LurchPackage object in your function's * data field, you could instead simply write a slot with this signature, * \code * QScriptValue script_doFoo ( QScriptContext* context, QScriptEngine* engine ) * \endcode * and then call setupSlotsAsFunctions() in your setup() routine. All those details * will be handled for you, and because the slot is in this object, you have access * to all its members when implementing the slot. */ void setupSlotsAsFunctions ( QScriptValue scope ); /** \brief Read the full contents of the named file as text, and return it * * Useful for reading in a file full of script code, so that it can be passed to * a script engine for interpreting. If any error occurs, an empty string is returned. */ static QString source ( QString filename ); /** \brief Creates a simple representation of this package as a document * * That representation includes title, author, language, and version, as well as flagging * the document with an attribute that indicates that it is the document representation of * a LurchPackage. * * For information about how to use this function, see asDocument() and how subclasses * reimplement that function using this one. */ Lob documentSkeleton ( QStringList dependencies = QStringList() ) const; /** \brief An HTML paragraph describing what setupSlotsAsFunctions() does for this package * * Many packages install JavaScript functions by implementing them in C++ as package slots, * and using a setupSlotsAsFunctions() call. So this function is a convenient way to * document that fact in a consistent way across packages. It appends to the given document * a list of such script slots installed this package. It does so with one call to addText(), * after having generated the HTML to pass to that call. * If there are no slots in this package that setupSlotsAsFunctions() would install as * script functions, this method does nothing. */ void describeSetupSlotsAsFunctions ( Lob document ) const; /** \brief An HTML paragraph and script block stating that this package uses loaded script code * * Many packages load script code from a Qt resource, so this function is a convenient way to * document that fact in a consistent way across packages. It appends to the given document * a statement that the code in the given \a filename is executed by this package. It * includes the code in the document for the user to read, but does not mark it with any * script type, so that it will not impact any LurchEnvironment into which this package is * loaded. It does so with calls to addText() and addScript(). */ static void describeLoadedScriptCode ( Lob document, QString filename ); /** \brief Adds to a package representation document a link to its help file * * For information about how to use this function, see asDocument() and how subclasses * reimplement that function using this one. */ static void addHelpAttribute ( Lob document, QString helpURN ); /** \brief Adds to a package representation document a segment of text * * No HTML tags are placed in the text, so for example the caller may wish to surround * \a text in paragraph tags, if that is the intent. * * For information about how to use this function, see asDocument() and how subclasses * reimplement that function using this one. * * It can be convenient to use this function with describeSetupSlotsAsFunctions(). */ static void addText ( Lob document, QString text ); /** \brief Adds to a package representation document a block of JavaScript code * * This is simply a call to addText(), passing along a syntax-highlighted version of the * given code. It does not include it as a script Lob, but rather just as an HTML * representation of the code. * * For more information about how to use this function, see asDocument() and how subclasses * reimplement that function using this one. */ static void addScript ( Lob document, QString code ); /** \brief Caches a string of script code under a given name, for later retrieval * * Later you can retrieve it with program(). * This works well when used in tandem with source(). * * This is global data storage, and thus names must be unique within a subclass of * LurchPackage. That is, each object does not get its own cache, but rather each * subclass does. So only store data that is determined by the class, and not a function * of the individual object of that class. This improves efficiency. */ void cacheProgram ( QString name, QString code ); /** \brief Retrieves a cached string of script code, given the name under which it was stored * * See cacheProgram(). */ const QScriptProgram& program ( QString name ) const; /** The environment into which this package was last initialized */ QPointer environment; signals: /** \brief Emitted when this package wishes to communicate a message to the UI, if any * * The environment in which this package was loaded will pass this signal on. If there is * a user interface (e.g., not in simple_script) then it may listen to and react to this * signal. */ void packageMessageToInterface ( QString message ); private: #ifdef LURCH_UNIT_TEST friend class LURCH_UNIT_TEST; #endif /** Where the LurchPackage() constructor stores the this pointer, so that all * created instances of the class can be tracked using methods like lookupPackage(). */ static QList allInstances; /** Find at which index into allInstances this object lies (inverse of lookupPackage()) */ int index (); /** Look up a package object given its index (inverse of index()) */ static LurchPackage* lookupPackage ( int index ); /** A function outside of the LurchPackage class which is used by * LurchPackage::setupSlotsAsFunctions() to dispatch function calls to slots within a * particular LurchPackage object. * * Declaring it friend allows it to call lookupPackage() in this class. */ friend QScriptValue dispatcher ( QScriptContext* context, QScriptEngine* engine ); /** Provides access to a static member that is guaranteed to be initialized before being * needed. If this were simply declared as a static member variable, it might not be * initialized before calls to registerSubclass() during static initialization time * (at program startup). This causes bus errors. So instead we create this function * that returns the object in question, and which guarantees its existence. */ static QMap& urn2id (); /** Provides a way to store code loaded from (real or virtual) files. * * \see cacheProgram(), program() */ static QCache codeCache; }; #define LURCH_PACKAGE \ private: \ static int typeId; \ static QString urn; \ public: \ int metaTypeID () const; \ QString classURN () const; #define REGISTER_LURCH_PACKAGE(_class,_urn) \ QString _class::urn = _urn; \ int _class::typeId = LurchPackage::registerSubclass(_class::urn,qRegisterMetaType<_class>()); \ QString _class::classURN () const { return urn; } \ int _class::metaTypeID () const { return typeId; } #endif // LURCHPACKAGE