#ifndef __Execution_h_ #define __Execution_h_ #include #include "DataFormat.h" #include "Parse.h" namespace Execution { // Given an alert or top list record, look up the corresponding alerts // daily data, and dump the contents of both. std::string debugDump(Record const *primary); // This is aimed at debugging. Usually you'd ask for a char * and you'd // have to check for errors. That's harder to use but much more efficient. // If record is NULL or doesn't contain a symbol, this returns "". std::string getSymbol(Record const *record); // You often want to do several related computations. Like tell me if this // record matches, if so give me the columns from this record. RecordInfo // contains a cache, that makes the second computation faster. The cache // is specific to the record, so we keep them together. // // We used to store a copy of the smart pointer to the currentRecord. That // was very helpful in some test code, but didn't do much for the real code. // The real code already had a copy of the smart pointer that would stay as // long as was required. // // The smart pointers are usually pretty fast. But we were having some // performance issues. It seems that multiple CPUs were all copying the same // smart pointers at the same time, and that causes bad performance problems. struct RecordInfo { std::vector< ValueBox > cache; Record const *currentRecord; void setRecord(Record::Ref const &record) { currentRecord = &*record; } RecordInfo() : currentRecord(NULL) { } RecordInfo(Record::Ref const &record) : currentRecord(&*record) { } }; class Executable { protected: Executable() { } // A list of all child nodes. // Note that we are creating a new list each time. Don't say // it = allChildrenDebug().begin() // Store the list in a temporary variable if you intend to iterate over it. virtual std::vector< Executable * > allChildrenDebug(); // Something like "+" or "sin". The name of the function. // If we have any arguments that are not listed in allChildrenDebug(), // we can list these here, like "CachedValue 3". virtual std::string thisItemNameDebug(); // Returns something like "2" or " 2 3" or " (sin 3) (ln 4)" or "". // nameDebug() would mix this with the result from thisItemNameDebug() to // make something like "(sin 2)". virtual std::string childrenNamesDebug(); public: virtual ValueBox execute(RecordInfo &recordInfo) const =0; virtual ~Executable() { } static Executable *create(Parse::Tree const &source); // used is a map from cache locations (i.e. 0 is the first entry in the // cache vector) to the number of nodes which use the cache. Nothing // should point to 0. If a cache entry has not been used, just don't // include that as a key. This is computed recursively. virtual void cacheInfoDebug(std::map< int, int > &used); // Something like "(+ 1 (sin 3))". LISP like syntax for simplicity. std::string nameDebug(); // If you create a TransactionDebug object that will group together all // the following calls to nameDebug(). This will sometimes make a printout // easier to read. // // In particular this is aimed at the cache. If multiple items all share // a chache entry, by default they will all display the rule for filling // the cache. But usually these are all the same. (Otherwise that would // be a bug!) So instead we only display the full info the first time we // see each cache entry. // // The transaction will start when you create the TransactionDebug object. // The transaction will end when you destroy the TransactionDebug object. // // Only one transaction is active at a time. If you create additional // TransactionDebug objects while a transaction is already active, the // additional ones are ignored. We assume that you will always destroy // the TransactionDebug in the reverse order that you created them. If you // create these on the stack, you get this for free. If you use new and // delete, you might cause a problem. // // Transactions are specific to a thread. Creating a transaction in one // thread has no effect on any other thread. Make sure you always create // and delete each TransactionDebug object in the same thread. class TransactionDebug { private: bool _first; public: TransactionDebug(); ~TransactionDebug(); }; }; // There are different ways to organize the code. Some items, like the // plus function and the and function, I wanted to keep pure. They know // nothing about implementation. Semantics.C and Parse.C should not have // to know anything about Execution.h. // // In other places, I have a specific idea, and I want to write the code // for the parse tree and the executable together. Symbol lists are a // perfect example of this. The strategy can make a symbol list object. // that object knows a lot about how to specify and implement symbol lists. // (That includes background threads and database connections!) Semantics.C // never has to know anything about symbol lists. Exectuion.C only needs // to know the ICompile interface. class ICompile { public: virtual Executable *compile() const =0; virtual ~ICompile() { } }; // This can be useful when creating a new executable. This class assumes // that each object contains exactly one child which is also an executable. // This class automatically deletes the child when this object is deleted. // Subclasses can add other data of other types. class Executable1 : public Executable, NoCopy, NoAssign { protected: Executable *const _arg1; Executable1(Executable *arg1) : _arg1(arg1) { } virtual std::vector< Executable * > allChildrenDebug(); public: ~Executable1() { delete _arg1; } }; }; #endif