HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
predicateExpression.h
Go to the documentation of this file.
1 //
2 // Copyright 2023 Pixar
3 //
4 // Licensed under the terms set forth in the LICENSE.txt file available at
5 // https://openusd.org/license.
6 //
7 #ifndef PXR_USD_SDF_PREDICATE_EXPRESSION_H
8 #define PXR_USD_SDF_PREDICATE_EXPRESSION_H
9 
10 #include "pxr/pxr.h"
11 #include "pxr/usd/sdf/api.h"
12 #include "pxr/base/tf/hash.h"
13 #include "pxr/base/vt/value.h"
14 
15 #include <iosfwd>
16 #include <string>
17 #include <tuple>
18 #include <utility>
19 #include <vector>
20 
22 
23 /// \class SdfPredicateExpression
24 ///
25 /// Represents a logical expression syntax tree consisting of predicate function
26 /// calls joined by the logical operators 'and', 'or', 'not', and an implied-and
27 /// operator that represents two subexpressions joined by only whitespace.
28 ///
29 /// An SdfPredicateExpression can be constructed with a string, which will parse
30 /// an expression. The syntax for an expression is as follows:
31 ///
32 /// The fundamental building blocks are calls to predicate functions. There are
33 /// three syntaxes for function calls.
34 ///
35 /// \li Bare call: just a function name: `isDefined`
36 /// \li Colon call: name, colon, positional arguments: `isa:mammal,bird`
37 /// \li Paren call: name and parenthesized positional and keyword args:
38 /// `isClose(1.23, tolerance=0.01)`
39 ///
40 /// Colon call arguments are all positional and must be separated by commas with
41 /// no spaces between arguments. In paren calls, positional arguments must
42 /// precede keyword arguments, and whitespace is allowed between arguments.
43 ///
44 /// The string parser supports argument values of the following types:
45 /// double-quoted "strings", unquoted strings, integers, floating-point numbers,
46 /// and boolean values 'true' and 'false'.
47 ///
48 /// The unary operator 'not' may appear preceding a function call, or a
49 /// subexpresion enclosed in parentheses. The binary operators 'and' and 'or'
50 /// may appear between subexpressions. If subexpressions appear adjacent to each
51 /// other (other than possible whitespace), this is considered an implied 'and'
52 /// operator.
53 ///
54 /// Operator precedence in order from highest to lowest is: 'not',
55 /// <implied-and>, 'and', 'or'.
56 ///
57 /// Here are some examples of valid predicate expression syntax:
58 ///
59 /// \li `foo` (call "foo" with no arguments)
60 /// \li `foo bar` (implicit 'and' of "foo" and "bar")
61 /// \li `foo not bar` (implicit 'and' of "foo" and "not bar")
62 /// \li `color:red (shiny or matte)`
63 /// \li `animal or mineral or vegetable`
64 /// \li `(mammal or bird) and (tame or small)`
65 /// \li `isClose(100, tolerance=3.0) or negative`
66 ///
68 {
69 public:
70 
71  /// \class FnArg
72  ///
73  /// Represents a function argument name and value. Positional arguments
74  /// have empty names.
75  struct FnArg {
76  static FnArg Positional(VtValue const &val) {
77  return { std::string(), val };
78  }
79  static FnArg Keyword(std::string const &name, VtValue const &val) {
80  return { name, val };
81  }
82  std::string argName;
84 
85  template <class HashState>
86  friend void TfHashAppend(HashState &h, FnArg const &arg) {
87  h.Append(arg.argName, arg.value);
88  }
89 
90  friend bool operator==(FnArg const &l, FnArg const &r) {
91  return std::tie(l.argName, l.value) == std::tie(r.argName, r.value);
92  }
93  friend bool operator!=(FnArg const &l, FnArg const &r) {
94  return !(l == r);
95  }
96 
97  friend void swap(FnArg &l, FnArg &r) {
98  swap(l.argName, r.argName);
99  swap(l.value, r.value);
100  }
101  };
102 
103  /// \class FnCall
104  ///
105  /// Represents a function call in an expression with calling style, function
106  /// name, and arguments.
107  struct FnCall {
108  enum Kind {
109  BareCall, ///< no-arg call like 'active'
110  ColonCall, ///< colon-separated pos args, like 'isa:Imageable'
111  ParenCall ///< paren/comma & pos/kw args like 'foo(23, bar=baz)'
112  };
113 
115  std::string funcName;
116  std::vector<FnArg> args;
117 
118  template <class HashState>
119  friend void TfHashAppend(HashState &h, FnCall const &c) {
120  h.Append(c.kind, c.funcName, c.args);
121  }
122 
123  friend bool operator==(FnCall const &l, FnCall const &r) {
124  return std::tie(l.kind, l.funcName, l.args) ==
125  std::tie(r.kind, r.funcName, r.args);
126  }
127  friend bool operator!=(FnCall const &l, FnCall const &r) {
128  return !(l == r);
129  }
130  friend void swap(FnCall &l, FnCall &r) {
131  auto lt = std::tie(l.kind, l.funcName, l.args);
132  auto rt = std::tie(r.kind, r.funcName, r.args);
133  swap(lt, rt);
134  }
135  };
136 
137  /// Construct the empty expression whose bool-operator returns false.
138  SdfPredicateExpression() = default;
139 
140  /// Copy construct from another expression.
142 
143  /// Move construct from another expression.
145 
146  /// Construct an expression by parsing \p expr. If provided, \p context
147  /// appears in a parse error, if one is generated. See GetParseError().
148  /// See the class documentation for details on expression syntax.
149  SDF_API
150  explicit SdfPredicateExpression(std::string const &expr,
151  std::string const &context = {});
152 
153  /// Copy assign from another expression.
155  operator=(SdfPredicateExpression const &) = default;
156 
157  /// Move assign from another expression.
159  operator=(SdfPredicateExpression &&) = default;
160 
161  /// Enumerant describing a subexpression operation.
162  enum Op { Call, Not, ImpliedAnd, And, Or };
163 
164  /// Produce a new expression by prepending the 'not' operator onto \p right.
165  SDF_API
168 
169  /// Produce a new expression by combining \p left and \p right with the
170  /// operator \p op. The \p op must be one of ImpliedAnd, And, or Or.
171  SDF_API
173  MakeOp(Op op,
176 
177  /// Produce a new expression containing just a the function call \p call.
178  SDF_API
180  MakeCall(FnCall &&call);
181 
182  /// Walk this expression's syntax tree in depth-first order, calling \p call
183  /// with the current function call when a function call is encountered, and
184  /// calling \p logic multiple times for each logical operation encountered.
185  /// When calling \p logic, the logical operation is passed as the \p Op
186  /// parameter, and an integer indicating "where" we are in the set of
187  /// operands is passed as the int parameter. For a 'not', call \p
188  /// logic(Op=Not, int=0) to start, then after the subexpression that the
189  /// 'not' applies to is walked, call \p logic(Op=Not, int=1). For the
190  /// binary operators like 'and' and 'or', call \p logic(Op, 0) before the
191  /// first argument, then \p logic(Op, 1) after the first subexpression, then
192  /// \p logic(Op, 2) after the second subexpression. For a concrete example,
193  /// consider the following expression:
194  ///
195  /// (foo or bar) and not baz
196  ///
197  /// The sequence of calls from Walk() will be:
198  ///
199  /// logic(And, 0)
200  /// logic(Or, 0)
201  /// call("foo")
202  /// logic(Or, 1)
203  /// call("bar")
204  /// logic(Or, 2)
205  /// logic(And, 1)
206  /// logic(Not, 0)
207  /// call("baz")
208  /// logic(Not, 1)
209  /// logic(And, 2)
210  ///
211  SDF_API
212  void Walk(TfFunctionRef<void (Op, int)> logic,
213  TfFunctionRef<void (FnCall const &)> call) const;
214 
215  /// Equivalent to Walk(), except that the \p logic function is called with a
216  /// const reference to the current Op stack instead of just the top of it.
217  /// The top of the Op stack is the vector's back. This is useful in case
218  /// the processing code needs to understand the context in which an Op
219  /// appears.
220  SDF_API
221  void WalkWithOpStack(
222  TfFunctionRef<void (std::vector<std::pair<Op, int>> const &)> logic,
223  TfFunctionRef<void (FnCall const &)> call) const;
224 
225  /// Return a text representation of this expression that parses to the same
226  /// expression.
227  SDF_API
228  std::string GetText() const;
229 
230  /// Return true if this is the empty expression; i.e. default-constructed or
231  /// constructed from a string with invalid syntax.
232  bool IsEmpty() const {
233  return _ops.empty();
234  }
235 
236  /// Return true if this expression contains any operations, false otherwise.
237  explicit operator bool() const {
238  return !IsEmpty();
239  }
240 
241  /// Return parsing errors as a string if this function was constructed from
242  /// a string and parse errors were encountered.
243  std::string const &GetParseError() const & {
244  return _parseError;
245  }
246 
247  /// Return parsing errors as a string if this function was constructed from
248  /// a string and parse errors were encountered.
249  std::string GetParseError() && {
250  return std::move(_parseError);
251  }
252 
253 private:
254  template <class HashState>
255  friend void TfHashAppend(HashState &h, SdfPredicateExpression const &expr) {
256  h.Append(expr._ops, expr._calls, expr._parseError);
257  }
258 
259  friend bool
261  SdfPredicateExpression const &r) {
262  return std::tie(l._ops, l._calls, l._parseError) ==
263  std::tie(r._ops, r._calls, r._parseError);
264  }
265 
266  friend bool
268  SdfPredicateExpression const &r) {
269  return !(l == r);
270  }
271 
272  SDF_API
273  friend std::ostream &
274  operator<<(std::ostream &, SdfPredicateExpression const &);
275 
276  // The expression is represented in function-call style, but *in reverse* to
277  // facilitate efficient assembly. For example, an expression like "a and b"
278  // would be represented as { Call(b), Call(a), And } rather than { And,
279  // Call(a), Call(b) }. This way, joining two expressions like "a" 'and' "b"
280  // can be done by appending to a vector, avoiding having to shift all the
281  // elements down to insert the new operation at the head. See the
282  // implementation of Walk() for guidance.
283  std::vector<Op> _ops;
284 
285  // On the contrary, the elements in _calls are in forward-order, so the last
286  // Call in _ops corresponds to the first element of _calls.
287  std::vector<FnCall> _calls;
288 
289  // This member holds a parsing error string if this expression was
290  // constructed by the parser and errors were encountered during the parsing.
291  std::string _parseError;
292 };
293 
295 
296 #endif // PXR_USD_SDF_PREDICATE_EXPRESSION_H
friend bool operator!=(FnArg const &l, FnArg const &r)
static FnArg Keyword(std::string const &name, VtValue const &val)
paren/comma & pos/kw args like 'foo(23, bar=baz)'
GLint left
Definition: glcorearb.h:2005
SDF_API void Walk(TfFunctionRef< void(Op, int)> logic, TfFunctionRef< void(FnCall const &)> call) const
SdfPredicateExpression & operator=(SdfPredicateExpression const &)=default
Copy assign from another expression.
GLdouble right
Definition: glad.h:2817
friend bool operator==(SdfPredicateExpression const &l, SdfPredicateExpression const &r)
friend void TfHashAppend(HashState &h, FnCall const &c)
static SDF_API SdfPredicateExpression MakeCall(FnCall &&call)
Produce a new expression containing just a the function call call.
friend void TfHashAppend(HashState &h, SdfPredicateExpression const &expr)
SdfPredicateExpression()=default
Construct the empty expression whose bool-operator returns false.
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
Definition: core.h:1859
OutGridT const XformOp bool bool
friend bool operator==(FnCall const &l, FnCall const &r)
friend void swap(FnCall &l, FnCall &r)
SDF_API std::string GetText() const
friend bool operator!=(SdfPredicateExpression const &l, SdfPredicateExpression const &r)
friend bool operator==(FnArg const &l, FnArg const &r)
GLuint const GLchar * name
Definition: glcorearb.h:786
static SDF_API SdfPredicateExpression MakeOp(Op op, SdfPredicateExpression &&left, SdfPredicateExpression &&right)
friend bool operator!=(FnCall const &l, FnCall const &r)
#define SDF_API
Definition: api.h:23
GLfloat GLfloat GLfloat GLfloat h
Definition: glcorearb.h:2002
SDF_API void WalkWithOpStack(TfFunctionRef< void(std::vector< std::pair< Op, int >> const &)> logic, TfFunctionRef< void(FnCall const &)> call) const
PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE
Definition: path.h:1425
static SDF_API SdfPredicateExpression MakeNot(SdfPredicateExpression &&right)
Produce a new expression by prepending the 'not' operator onto right.
colon-separated pos args, like 'isa:Imageable'
GLuint GLfloat * val
Definition: glcorearb.h:1608
#define PXR_NAMESPACE_CLOSE_SCOPE
Definition: pxr.h:74
friend void TfHashAppend(HashState &h, FnArg const &arg)
GLboolean r
Definition: glcorearb.h:1222
friend void swap(FnArg &l, FnArg &r)
static FnArg Positional(VtValue const &val)
SDF_API friend std::ostream & operator<<(std::ostream &, SdfPredicateExpression const &)
Op
Enumerant describing a subexpression operation.
std::string const & GetParseError() const &
Definition: value.h:146