diff --git a/src/App/Expression.h b/src/App/Expression.h index ec9268596a..75faab5875 100644 --- a/src/App/Expression.h +++ b/src/App/Expression.h @@ -49,41 +49,216 @@ class Document; using ExpressionPtr = std::unique_ptr; -AppExport bool isAnyEqual(const App::any &v1, const App::any &v2); + +/** + * @brief Check whether two values are equal. + * + * @param[in] v1 The first value. + * @param[in] v2 The second value. + * @return true if the values are equal, false otherwise. + */ +AppExport bool isAnyEqual(const App::any& v1, const App::any& v2); + +/** + * @brief Convert an App::any value to a Base::Quantity. + * + * @param[in] value The value to convert. + * @param[in] errmsg Optional error message to use in case of failure. + * + * @return The converted Base::Quantity. + * @throw Base::TypeError if the value cannot be converted to a Base::Quantity. + */ AppExport Base::Quantity anyToQuantity(const App::any &value, const char *errmsg = nullptr); // clang-format off -// Map of depending objects to a map of depending property name to the full referencing object identifier -using ExpressionDeps = std::map > >; +/// Map of depending objects to a map of depending property name to the full referencing object identifier +using ExpressionDeps = std::map>>; // clang-format on +/** + * @brief %Base class for expression visitors. + * @ingroup ExpressionFramework + * + * This is a class that provides functionality to define visitors for + * expressions. For a high-level overview of the %Expression framework see + * topic @ref ExpressionFramework "Expression Framework". + */ class AppExport ExpressionVisitor { public: virtual ~ExpressionVisitor() = default; - virtual void visit(Expression &e) = 0; + + /** + * @brief Visit an expression. + * + * This is the method that is called when visiting an expression. + * + * @param[in,out] e The expression to visit. + */ + virtual void visit(Expression& e) = 0; + + /// Called before starting modifications virtual void aboutToChange() {} + + /** + * @brief Called after a modification has been made. + * + * @return The number of changes made. + */ virtual int changed() const { return 0;} + + /// Reset the change counter. virtual void reset() {} + + /// Get the property link if applicable. virtual App::PropertyLinkBase* getPropertyLink() {return nullptr;} protected: - void getIdentifiers(Expression &e, std::map &); - bool adjustLinks(Expression &e, const std::set &inList); - bool relabeledDocument(Expression &e, const std::string &oldName, const std::string &newName); - bool renameObjectIdentifier(Expression &e, - const std::map &, const ObjectIdentifier &); - void collectReplacement(Expression &e, std::map &, - const App::DocumentObject *parent, App::DocumentObject *oldObj, App::DocumentObject *newObj) const; - bool updateElementReference(Expression &e, App::DocumentObject *feature,bool reverse); - void importSubNames(Expression &e, const ObjectIdentifier::SubNameMap &subNameMap); - void updateLabelReference(Expression &e, App::DocumentObject *obj, - const std::string &ref, const char *newLabel); - void moveCells(Expression &e, const CellAddress &address, int rowCount, int colCount); + /** + * @brief Get the identifiers used in an expression. + * + * @param[in] e The expression. + * @param[in, out] ids The map to fill with identifiers. + */ + void getIdentifiers(Expression& e, std::map& ids); + + /** + * @brief Adjust the links in an expression. + * + * @param[in,out] e The expression. + * @param[in] inList The set of document objects to adjust links for. + * @return true if any links were adjusted, false otherwise. + */ + bool adjustLinks(Expression& e, const std::set& inList); + + /** + * @brief Update label references in an expression. + * + * @param[in,out] e The expression. + * @param[in] oldName The old document name. + * @param[in] newName The new document name. + * @return true if any label references were updated, false otherwise. + */ + bool relabeledDocument(Expression& e, const std::string& oldName, const std::string& newName); + + /** + * @brief Rename object identifiers in an expression. + * + * Object identifiers are renamed according to the provided map of old to + * new object identifiers. The new identifiers are made relative to @p + * path. + * + * @param[in,out] e The expression. + * @param[in] paths The map of old to new object identifiers. + * @param[in] path The object identifier path context. + * + * @return True if any object identifiers were renamed, false otherwise. + */ + bool renameObjectIdentifier(Expression& e, + const std::map& paths, + const ObjectIdentifier& path); + + /** + * @brief Collect replacements in an expression. + * + * This function runs before the @ref renameObjectIdentifier function and + * collects all occurrences of a specific old document object + * and maps them to a new document object in the provided map. + * + * @param[in,out] e The expression. + * @param[in,out] paths The map to fill with old to new object identifier replacements. + * @param[in] parent The parent document object. + * @param[in] oldObj The old document object to be replaced. + * @param[in] newObj The new document object to replace with. + */ + void collectReplacement(Expression& e, + std::map& paths, + const App::DocumentObject* parent, + App::DocumentObject* oldObj, + App::DocumentObject* newObj) const; + + /** + * @brief Update element references in an expression. + * + * @param[in,out] e The expression. + * @param[in] feature The document object feature for which the update + * element references should be updated. + * @param[in] reverse If true, use the old style, i.e. non-mapped element + * reference to query for the new style, i.e. mapped element reference when + * update. If false, then the other way around. + * + * @return true if any element references were updated, false otherwise. + */ + bool updateElementReference(Expression& e, App::DocumentObject* feature, bool reverse); + + /** + * @brief Rewrite sub-names in an expression after importing external + * objects. + * + * @param[in,out] e The expression. + * @param[in] subNameMap The map of sub-name replacements. + */ + void importSubNames(Expression& e, const ObjectIdentifier::SubNameMap& subNameMap); + + /** + * @brief Update label references in an expression. + * + * @param[in,out] e The expression. + * @param[in] obj The document object whose label has changed. + * @param[in] ref The old label reference. + * @param[in] newLabel The new label. + */ + void updateLabelReference(Expression& e, + App::DocumentObject* obj, + const std::string& ref, + const char* newLabel); + + /** + * @brief Move cell references in an expression. + * + * This function visits the expression and updates all cell references to + * @p address. These cell references are then updated by moving them by @p + * rowCount rows and @p colCount columns. + * + * @param[in,out] e The expression. + * @param[in] address The cell address that needs to be updated. + * @param[in] rowCount The number of rows to move. + * @param[in] colCount The number of columns to move. + */ + void moveCells(Expression& e, const CellAddress& address, int rowCount, int colCount); + + /** + * @brief Offset cell references in an expression. + * + * This function visits the expression and in a range expression, offsets + * all cell relative references by @p rowOffset rows and @p colOffset + * columns. + * + * @param[in,out] e The expression. + * @param[in] rowOffset The number of rows to offset. + * @param[in] colOffset The number of columns to offset. + */ void offsetCells(Expression &e, int rowOffset, int colOffset); }; +/** + * @brief Class for expression visitors that can modify expressions. + * @ingroup ExpressionFramework + * + * With respect to the base class @ref ExpressionVisitor, this class adds + * functionality to modify the visited @ref Expression objects and signal on + * changes. It also keeps track of the number of modifications made to the + * visited expressions. For a high-level overview of the %Expression framework + * see topic @ref ExpressionFramework "Expression Framework". + * + * @tparam P The property type being modified. + */ template class ExpressionModifier : public ExpressionVisitor { public: + /** + * @brief Construct a new ExpressionModifier object. + * + * @param[in,out] _prop The property being modified. + */ explicit ExpressionModifier(P & _prop) : prop(_prop) , propLink(freecad_cast(&prop)) @@ -104,9 +279,16 @@ public: App::PropertyLinkBase* getPropertyLink() override {return propLink;} protected: - P & prop; - App::PropertyLinkBase *propLink; + /// The property being modified. + P& prop; + + /// The property link if applicable. + App::PropertyLinkBase* propLink; + + /// The atomic property change signaller. typename AtomicPropertyChangeInterface

::AtomicPropertyChange signaller; + + /// The number of changes made. int _changed{0}; }; @@ -122,7 +304,11 @@ class AppExport Expression : public Base::BaseClass { TYPESYSTEM_HEADER_WITH_OVERRIDE(); public: - + /** + * @brief Construct a new Expression object. + * + * @param[in] _owner The document object that owns this expression. + */ explicit Expression(const App::DocumentObject * _owner); ~Expression() override; @@ -137,13 +323,49 @@ public: */ virtual bool isTouched() const { return false; } - Expression * eval() const; + /** + * @brief Evaluate the expression. + * + * Evaluating an expression returns another expression that represents a + * value. Contrast this with the @ref simplify function that returns a + * possibly simpler version of the same expression. + * + * @return The evaluated expression. + */ + Expression* eval() const; - std::string toString(bool persistent=false, bool checkPriority=false, int indent=0) const; + /** + * @brief Convert the expression to a string. + * + * @param[in] persistent If true, the string representation is persistent + * and can be saved to a file. + * @param[in] checkPriority If true, check whether the expression requires + * parentheses based on operator priority. + * @param[in] indent The indentation level for pretty-printing. + * + * @return The string representation of the expression. + */ + std::string toString(bool persistent = false, bool checkPriority = false, int indent = 0) const; + + /** + * @brief Write a string representation of the expression to a stream. + * + * @param[in,out] os The output stream to write to. + * @copydoc Expression::toString(bool, bool, int) const + */ void toString(std::ostream &os, bool persistent=false, bool checkPriority=false, int indent=0) const; - static Expression * parse(const App::DocumentObject * owner, const std::string& buffer); + /** + * @brief Parse an expression from a string. + * + * @param[in] owner The document object that will own the parsed expression. + * @param[in] buffer The string to parse. + * + * @return The parsed expression. + */ + static Expression* parse(const App::DocumentObject * owner, const std::string& buffer); + /// Copy an expression. Expression * copy() const; /** @@ -156,28 +378,104 @@ public: */ virtual int priority() const; - void getIdentifiers(std::map &) const; + /** + * @brief Get the identifiers in the expression. + * + * @param[in,out] deps The mapping from object identifiers to a boolean + * indicating whether the dependency is hidden (`href` references). + */ + void getIdentifiers(std::map& deps) const; + + /** + * @brief Get the identifiers in the expression. + * + * @return The mapping from object identifiers to a boolean indicating + * whether the dependency is hidden (`href` references). + */ std::map getIdentifiers() const; - enum DepOption { - DepNormal, - DepHidden, - DepAll, + /// Options for obtaining the dependencies of an expression. + enum DepOption + { + DepNormal, ///< Get normal dependencies. + DepHidden, ///< Get the hidden dependencies (`href` references). + DepAll, ///< Get both normal and hidden dependencies. }; - void getDeps(ExpressionDeps &deps, int option=DepNormal) const; + + /** + * @brief Get the dependencies of the expression. + * + * @param[in,out] deps The dependencies to fill. + * @param[in] option The dependency option. + */ + void getDeps(ExpressionDeps& deps, int option = DepNormal) const; + + /** + * @brief Get the dependencies of the expression. + * + * @param[in] option The dependency option. + * @return The dependencies. + */ ExpressionDeps getDeps(int option=DepNormal) const; - std::map getDepObjects(std::vector *labels=nullptr) const; - void getDepObjects(std::map &, std::vector *labels=nullptr) const; + /** + * @brief Get the dependent document objects of the expression. + * + * @param[in,out] labels Optional vector to fill with labels of dependent objects. + * + * @return Map of dependent document objects to a boolean indicating whether + * they are hidden (`href` references). + */ + std::map getDepObjects(std::vector* labels = nullptr) const; + /** + * @brief Get the dependent document objects of the expression. + * + * @param[in,out] deps The map to fill with dependent document objects to a + * boolean indicating whether they are hidden (`href` references). + * @param[in,out] labels Optional vector to fill with labels of dependent objects. + */ + void getDepObjects(std::map& deps, std::vector *labels=nullptr) const; + + /** + * @brief Import sub-names in the expression after importing external objects. + * + * @param[in] nameMap The map of sub-name replacements. + * + * @return The updated expression. + */ ExpressionPtr importSubNames(const std::map &nameMap) const; + /** + * @brief Update label references in the expression. + * + * @param[in] obj The document object whose label has changed. + * @param[in] ref The old label reference. + * @param[in] newLabel The new label. + * + * @return The updated expression. + */ ExpressionPtr updateLabelReference(App::DocumentObject *obj, const std::string &ref, const char *newLabel) const; + /** + * @brief Replace a document object in the expression. + * + * @param[in] parent The parent document object. + * @param[in] oldObj The old document object to be replaced. + * @param[in] newObj The new document object to replace with. + * + * @return The updated expression. + */ ExpressionPtr replaceObject(const App::DocumentObject *parent, App::DocumentObject *oldObj, App::DocumentObject *newObj) const; + /** + * @brief Adjust links according to a new inList. + * + * @param[in] inList The new inList to adjust links for. + * @return true if any links were adjusted, false otherwise. + */ bool adjustLinks(const std::set &inList); /** @@ -191,30 +489,55 @@ public: */ virtual Expression * simplify() const = 0; + /// Visit the expression with a visitor. void visit(ExpressionVisitor & v); + /// Exception class for expression errors. class Exception : public Base::Exception { public: explicit Exception(const char *sMessage) : Base::Exception(sMessage) { } }; + /// Get the owner of the expression. App::DocumentObject * getOwner() const { return owner; } + struct Component; + /// Add a component to the expression. virtual void addComponent(Component* component); using ComponentList = std::vector; - static Component *createComponent(const std::string &n); + /// Create a component from a name. + static Component* createComponent(const std::string& n); + + /** + * @brief Create an index or range component. + * + * @param[in] e1 Either the index in an array index or the start of a + * range. + * + * @param[in] e2 The end of a range, may be `nullptr`. + * @param[in] e3 The step of a range, may be `nullptr`. + */ static Component *createComponent(Expression *e1, Expression *e2=nullptr, Expression *e3=nullptr, bool isRange=false); + /// Check if the expression has a component. bool hasComponent() const {return !components.empty();} + /// Get the value as boost::any. boost::any getValueAsAny() const; + /// Get the value as a Python object. Py::Object getPyValue() const; + /** + * @brief Check if this expression is the same as another. + * + * @param[in] other The other expression to compare with. + * @param[in] checkComment If true, also check the comment. + */ bool isSame(const Expression &other, bool checkComment=true) const; friend class ExpressionVisitor; @@ -245,8 +568,11 @@ protected: protected: // clang-format off - App::DocumentObject * owner; /**< The document object used to access unqualified variables (i.e local scope) */ + /// The document object used to access unqualified variables (i.e local scope). + App::DocumentObject * owner; + + /// The list of components. ComponentList components; public: diff --git a/src/App/ExpressionParser.h b/src/App/ExpressionParser.h index c109862f33..584e16b76d 100644 --- a/src/App/ExpressionParser.h +++ b/src/App/ExpressionParser.h @@ -42,6 +42,9 @@ namespace App // included by everyone /////////////////////////////////////////////////////////////////////////////////// +/** + * @brief %Part of an expression that represents an index or range. + */ struct AppExport Expression::Component { ObjectIdentifier::Component comp; diff --git a/src/App/GeoFeature.cpp b/src/App/GeoFeature.cpp index 0a4e49a5db..d5a3657ecb 100644 --- a/src/App/GeoFeature.cpp +++ b/src/App/GeoFeature.cpp @@ -139,7 +139,7 @@ DocumentObject* GeoFeature::resolveElement(const DocumentObject* obj, bool append, ElementNameType type, const DocumentObject* filter, - const char** _element, + const char** element, GeoFeature** geoFeature) { elementName.newName.clear(); @@ -150,11 +150,11 @@ DocumentObject* GeoFeature::resolveElement(const DocumentObject* obj, if (!subname) { subname = ""; } - const char* element = Data::findElementName(subname); - if (_element) { - *_element = element; + const char* foundElement = Data::findElementName(subname); + if (element) { + *element = foundElement; } - auto sobj = obj->getSubObject(std::string(subname, element).c_str()); + auto sobj = obj->getSubObject(std::string(subname, foundElement).c_str()); if (!sobj) { return nullptr; } @@ -172,16 +172,16 @@ DocumentObject* GeoFeature::resolveElement(const DocumentObject* obj, if (filter && geo != filter) { return nullptr; } - if (!element || !element[0]) { + if (!foundElement || !foundElement[0]) { if (append) { elementName.oldName = Data::oldElementName(subname); } return sobj; } - if (!geo || hasHiddenMarker(element)) { + if (!geo || hasHiddenMarker(foundElement)) { if (!append) { - elementName.oldName = element; + elementName.oldName = foundElement; } else { elementName.oldName = Data::oldElementName(subname); @@ -189,11 +189,11 @@ DocumentObject* GeoFeature::resolveElement(const DocumentObject* obj, return sobj; } if (!append) { - elementName = geo->getElementName(element, type); + elementName = geo->getElementName(foundElement, type); } else { - const auto& names = geo->getElementName(element, type); - std::string prefix(subname, element - subname); + const auto& names = geo->getElementName(foundElement, type); + std::string prefix(subname, foundElement - subname); if (!names.newName.empty()) { elementName.newName = prefix + names.newName; } diff --git a/src/App/GeoFeature.h b/src/App/GeoFeature.h index 57eb2fa62f..93a94ecbd8 100644 --- a/src/App/GeoFeature.h +++ b/src/App/GeoFeature.h @@ -93,19 +93,23 @@ public: const char* name, ElementNameType type = Normal) const; - /** Resolve both the new and old style element name + /** + * @brief Resolve both the new and old style element name. * - * @param obj: top parent object - * @param subname: subname reference - * @param elementName: output of a pair(newElementName,oldElementName) - * @param append: Whether to include subname prefix into the returned - * element name - * @param type: the type of element name to request - * @param filter: If none zero, then only perform lookup when the element - * owner object is the same as this filter - * @param element: return the start of element name in subname + * @param[in] obj The top parent object + * @param[in] subname The subname reference. + * @param[out] elementName Output of a pair(newElementName,oldElementName) + * @param[in] append: Whether to include the subname prefix into the + * returned element name. + * @param[in] type The type of element name to request. + * @param[in] filter If not `nullptr`, then only perform lookup when the + * element owner object is the same as this filter. + * @param[out] element If not `nullptr`, provide start of element name in + * subname. + * @param[out] geoFeature If not `nullptr`, provide the GeoFeature that + * contains the element. * - * @return Return the owner object of the element + * @return Return the owner object of the element. */ static DocumentObject* resolveElement(const App::DocumentObject* obj, const char* subname, @@ -114,7 +118,7 @@ public: ElementNameType type = Normal, const DocumentObject* filter = nullptr, const char** element = nullptr, - GeoFeature** geo = nullptr); + GeoFeature** geoFeature = nullptr); /** * @brief Deprecated. Calculates the placement in the global reference coordinate system diff --git a/src/App/PropertyLinks.h b/src/App/PropertyLinks.h index a0af06530d..9ca0ea549d 100644 --- a/src/App/PropertyLinks.h +++ b/src/App/PropertyLinks.h @@ -393,28 +393,31 @@ public: /// Update all element references in all link properties of \a feature static void updateElementReferences(DocumentObject* feature, bool reverse = false); - + /// Update all element references in the _ElementRefMap static void updateAllElementReferences(bool reverse = false); /// Obtain link properties that contain element references to a given object static const std::unordered_set& getElementReferences(DocumentObject*); - /** Helper function for update individual element reference + /** + * @brief Update individual element references. * - * @param feature: if given, than only update element reference belonging - * to this feature. If not, then update geometry element - * references. - * @param sub: the subname reference to be updated. - * @param shadow: a pair of new and old style element references to be updated. - * @param reverse: if true, then use the old style, i.e. non-mapped element - * reference to query for the new style, i.e. mapped - * element reference when update. If false, then the other - * way around. - * @param notify: if true, call aboutToSetValue() before change + * This helper function is to be called by each link property in the event + * of geometry element reference change due to geometry model changes. * - * This helper function is to be called by each link property in the event of - * geometry element reference change due to geometry model changes. + * @param[in] feature If given, than only update element reference + * belonging to this feature. If not, then update all geometry element + * references. + * @param[in] obj The linked object. + * @param[in,out] sub The subname reference to be updated. + * @param[in,out] shadow A pair of new and old style element references to be updated. + * + * @param[in] reverse If true, then use the old style, i.e. non-mapped element + * reference to query for the new style, i.e. mapped element reference when + * update. If false, then the other way around. + * + * @param[in] notify: if true, call aboutToSetValue() before change */ bool _updateElementReference(App::DocumentObject* feature, App::DocumentObject* obj, @@ -446,20 +449,21 @@ public: const std::vector& objs, bool clear); - /** Helper function for link import operation + /** + * @brief Try import a subname reference. * - * @param obj: the linked object - * @param sub: subname reference - * @param doc: importing document - * @param nameMap: a name map from source object to its imported counter part + * This operation will go through all subname references and import all + * externally linked objects. After import, the link property must be + * changed to point to the newly imported objects, which should happen + * inside the API CopyOnImportExternal(). This function helps to rewrite + * subname reference to point to the correct sub objects that are imported. * - * @return Return a changed subname reference, or empty string if no change. + * @param obj The linked object. + * @param sub The subname reference. + * @param doc The importing document. + * @param nameMap A name map from source object to its imported counter part. * - * Link import operation will go through all link property and imports all - * externally linked object. After import, the link property must be - * changed to point to the newly imported objects, which should happen inside - * the API CopyOnImportExternal(). This function helps to rewrite subname - * reference to point to the correct sub objects that are imported. + * @return A changed subname reference, or an empty string if no change. */ static std::string tryImportSubName(const App::DocumentObject* obj, const char* sub,