Merge pull request #25198 from pieterhijma/doc-expressions

Doc: Improve the documentation of expressions
This commit is contained in:
Chris Hennes
2026-02-03 21:06:13 +01:00
committed by GitHub
9 changed files with 722 additions and 331 deletions

View File

@@ -1013,7 +1013,7 @@ ExpressionPtr Expression::importSubNames(const std::map<std::string,std::string>
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<Expression*>::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<Expression*>::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<std::string> labels; /**< Label string primitive */
static std::map<std::string, FunctionExpression::Function> 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<std::string> labels;
/// Registered functions during parsing.
static std::map<std::string, FunctionExpression::Function> 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

View File

@@ -49,41 +49,216 @@ class Document;
using ExpressionPtr = std::unique_ptr<Expression>;
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<App::DocumentObject*, std::map<std::string, std::vector<ObjectIdentifier> > >;
/// Map of depending objects to a map of depending property name to the full referencing object identifier
using ExpressionDeps = std::map<App::DocumentObject*, std::map<std::string, std::vector<ObjectIdentifier>>>;
// 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<App::ObjectIdentifier, bool> &);
bool adjustLinks(Expression &e, const std::set<App::DocumentObject*> &inList);
bool relabeledDocument(Expression &e, const std::string &oldName, const std::string &newName);
bool renameObjectIdentifier(Expression &e,
const std::map<ObjectIdentifier,ObjectIdentifier> &, const ObjectIdentifier &);
void collectReplacement(Expression &e, std::map<ObjectIdentifier,ObjectIdentifier> &,
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<App::ObjectIdentifier, bool>& 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<App::DocumentObject*>& 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<ObjectIdentifier, ObjectIdentifier>& 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<ObjectIdentifier, ObjectIdentifier>& 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 P> 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<App::PropertyLinkBase*>(&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<P>::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<App::ObjectIdentifier,bool> &) 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<App::ObjectIdentifier, bool>& 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<App::ObjectIdentifier,bool> 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<App::DocumentObject*,bool> getDepObjects(std::vector<std::string> *labels=nullptr) const;
void getDepObjects(std::map<App::DocumentObject*,bool> &, std::vector<std::string> *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<App::DocumentObject*, bool> getDepObjects(std::vector<std::string>* 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<App::DocumentObject*,bool>& deps, std::vector<std::string> *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<std::string,std::string> &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<App::DocumentObject*> &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<Component*>;
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:

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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> 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<DocumentObject*>(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<Expression> 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<int, ObjectIdentifier>& 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<App::ObjectIdentifier>
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<App::ObjectIdentifier>& 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<const Expression> 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<ObjectIdentifier, ObjectIdentifier>& 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<ObjectIdentifier, ObjectIdentifier>& paths)
{

View File

@@ -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<App::ObjectIdentifier, const App::Expression*> 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<App::ObjectIdentifier, App::ExpressionPtr>&& 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<PropertyExpressionEngine>
@@ -98,7 +145,7 @@ public:
std::shared_ptr<const App::Expression> 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<App::Expression> 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<App::ObjectIdentifier>& 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<App::ObjectIdentifier>& 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<const App::Expression> 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<App::ObjectIdentifier, App::ObjectIdentifier>& 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<App::ObjectIdentifier, App::ObjectIdentifier>& 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<const App::ObjectIdentifier, ExpressionInfo>;
#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<App::ObjectIdentifier> 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> expression,
boost::unordered_map<App::ObjectIdentifier, int>& nodes,
boost::unordered_map<int, App::ObjectIdentifier>& revNodes,
std::vector<Edge>& 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<int, App::ObjectIdentifier>& revNodes,
DiGraph& g,

View File

@@ -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<PropertyLinkBase*>& 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<App::DocumentObject*>& 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,

View File

@@ -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.
*/
/**