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 Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #ifndef PXR_USD_SDF_PREDICATE_EXPRESSION_H
25 #define PXR_USD_SDF_PREDICATE_EXPRESSION_H
26 
27 #include "pxr/pxr.h"
28 #include "pxr/usd/sdf/api.h"
29 #include "pxr/base/tf/hash.h"
30 #include "pxr/base/vt/value.h"
31 
32 #include <iosfwd>
33 #include <string>
34 #include <tuple>
35 #include <utility>
36 #include <vector>
37 
39 
40 /// \class SdfPredicateExpression
41 ///
42 /// Represents a logical expression syntax tree consisting of predicate function
43 /// calls joined by the logical operators 'and', 'or', 'not', and an implied-and
44 /// operator that represents two subexpressions joined by only whitespace.
45 ///
46 /// An SdfPredicateExpression can be constructed with a string, which will parse
47 /// an expression. The syntax for an expression is as follows:
48 ///
49 /// The fundamental building blocks are calls to predicate functions. There are
50 /// three syntaxes for function calls.
51 ///
52 /// \li Bare call: just a function name: `isDefined`
53 /// \li Colon call: name, colon, positional arguments: `isa:mammal,bird`
54 /// \li Paren call: name and parenthesized positional and keyword args:
55 /// `isClose(1.23, tolerance=0.01)`
56 ///
57 /// Colon call arguments are all positional and must be separated by commas with
58 /// no spaces between arguments. In paren calls, positional arguments must
59 /// precede keyword arguments, and whitespace is allowed between arguments.
60 ///
61 /// The string parser supports argument values of the following types:
62 /// double-quoted "strings", unquoted strings, integers, floating-point numbers,
63 /// and boolean values 'true' and 'false'.
64 ///
65 /// The unary operator 'not' may appear preceding a function call, or a
66 /// subexpresion enclosed in parentheses. The binary operators 'and' and 'or'
67 /// may appear between subexpressions. If subexpressions appear adjacent to each
68 /// other (other than possible whitespace), this is considered an implied 'and'
69 /// operator.
70 ///
71 /// Operator precedence in order from highest to lowest is: 'not',
72 /// <implied-and>, 'and', 'or'.
73 ///
74 /// Here are some examples of valid predicate expression syntax:
75 ///
76 /// \li `foo` (call "foo" with no arguments)
77 /// \li `foo bar` (implicit 'and' of "foo" and "bar")
78 /// \li `foo not bar` (implicit 'and' of "foo" and "not bar")
79 /// \li `color:red (shiny or matte)`
80 /// \li `animal or mineral or vegetable`
81 /// \li `(mammal or bird) and (tame or small)`
82 /// \li `isClose(100, tolerance=3.0) or negative`
83 ///
85 {
86 public:
87 
88  /// \class FnArg
89  ///
90  /// Represents a function argument name and value. Positional arguments
91  /// have empty names.
92  struct FnArg {
93  static FnArg Positional(VtValue const &val) {
94  return { std::string(), val };
95  }
96  static FnArg Keyword(std::string const &name, VtValue const &val) {
97  return { name, val };
98  }
101 
102  template <class HashState>
103  friend void TfHashAppend(HashState &h, FnArg const &arg) {
104  h.Append(arg.argName, arg.value);
105  }
106 
107  friend bool operator==(FnArg const &l, FnArg const &r) {
108  return std::tie(l.argName, l.value) == std::tie(r.argName, r.value);
109  }
110  friend bool operator!=(FnArg const &l, FnArg const &r) {
111  return !(l == r);
112  }
113 
114  friend void swap(FnArg &l, FnArg &r) {
115  swap(l.argName, r.argName);
116  swap(l.value, r.value);
117  }
118  };
119 
120  /// \class FnCall
121  ///
122  /// Represents a function call in an expression with calling style, function
123  /// name, and arguments.
124  struct FnCall {
125  enum Kind {
126  BareCall, ///< no-arg call like 'active'
127  ColonCall, ///< colon-separated pos args, like 'isa:Imageable'
128  ParenCall ///< paren/comma & pos/kw args like 'foo(23, bar=baz)'
129  };
130 
133  std::vector<FnArg> args;
134 
135  template <class HashState>
136  friend void TfHashAppend(HashState &h, FnCall const &c) {
137  h.Append(c.kind, c.funcName, c.args);
138  }
139 
140  friend bool operator==(FnCall const &l, FnCall const &r) {
141  return std::tie(l.kind, l.funcName, l.args) ==
142  std::tie(r.kind, r.funcName, r.args);
143  }
144  friend bool operator!=(FnCall const &l, FnCall const &r) {
145  return !(l == r);
146  }
147  friend void swap(FnCall &l, FnCall &r) {
148  auto lt = std::tie(l.kind, l.funcName, l.args);
149  auto rt = std::tie(r.kind, r.funcName, r.args);
150  swap(lt, rt);
151  }
152  };
153 
154  /// Construct the empty expression whose bool-operator returns false.
155  SdfPredicateExpression() = default;
156 
157  /// Copy construct from another expression.
159 
160  /// Move construct from another expression.
162 
163  /// Construct an expression by parsing \p expr. If provided, \p context
164  /// appears in a parse error, if one is generated. See GetParseError().
165  /// See the class documentation for details on expression syntax.
166  SDF_API
167  explicit SdfPredicateExpression(std::string const &expr,
168  std::string const &context = {});
169 
170  /// Copy assign from another expression.
172  operator=(SdfPredicateExpression const &) = default;
173 
174  /// Move assign from another expression.
176  operator=(SdfPredicateExpression &&) = default;
177 
178  /// Enumerant describing a subexpression operation.
179  enum Op { Call, Not, ImpliedAnd, And, Or };
180 
181  /// Produce a new expression by prepending the 'not' operator onto \p right.
182  SDF_API
185 
186  /// Produce a new expression by combining \p left and \p right with the
187  /// operator \p op. The \p op must be one of ImpliedAnd, And, or Or.
188  SDF_API
190  MakeOp(Op op,
193 
194  /// Produce a new expression containing just a the function call \p call.
195  SDF_API
197  MakeCall(FnCall &&call);
198 
199  /// Walk this expression's syntax tree in depth-first order, calling \p call
200  /// with the current function call when a function call is encountered, and
201  /// calling \p logic multiple times for each logical operation encountered.
202  /// When calling \p logic, the logical operation is passed as the \p Op
203  /// parameter, and an integer indicating "where" we are in the set of
204  /// operands is passed as the int parameter. For a 'not', call \p
205  /// logic(Op=Not, int=0) to start, then after the subexpression that the
206  /// 'not' applies to is walked, call \p logic(Op=Not, int=1). For the
207  /// binary operators like 'and' and 'or', call \p logic(Op, 0) before the
208  /// first argument, then \p logic(Op, 1) after the first subexpression, then
209  /// \p logic(Op, 2) after the second subexpression. For a concrete example,
210  /// consider the following expression:
211  ///
212  /// (foo or bar) and not baz
213  ///
214  /// The sequence of calls from Walk() will be:
215  ///
216  /// logic(And, 0)
217  /// logic(Or, 0)
218  /// call("foo")
219  /// logic(Or, 1)
220  /// call("bar")
221  /// logic(Or, 2)
222  /// logic(And, 1)
223  /// logic(Not, 0)
224  /// call("baz")
225  /// logic(Not, 1)
226  /// logic(And, 2)
227  ///
228  SDF_API
229  void Walk(TfFunctionRef<void (Op, int)> logic,
230  TfFunctionRef<void (FnCall const &)> call) const;
231 
232  /// Equivalent to Walk(), except that the \p logic function is called with a
233  /// const reference to the current Op stack instead of just the top of it.
234  /// The top of the Op stack is the vector's back. This is useful in case
235  /// the processing code needs to understand the context in which an Op
236  /// appears.
237  SDF_API
238  void WalkWithOpStack(
239  TfFunctionRef<void (std::vector<std::pair<Op, int>> const &)> logic,
240  TfFunctionRef<void (FnCall const &)> call) const;
241 
242  /// Return a text representation of this expression that parses to the same
243  /// expression.
244  SDF_API
245  std::string GetText() const;
246 
247  /// Return true if this is the empty expression; i.e. default-constructed or
248  /// constructed from a string with invalid syntax.
249  bool IsEmpty() const {
250  return _ops.empty();
251  }
252 
253  /// Return true if this expression contains any operations, false otherwise.
254  explicit operator bool() const {
255  return !IsEmpty();
256  }
257 
258  /// Return parsing errors as a string if this function was constructed from
259  /// a string and parse errors were encountered.
260  std::string const &GetParseError() const & {
261  return _parseError;
262  }
263 
264  /// Return parsing errors as a string if this function was constructed from
265  /// a string and parse errors were encountered.
267  return _parseError;
268  }
269 
270 private:
271  template <class HashState>
272  friend void TfHashAppend(HashState &h, SdfPredicateExpression const &expr) {
273  h.Append(expr._ops, expr._calls, expr._parseError);
274  }
275 
276  friend bool
278  SdfPredicateExpression const &r) {
279  return std::tie(l._ops, l._calls, l._parseError) ==
280  std::tie(r._ops, r._calls, r._parseError);
281  }
282 
283  friend bool
285  SdfPredicateExpression const &r) {
286  return !(l == r);
287  }
288 
289  SDF_API
290  friend std::ostream &
291  operator<<(std::ostream &, SdfPredicateExpression const &);
292 
293  // The expression is represented in function-call style, but *in reverse* to
294  // facilitate efficient assembly. For example, an expression like "a and b"
295  // would be represented as { Call(b), Call(a), And } rather than { And,
296  // Call(a), Call(b) }. This way, joining two expressions like "a" 'and' "b"
297  // can be done by appending to a vector, avoiding having to shift all the
298  // elements down to insert the new operation at the head. See the
299  // implementation of Walk() for guidance.
300  std::vector<Op> _ops;
301 
302  // On the contrary, the elements in _calls are in forward-order, so the last
303  // Call in _ops corresponds to the first element of _calls.
304  std::vector<FnCall> _calls;
305 
306  // This member holds a parsing error string if this expression was
307  // constructed by the parser and errors were encountered during the parsing.
308  std::string _parseError;
309 };
310 
312 
313 #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.
GLsizei const GLchar *const * string
Definition: glcorearb.h:814
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:1736
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)
std::string GetParseError() const &&
friend bool operator!=(FnCall const &l, FnCall const &r)
#define SDF_API
Definition: api.h:40
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:1432
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:91
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:164