Doc: Improve the App::Expression documentation

This commit is contained in:
Pieter Hijma
2025-10-28 15:44:53 +01:00
committed by Chris Hennes
parent 063f03c764
commit f113e775a1
5 changed files with 417 additions and 80 deletions

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,7 +304,11 @@ class AppExport Expression : public Base::BaseClass {
TYPESYSTEM_HEADER_WITH_OVERRIDE();
public:
/**
* @brief Construct a new Expression object.
*
* @param[in] _owner The document object that owns this expression.
*/
explicit Expression(const App::DocumentObject * _owner);
~Expression() override;
@@ -137,13 +323,49 @@ public:
*/
virtual bool isTouched() const { return false; }
Expression * eval() const;
/**
* @brief Evaluate the expression.
*
* Evaluating an expression returns another expression that represents a
* value. Contrast this with the @ref simplify function that returns a
* possibly simpler version of the same expression.
*
* @return The evaluated expression.
*/
Expression* eval() const;
std::string toString(bool persistent=false, bool checkPriority=false, int indent=0) const;
/**
* @brief Convert the expression to a string.
*
* @param[in] persistent If true, the string representation is persistent
* and can be saved to a file.
* @param[in] checkPriority If true, check whether the expression requires
* parentheses based on operator priority.
* @param[in] indent The indentation level for pretty-printing.
*
* @return The string representation of the expression.
*/
std::string toString(bool persistent = false, bool checkPriority = false, int indent = 0) const;
/**
* @brief Write a string representation of the expression to a stream.
*
* @param[in,out] os The output stream to write to.
* @copydoc Expression::toString(bool, bool, int) const
*/
void toString(std::ostream &os, bool persistent=false, bool checkPriority=false, int indent=0) const;
static Expression * parse(const App::DocumentObject * owner, const std::string& buffer);
/**
* @brief Parse an expression from a string.
*
* @param[in] owner The document object that will own the parsed expression.
* @param[in] buffer The string to parse.
*
* @return The parsed expression.
*/
static Expression* parse(const App::DocumentObject * owner, const std::string& buffer);
/// Copy an expression.
Expression * copy() const;
/**
@@ -156,28 +378,104 @@ public:
*/
virtual int priority() const;
void getIdentifiers(std::map<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);
/**
@@ -191,30 +489,55 @@ public:
*/
virtual Expression * simplify() const = 0;
/// Visit the expression with a visitor.
void visit(ExpressionVisitor & v);
/// Exception class for expression errors.
class Exception : public Base::Exception {
public:
explicit Exception(const char *sMessage) : Base::Exception(sMessage) { }
};
/// Get the owner of the expression.
App::DocumentObject * getOwner() const { return owner; }
struct Component;
/// Add a component to the expression.
virtual void addComponent(Component* component);
using ComponentList = std::vector<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;
@@ -245,8 +568,11 @@ protected:
protected:
// clang-format off
App::DocumentObject * owner; /**< The document object used to access unqualified variables (i.e local scope) */
/// The document object used to access unqualified variables (i.e local scope).
App::DocumentObject * owner;
/// The list of components.
ComponentList components;
public:

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;

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

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