diff --git a/src/App/Expression.cpp b/src/App/Expression.cpp index 1afb5ec56e..a14c35305f 100644 --- a/src/App/Expression.cpp +++ b/src/App/Expression.cpp @@ -1013,7 +1013,7 @@ ExpressionPtr Expression::importSubNames(const std::map if(key.second.empty() || subNameMap.contains(key)) continue; std::string imported = PropertyLinkBase::tryImportSubName( - obj,key.second.c_str(),owner->getDocument(), nameMap); + obj, key.second.c_str(), owner->getDocument(), nameMap); if(!imported.empty()) subNameMap.emplace(std::move(key),std::move(imported)); } @@ -1213,14 +1213,6 @@ void UnitExpression::setQuantity(const Quantity &_quantity) } } -/** - * Set unit information. - * - * @param _unit A unit object - * @param _unitstr The unit expressed as a string - * @param _scaler Scale factor to convert unit into internal unit. - */ - void UnitExpression::setUnit(const Quantity &_quantity) { quantity = _quantity; @@ -1231,33 +1223,16 @@ void UnitExpression::setUnit(const Quantity &_quantity) } } -/** - * Simplify the expression. In this case, a NumberExpression is returned, - * as it cannot be simplified any more. - */ - Expression *UnitExpression::simplify() const { return new NumberExpression(owner, quantity); } -/** - * Return a string representation, in this case the unit string. - */ - -/** - * Return a string representation of the expression. - */ - void UnitExpression::_toString(std::ostream &ss, bool,int) const { ss << unitStr; } -/** - * Return a copy of the expression. - */ - Expression *UnitExpression::_copy() const { return new UnitExpression(owner, quantity, unitStr); @@ -1280,29 +1255,16 @@ NumberExpression::NumberExpression(const DocumentObject *_owner, const Quantity { } -/** - * Simplify the expression. For NumberExpressions, we return a copy(), as it cannot - * be simplified any more. - */ - Expression *NumberExpression::simplify() const { return copy(); } -/** - * Create and return a copy of the expression. - */ - Expression *NumberExpression::_copy() const { return new NumberExpression(owner, getQuantity()); } -/** - * Negate the stored value. - */ - void NumberExpression::negate() { setQuantity(-getQuantity()); @@ -1353,10 +1315,6 @@ OperatorExpression::~OperatorExpression() delete right; } -/** - * Determine whether the expression is touched or not, i.e relies on properties that are touched. - */ - bool OperatorExpression::isTouched() const { return left->isTouched() || right->isTouched(); @@ -1478,14 +1436,6 @@ Py::Object OperatorExpression::_getPyValue() const { return calc(this,op,left,right,false); } -/** - * Simplify the expression. For OperatorExpressions, we return a NumberExpression if - * both the left and right side can be simplified to NumberExpressions. In this case - * we can calculate the final value of the expression. - * - * @returns Simplified expression. - */ - Expression *OperatorExpression::simplify() const { Expression * v1 = left->simplify(); @@ -1501,12 +1451,6 @@ Expression *OperatorExpression::simplify() const return new OperatorExpression(owner, v1, op, v2); } -/** - * Create a string representation of the expression. - * - * @returns A string representing the expression. - */ - void OperatorExpression::_toString(std::ostream &s, bool persistent,int) const { bool needsParens; @@ -1608,22 +1552,11 @@ void OperatorExpression::_toString(std::ostream &s, bool persistent,int) const right->toString(s,persistent); } -/** - * A deep copy of the expression. - */ - Expression *OperatorExpression::_copy() const { return new OperatorExpression(owner, left->copy(), op, right->copy()); } -/** - * Return the operators priority. This is used to add parentheses where - * needed when creating a string representation of the expression. - * - * @returns The operator's priority. - */ - int OperatorExpression::priority() const { switch (op) { @@ -1850,13 +1783,6 @@ FunctionExpression::~FunctionExpression() } } -/** - * Determine whether the expressions is considered touched, i.e one or both of its arguments - * are touched. - * - * @return True if touched, false if not. - */ - bool FunctionExpression::isTouched() const { std::vector::const_iterator i = args.begin(); @@ -2658,12 +2584,6 @@ Py::Object FunctionExpression::_getPyValue() const { return evaluate(this,f,args); } -/** - * Try to simplify the expression, i.e calculate all constant expressions. - * - * @returns A simplified expression. - */ - Expression *FunctionExpression::simplify() const { size_t numerics = 0; @@ -2692,12 +2612,6 @@ Expression *FunctionExpression::simplify() const std::move(simplifiedArgs)); } -/** - * Create a string representation of the expression. - * - * @returns A string representing the expression. - */ - void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const { switch (f) { @@ -2848,12 +2762,6 @@ void FunctionExpression::_toString(std::ostream &ss, bool persistent,int) const ss << ')'; } -/** - * Create a copy of the expression. - * - * @returns A deep copy of the expression. - */ - Expression *FunctionExpression::_copy() const { std::vector::const_iterator i = args.begin(); @@ -2891,30 +2799,11 @@ VariableExpression::VariableExpression(const DocumentObject *_owner, const Objec VariableExpression::~VariableExpression() = default; -/** - * Determine if the expression is touched or not, i.e whether the Property object it - * refers to is touched(). - * - * @returns True if the Property object is touched, false if not. - */ - bool VariableExpression::isTouched() const { return var.isTouched(); } -/** - * Find the property this expression referse to. - * - * Unqualified names (i.e the name only without any dots) are resolved in the owning DocumentObjects. - * Qualified names are looked up in the owning Document. It is first looked up by its internal name. - * If not found, the DocumentObjects' labels searched. - * - * If something fails, an exception is thrown. - * - * @returns The Property object if it is derived from either PropertyInteger, PropertyFloat, or PropertyString. - */ - const Property * VariableExpression::getProperty() const { const Property * prop = var.getProperty(); @@ -2986,22 +2875,11 @@ void VariableExpression::_toString(std::ostream &ss, bool persistent,int) const ss << var.toString(); } -/** - * Simplify the expression. Simplification of VariableExpression objects is - * not possible (if it is instantiated it would be an evaluation instead). - * - * @returns A copy of the expression. - */ - Expression *VariableExpression::simplify() const { return copy(); } -/** - * Return a copy of the expression. - */ - Expression *VariableExpression::_copy() const { return new VariableExpression(owner, var); @@ -3202,10 +3080,6 @@ StringExpression::~StringExpression() { } } -/** - * Simplify the expression. For strings, this is a simple copy of the object. - */ - Expression *StringExpression::simplify() const { return copy(); @@ -3216,10 +3090,6 @@ void StringExpression::_toString(std::ostream &ss, bool,int) const ss << quote(text); } -/** - * Return a copy of the expression. - */ - Expression *StringExpression::_copy() const { return new StringExpression(owner, text); @@ -3549,9 +3419,10 @@ bool isModuleImported(PyObject *module) { } /** - * Error function for parser. Throws a generic Base::Exception with the parser error. + * @brief Error function for parser. + * + * @throws Base::Exception A generic parser error. */ - void ExpressionParser_yyerror(const char *errorinfo) { (void)errorinfo; @@ -3587,12 +3458,24 @@ double num_change(char* yytext,char dez_delim,char grp_delim) return ret_val; } -static Expression * ScanResult = nullptr; /**< The resulting expression after a successful parsing */ -static const App::DocumentObject * DocumentObject = nullptr; /**< The DocumentObject that will own the expression */ -static bool unitExpression = false; /**< True if the parsed string is a unit only */ -static bool valueExpression = false; /**< True if the parsed string is a full expression */ -static std::stack labels; /**< Label string primitive */ -static std::map registered_functions; /**< Registered functions */ +/// The resulting expression after a successful parsing. +static Expression* ScanResult = nullptr; + +/// The DocumentObject that will own the expression. +static const App::DocumentObject* DocumentObject = nullptr; + +/// Whether the parsed string is a unit only. +static bool unitExpression = false; + +/// Whether the parsed string is a full expression. +static bool valueExpression = false; + +/// Label string primitive. +static std::stack labels; + +/// Registered functions during parsing. +static std::map registered_functions; + static int last_column; static int column; @@ -3889,3 +3772,4 @@ bool ExpressionParser::isTokenAUnit(const std::string & str) #if defined(__clang__) # pragma clang diagnostic pop #endif + diff --git a/src/App/Expression.h b/src/App/Expression.h index 1b416c4ca8..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,74 +304,240 @@ 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; + /** + * @brief Check if the expression is touched. + * + * An expression is touched if one of the properties it references is + * touched. + * + * @return true if the expression is touched, false otherwise. + */ 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; + /** + * @brief Get the operator priority. + * + * This is used to determine whether parentheses are needed when + * converting the expression to a string. + * + * @return The operator priority. + */ 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); + /** + * @brief Simplify the expression. + * + * In contrast to @ref eval, which evaluates the expression to a value, + * this function simplifies the expression by computing all constant + * expressions. + * + * @return The simplified expression. + */ 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; @@ -220,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 1cabea5ccb..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; @@ -143,6 +146,9 @@ public: Expression* simplify() const override; + /** + * @brief Negate the stored value. + */ void negate(); bool isInteger(long* v = nullptr) const; @@ -473,6 +479,19 @@ public: void setPath(const ObjectIdentifier& path); + /** + * @brief Find the property this expression referse to. + * + * Unqualified names (i.e the name only without any dots) are resolved in + * the owning DocumentObjects. Qualified names are looked up in the owning + * Document, first, by its internal name, then if not found, by the + * DocumentObjects' labels. + * + * @return The Property object if it is derived from either + * PropertyInteger, PropertyFloat, or PropertyString. + * + * @trhows Expression::Exception If the property cannot be resolved. + */ const App::Property* getProperty() const; void addComponent(Component* component) override; 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/PropertyExpressionEngine.cpp b/src/App/PropertyExpressionEngine.cpp index c0fa969562..b670a451ab 100644 --- a/src/App/PropertyExpressionEngine.cpp +++ b/src/App/PropertyExpressionEngine.cpp @@ -99,28 +99,13 @@ struct PropertyExpressionEngine::Private TYPESYSTEM_SOURCE(App::PropertyExpressionEngine, App::PropertyExpressionContainer) -/** - * @brief Construct a new PropertyExpressionEngine object. - */ - PropertyExpressionEngine::PropertyExpressionEngine() : validator(0) {} -/** - * @brief Destroy the PropertyExpressionEngine object. - */ - PropertyExpressionEngine::~PropertyExpressionEngine() = default; -/** - * @brief Estimate memory size of this property. - * - * \fixme Should probably return something else than 0. - * - * @return Size of object. - */ - +// fixme Should probably return something else than 0. unsigned int PropertyExpressionEngine::getMemSize() const { return 0; @@ -360,15 +345,6 @@ void PropertyExpressionEngine::Restore(Base::XMLReader& reader) reader.readEndElement("ExpressionEngine"); } -/** - * @brief Update graph structure with given path and expression. - * @param path Path - * @param expression Expression to query for dependencies - * @param nodes Map with nodes of graph, including dependencies of 'expression' - * @param revNodes Reverse map of the nodes, containing only the given paths, without dependencies. - * @param edges Edges in graph - */ - void PropertyExpressionEngine::buildGraphStructures( const ObjectIdentifier& path, const std::shared_ptr expression, @@ -410,12 +386,6 @@ void PropertyExpressionEngine::buildGraphStructures( } } -/** - * @brief Create a canonical object identifier of the given object \a p. - * @param p ObjectIndentifier - * @return New ObjectIdentifier - */ - ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier& oid) const { DocumentObject* docObj = freecad_cast(getContainer()); @@ -446,11 +416,6 @@ ObjectIdentifier PropertyExpressionEngine::canonicalPath(const ObjectIdentifier& return oid.canonicalPath(); } -/** - * @brief Number of expressions managed by this object. - * @return Number of expressions. - */ - size_t PropertyExpressionEngine::numExpressions() const { return expressions.size(); @@ -510,12 +475,6 @@ void PropertyExpressionEngine::onContainerRestored() } } -/** - * @brief Get expression for \a path. - * @param path ObjectIndentifier to query for. - * @return Expression for \a path, or empty boost::any if not found. - */ - const boost::any PropertyExpressionEngine::getPathValue(const App::ObjectIdentifier& path) const { // Get a canonical path @@ -529,13 +488,6 @@ const boost::any PropertyExpressionEngine::getPathValue(const App::ObjectIdentif return boost::any(); } -/** - * @brief Set expression with optional comment for \a path. - * @param path Path to update - * @param expr New expression - * @param comment Optional comment. - */ - void PropertyExpressionEngine::setValue(const ObjectIdentifier& path, std::shared_ptr expr) { @@ -573,11 +525,9 @@ void PropertyExpressionEngine::setValue(const ObjectIdentifier& path, } } -/** - * @brief The cycle_detector struct is used by the boost graph routines to detect cycles in the - * graph. - */ +/* The cycle_detector struct is used by the boost graph routines to detect + * cycles in the graph. */ struct cycle_detector: public boost::dfs_visitor<> { cycle_detector(bool& has_cycle, int& src) @@ -597,14 +547,6 @@ protected: int& _src; }; -/** - * @brief Build a graph of all expressions in \a exprs. - * @param exprs Expressions to use in graph - * @param revNodes Map from int[nodeid] to ObjectIndentifer. - * @param g Graph to update. May contain additional nodes than in revNodes, because of outside - * dependencies. - */ - void PropertyExpressionEngine::buildGraph(const ExpressionMap& exprs, boost::unordered_map& revNodes, DiGraph& g, @@ -656,12 +598,6 @@ void PropertyExpressionEngine::buildGraph(const ExpressionMap& exprs, } } -/** - * The code below builds a graph for all expressions in the engine, and - * finds any circular dependencies. It also computes the internal evaluation - * order, in case properties depends on each other. - */ - std::vector PropertyExpressionEngine::computeEvaluationOrder(ExecuteOption option) { @@ -686,11 +622,6 @@ PropertyExpressionEngine::computeEvaluationOrder(ExecuteOption option) return evaluationOrder; } -/** - * @brief Compute and update values of all registered expressions. - * @return StdReturn on success. - */ - DocumentObjectExecReturn* App::PropertyExpressionEngine::execute(ExecuteOption option, bool* touched) { @@ -823,12 +754,6 @@ DocumentObjectExecReturn* App::PropertyExpressionEngine::execute(ExecuteOption o return DocumentObject::StdReturn; } -/** - * @brief Find paths to document object. - * @param obj Document object - * @param paths Object identifier - */ - void PropertyExpressionEngine::getPathsToDocumentObject( DocumentObject* obj, std::vector& paths) const @@ -854,11 +779,6 @@ void PropertyExpressionEngine::getPathsToDocumentObject( } } -/** - * @brief Determine whether any dependencies of any of the registered expressions have been touched. - * @return True if at least on dependency has been touched. - */ - bool PropertyExpressionEngine::depsAreTouched() const { for (auto& v : _Deps) { @@ -870,13 +790,6 @@ bool PropertyExpressionEngine::depsAreTouched() const return false; } -/** - * @brief Validate the given path and expression. - * @param path Object Identifier for expression. - * @param expr Expression tree. - * @return Empty string on success, error message on failure. - */ - std::string PropertyExpressionEngine::validateExpression(const ObjectIdentifier& path, std::shared_ptr expr) const @@ -928,11 +841,6 @@ PropertyExpressionEngine::validateExpression(const ObjectIdentifier& path, return {}; } -/** - * @brief Rename paths based on \a paths. - * @param paths Map with current and new object identifier. - */ - void PropertyExpressionEngine::renameExpressions( const std::map& paths) { @@ -965,11 +873,6 @@ void PropertyExpressionEngine::renameExpressions( hasSetValue(); } -/** - * @brief Rename object identifiers in the registered expressions. - * @param paths Map with current and new object identifiers. - */ - void PropertyExpressionEngine::renameObjectIdentifiers( const std::map& paths) { diff --git a/src/App/PropertyExpressionEngine.h b/src/App/PropertyExpressionEngine.h index 6322892347..321de53dad 100644 --- a/src/App/PropertyExpressionEngine.h +++ b/src/App/PropertyExpressionEngine.h @@ -61,11 +61,46 @@ public: PropertyExpressionContainer(); ~PropertyExpressionContainer() override; + /** + * @brief Get the expressions map. + * + * This function returns a mapping from object identifier to expression. + * The object identifier specifies the property in the document object that + * the expression is bound to. + * + * @return The map of ObjectIdentifier to Expression pointers. + */ virtual std::map getExpressions() const = 0; + + /** + * @brief Set the expressions map. + * + * This function sets the mapping from object identifier to expression. + * The object identifier specifies the property in the document object that + * the expression is bound to. + * + * @param[in] exprs The new map of ObjectIdentifier to Expression pointers. + */ virtual void setExpressions(std::map&& exprs) = 0; protected: + /** + * @brief Handle document relabeling. + * + * Update the expressions in response to a document being relabeled. + * + * @param[in] doc The document that was relabeled. + */ virtual void onRelabeledDocument(const App::Document& doc) = 0; + + /** + * @brief Handle dynamic property renaming. + * + * Update the expressions in response to a dynamic property being renamed. + * + * @param[in] prop The property that was renamed. + * @param[in] oldName The old name of the property. + */ virtual void onRenameDynamicProperty(const App::Property& prop, const char* oldName) = 0; private: @@ -73,6 +108,18 @@ private: static void slotRenameDynamicProperty(const App::Property& prop, const char* oldName); }; + +/** + * @brief The class that manages expressions that target a property in a + * document object. + * @ingroup ExpressionFramework + * + * This class manages a set of expressions that are bound to properties in + * document objects. It provides functionality to evaluate the expressions, + * handle dependencies between expressions, and update the properties they + * are bound to. For a high-level overview of the %Expression framework see + * topic @ref ExpressionFramework "Expression Framework". + */ class AppExport PropertyExpressionEngine : public App::PropertyExpressionContainer, private App::AtomicPropertyChangeInterface @@ -98,7 +145,7 @@ public: std::shared_ptr expr)>; /** - * @brief The ExpressionInfo struct encapsulates an expression. + * @brief This struct encapsulates an expression. */ struct ExpressionInfo { @@ -127,8 +174,8 @@ public: void onRelabeledDocument(const App::Document& doc) override; void onRenameDynamicProperty(const App::Property& prop, const char* oldName) override; - void setValue() - {} // Dummy + /// Dummy setValue to satisfy a macro. + void setValue() {} Property* Copy() const override; @@ -138,8 +185,30 @@ public: void Restore(Base::XMLReader& reader) override; + /** + * @brief Set a given expression to @a path. + * + * Note that the "value" in this context is an expression. This means that + * this function does not evaluate the expression and updates the property + * that @a path points to. It merely registers the expression to be used + * when evaluating the property later. + * + * @param[in] path The path that the expression is targeting. + * @param[in] expr The new expression. + */ void setValue(const App::ObjectIdentifier& path, std::shared_ptr expr); + /** + * @brief Get the expression for @a path. + * + * Note that the "value" in this context is an expression. This means that + * this function does not return the evaluated value of the property that + * @a path points to. It merely returns the registered expression. + * + * @param[in] path ObjectIndentifier to query for. + * + * @return The expression for @a path, or empty boost::any if not found. + */ const boost::any getPathValue(const App::ObjectIdentifier& path) const override; /// Execute options @@ -154,32 +223,86 @@ public: /// Execute on document restore ExecuteOnRestore, }; - /** Evaluate the expressions + + /** + * @brief Evaluate the expressions. * - * @param option: execution option, see ExecuteOption. + * Evaluate the expressions and update the properties they are bound to. + * + * @param[in] option: execution option, see ExecuteOption. + * @param[out] touched: if not null, set to true if any property was + * changed. + * + * @return On success a pointer to DocumentObject::StdReturn is returned. On failure, it + * returns a pointer to a newly created App::DocumentObjectExecReturn that contains the error + * message. */ DocumentObjectExecReturn* execute(ExecuteOption option = ExecuteAll, bool* touched = nullptr); - void getPathsToDocumentObject(DocumentObject*, std::vector& paths) const; + /** + * @brief Find the paths to a given document object. + * + * @param[in] obj The document object to find paths to. + * @param[out] paths Object identifiers that point to @a obj. + */ + void getPathsToDocumentObject(DocumentObject* obj, + std::vector& paths) const; + /** + * @brief Check if any dependencies are touched. + * + * Determine whether any dependencies of any of the registered expressions + * have been touched. + * + * @return True if at least on dependency has been touched. + */ bool depsAreTouched() const; - /* Expression validator */ + /** + * @brief Set an extra validator function. + * + * @param[in] f The validator function. + */ void setValidator(ValidatorFunc f) { validator = f; } + /** + * @brief Validate the expression expression for a given path. + * + * @param[in] path The object identifier that the expression is targeting. + * @param expr The expression to validate. + * + * @return An empty string on success, an error message on failure. + */ std::string validateExpression(const App::ObjectIdentifier& path, std::shared_ptr expr) const; + /** + * @brief Rename object identifiers in the registered expressions. + * + * @param[in] paths A map with the current and new object identifiers. + */ void renameExpressions(const std::map& paths); + /** + * @brief Rename object identifiers in the registered expressions. + * + * @param[in] paths A map with the current and new object identifiers. + */ void renameObjectIdentifiers(const std::map& paths); - App::ObjectIdentifier canonicalPath(const App::ObjectIdentifier& p) const override; + /** + * @brief Create a canonical object identifier of the given object \a p. + * + * @param oid The object identifier from which we want a canonical path. + * @return The canonical object identifier. + */ + App::ObjectIdentifier canonicalPath(const App::ObjectIdentifier& oid) const override; + /// Get the number of expressions managed by this object. size_t numExpressions() const; /// signal called when an expression was changed @@ -210,14 +333,42 @@ private: using ExpressionMap = std::map; #endif + /** + * @brief Compute the evaluation order of the expressions. + * + * This method builds a graph for all expressions in the engine, and finds + * any circular dependencies. It also computes the internal evaluation + * order, in case properties depend on each other. + * + * @param[in] option Execution option, see ExecuteOption. + * + * @return A vector with the evaluation order of the properties and their + * dependencies in terms of object identifiers. + * + * @throws Base::RuntimeError if a circular dependency is detected. + */ std::vector computeEvaluationOrder(ExecuteOption option); + /** + * @brief Update graph structure with given path and expression. + * @param path Path + * @param expression Expression to query for dependencies + * @param nodes Map with nodes of graph, including dependencies of 'expression' + * @param revNodes Reverse map of the nodes, containing only the given paths, without dependencies. + * @param edges Edges in graph + */ void buildGraphStructures(const App::ObjectIdentifier& path, const std::shared_ptr expression, boost::unordered_map& nodes, boost::unordered_map& revNodes, std::vector& edges) const; - + /** + * @brief Build a graph of all expressions in \a exprs. + * @param exprs Expressions to use in graph + * @param revNodes Map from int[nodeid] to ObjectIndentifer. + * @param g Graph to update. May contain additional nodes than in revNodes, because of outside + * dependencies. + */ void buildGraph(const ExpressionMap& exprs, boost::unordered_map& revNodes, DiGraph& g, 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, diff --git a/src/App/core-app.dox b/src/App/core-app.dox index 672fbf243b..695b7658b3 100644 --- a/src/App/core-app.dox +++ b/src/App/core-app.dox @@ -227,7 +227,11 @@ * recomputed. Since properties may depend on other document objects, for * example because of the @ref App::DocumentObject::ExpressionEngine * "ExpressionEngine" property, these document objects have to be computed - * first. Managing the order of recomputation is managed by the @ref + * first. For more information on the role of expressions in recomputing + * document objects, see topic @ref SecExpressionsDocumentObjectRecompute + * "Expressions Framework". + * + * Managing the order of recomputation is managed by the @ref * App::Document "Document" which calls the @ref * App::DocumentObject::recompute() "recompute()" on the document object. To * signal that a document object needs to be recomputed, the document object @@ -251,7 +255,7 @@ * Vice versa, if a property of an object is changed, the InList indicates * which other objects depend on it and need to be recomputed. * - * So, as mentioned above, links define dependencies between document objects + * So, as mentioned above, links define dependencies between document objects. * These dependencies are recorded by means of setting the value of a @ref * App::PropertyLink "PropertyLink" (and similar properties) in a document * object `Obj` to a document object `Value`. Within @ref @@ -466,7 +470,78 @@ /** * @defgroup ExpressionFramework Expressions framework * @ingroup APP - * @brief The expression system allows users to write expressions and formulas that produce values + * @brief A system that allows users to write expressions and formulas that produce values. + * + * One of the differences between @ref DocumentObjectGroup "DocumentObjects" + * and @ref DocumentGroup "Documents" as @ref PropertyFramework + * "PropertyContainers" is that document objects support expressions whereas + * documents do not. %Document objects have a special hidden property called + * @ref App::DocumentObject::ExpressionEngine "ExpressionEngine" of type @ref + * App::PropertyExpressionEngine "PropertyExpressionEngine" that contains a + * mapping from @ref App::ObjectIdentifier "ObjectIdentifier" to @ref + * App::Expression "Expression". An object identifier defines the property + * inside the document object that will hold the result of the expression. We + * can also say that the expression is bound to the property that the object + * identifier identifies. + * + * Expressions can be as simple as `2 + 3`, but typically they reference other + * document objects and their properties, such as `Box001.Length` or even + * document objects in other documents such as `myDocument#Box001.Length`. + * + * Since expressions can reference other document objects, they effectively + * link to other document objects, potentially in other documents, and as such, + * a @ref App::PropertyExpressionEngine "PropertyExpressionEngine" inherits + * from the property external link container @ref App::PropertyXLinkContainer + * "PropertyXLinkContainer". + * + * @section SecExpressionsDocumentObjectRecompute The role of expressions in recomputing Documents + * + * For a high level overview of how document objects recompute, see topics @ref + * DependencyGraph "Document" and @ref SecDocumentObjectRecompute "Document + * Objects". On a recompute of a document object, the expressions in the + * expression engine are first evaluated by calling the @ref + * App::PropertyExpressionEngine::execute "PropertyExpressionEngine::execute()" + * function. This function obtains the values of the properties it references + * and evaluates the expression to a value that is stored in the property to + * which the expression is bound to. + * + * Now that the properties of the document objects have been updated according + * to the expressions, the document object can call @ref + * App::DocumentObject::execute "DocumentObject::execute()" to update its + * internal properties. + * + * After this execute of the document objects, the expression engine is + * executed again, but now only for the output properties ensuring that + * expressions that depend on properties of the document object itself that are + * set by @ref App::DocumentObject::execute "DocumentObject::execute()" are + * also updated. + * + * This process makes clear why it is important that there is a well-defined + * dependency graph and a topological order of document objects in terms of + * their dependencies as was discussed in topic @ref DependencyGraph + * "Document": If a document object is executed and references a property of + * another document object, we need to make sure that that document object has + * fully recomputed as we would compute a stale value of the property + * otherwise. Similarly, objects that refer to properties of a recently + * recomputed document object, need to be certain that the properties of this + * object have the latest value. As such, it is not possible to have a cyclic + * dependency between document objects as they would continuously update + * themselves. + * + * @section SecExpressionVisitors Expression Visitors + * + * Some actions require that expressions are updated. For example, when a document + * is relabeled, expressions that refer to the old name must be updated to + * refer to the new name. + * + * For this purpose, the visitor pattern is used. An expression visitor is derived + * from @ref App::ExpressionVisitor "ExpressionVisitor" and implements the + * virtual function @ref App::ExpressionVisitor::visit "visit()". This + * function is called for each node in the expression tree. + * + * There are two types of visitors: ones that gather information, for example a + * list of object identifiers in the expression, and ones that modify the + * expression, for example in case a document is relabeled. */ /**