Loading...
Searching...
No Matches
predicateProgram.h
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_PROGRAM_H
8#define PXR_USD_SDF_PREDICATE_PROGRAM_H
9
10#include "pxr/pxr.h"
11#include "pxr/usd/sdf/api.h"
12
14#include "pxr/base/tf/functionTraits.h"
15#include "pxr/base/vt/value.h"
16
17#include "pxr/usd/sdf/predicateExpression.h"
18#include "pxr/usd/sdf/predicateLibrary.h"
19
20#include <initializer_list>
21#include <memory>
22#include <string>
23#include <vector>
24
25PXR_NAMESPACE_OPEN_SCOPE
26
27// fwd decl
28template <class DomainType>
30
31// fwd decl
32template <class DomainType>
36
49template <class DomainType>
51{
52public:
53 using PredicateFunction =
55
57 SdfLinkPredicateExpression<DomainType>(
58 SdfPredicateExpression const &expr,
60
62 explicit operator bool() const {
63 return !_ops.empty();
64 }
65
68 operator()(DomainType const &obj) const {
71 int nest = 0;
72 auto funcIter = _funcs.cbegin();
73 auto opIter = _ops.cbegin(), opEnd = _ops.cend();
74
75 // The current implementation favors short-circuiting over constance
76 // propagation. It might be beneficial to avoid short-circuiting when
77 // constancy isn't known, in hopes of establishing constancy. For
78 // example, if we have 'A or B', and 'A' evaluates to 'true' with
79 // MayVaryOverDescendants, we will skip evaluating B
80 // (short-circuit). This means we would miss the possibility of
81 // upgrading the constancy in case B returned 'true' with
82 // ConstantOverDescendants. This isn't a simple switch to flip though;
83 // we'd have to do some code restructuring here.
84 //
85 // For posterity, the rules for propagating constancy are the following,
86 // where A and B are the truth-values, and c(A), c(B), are whether or
87 // not the constancy is ConstantOverDescendants for A, B, respectively:
88 //
89 // c(A or B) = (A and c(A)) or (B and c(B)) or (c(A) and c(B))
90 // c(A and B) = (!A and c(A)) or (!B and c(B)) or (c(A) and c(B))
91
92 // Helper for short-circuiting "and" and "or" operators. Advance,
93 // ignoring everything until we reach the next Close that brings us to
94 // the starting nest level.
95 auto shortCircuit = [&]() {
96 const int origNest = nest;
97 for (; opIter != opEnd; ++opIter) {
98 switch(*opIter) {
99 case Call: ++funcIter; break; // Skip calls.
100 case Not: case And: case Or: break; // Skip operations.
101 case Open: ++nest; break;
102 case Close:
103 if (--nest == origNest) {
104 return;
105 }
106 break;
107 };
108 }
109 };
110
111 // Evaluate the predicate expression by processing operations and
112 // invoking predicate functions.
113 for (; opIter != opEnd; ++opIter) {
114 switch (*opIter) {
115 case Call:
116 result.SetAndPropagateConstancy((*funcIter++)(obj));
117 break;
118 case Not: result = !result; break;
119 case And: case Or: {
120 const bool decidingValue = *opIter != And;
121 // If the and/or result is already the deciding value,
122 // short-circuit. Otherwise the result is the rhs, so continue.
123 if (result == decidingValue) {
124 shortCircuit();
125 }
126 }
127 break;
128 case Open: ++nest; break;
129 case Close: --nest; break;
130 };
131 }
132 return result;
133 }
134
135private:
136 enum _Op { Call, Not, Open, Close, And, Or };
137 std::vector<_Op> _ops;
138 std::vector<PredicateFunction> _funcs;
139};
140
141
145template <class DomainType>
147SdfLinkPredicateExpression(SdfPredicateExpression const &expr,
149{
150 using Expr = SdfPredicateExpression;
151 using Program = SdfPredicateProgram<DomainType>;
152
153 // Walk expr and populate prog, binding calls with lib.
154
155 Program prog;
156 std::string errs;
157
158 auto exprToProgramOp = [](Expr::Op op) {
159 switch (op) {
160 case Expr::Call: return Program::Call;
161 case Expr::Not: return Program::Not;
162 case Expr::ImpliedAnd: case Expr::And: return Program::And;
163 case Expr::Or: return Program::Or;
164 };
165 return static_cast<typename Program::_Op>(-1);
166 };
167
168 auto translateLogic = [&](Expr::Op op, int argIndex) {
169 switch (op) {
170 case Expr::Not: // Not is postfix, RPN-style.
171 if (argIndex == 1) {
172 prog._ops.push_back(Program::Not);
173 }
174 break;
175 case Expr::ImpliedAnd: // Binary logic ops are infix to facilitate
176 case Expr::And: // short-circuiting.
177 case Expr::Or:
178 if (argIndex == 1) {
179 prog._ops.push_back(exprToProgramOp(op));
180 prog._ops.push_back(Program::Open);
181 }
182 else if (argIndex == 2) {
183 prog._ops.push_back(Program::Close);
184 }
185 break;
186 case Expr::Call:
187 break; // do nothing, handled in translateCall.
188 };
189 };
190
191 auto translateCall = [&](Expr::FnCall const &call) {
192 // Try to bind the call against library overloads. If successful,
193 // insert a call op and the function.
194 if (auto fn = lib._BindCall(call.funcName, call.args)) {
195 prog._funcs.push_back(std::move(fn));
196 prog._ops.push_back(Program::Call);
197 }
198 else {
199 if (!errs.empty()) {
200 errs += ", ";
201 }
202 errs += "Failed to bind call of " + call.funcName;
203 }
204 };
205
206 // Walk the expression and build the "compiled" program.
207 expr.Walk(translateLogic, translateCall);
208
209 if (!errs.empty()) {
210 prog = {};
211 TF_RUNTIME_ERROR(errs);
212 }
213 return prog;
214}
215
216PXR_NAMESPACE_CLOSE_SCOPE
217
218#endif // PXR_USD_SDF_PREDICATE_PROGRAM_H
Low-level utilities for informing users of various internal and external diagnostic conditions.
Represents a logical expression syntax tree consisting of predicate function calls joined by the logi...
SDF_API void Walk(TfFunctionRef< void(Op, int)> logic, TfFunctionRef< void(FnCall const &)> call) const
Walk this expression's syntax tree in depth-first order, calling call with the current function call ...
Represents the result of a predicate function: a pair of the boolean result and a Constancy token ind...
static SdfPredicateFunctionResult MakeConstant(bool value)
Create with value and 'ConstantOverDescendants'.
void SetAndPropagateConstancy(SdfPredicateFunctionResult other)
Set this result's value to other's value, and propagate constancy; if both this and other are Constan...
Represents a library of predicate functions for use with SdfPredicateExpression.
std::function< SdfPredicateFunctionResult(DomainType const &)> PredicateFunction
The type of a bound function, the result of binding passed arguments.
Represents a callable "program", the result of linking an SdfPredicateExpression with an SdfPredicate...
friend SdfPredicateProgram SdfLinkPredicateExpression(SdfPredicateExpression const &expr, SdfPredicateLibrary< DomainType > const &lib)
Link expr with lib and return a callable program that evaluates expr on given objects of the DomainTy...
SdfPredicateFunctionResult operator()(DomainType const &obj) const
Run the predicate program on obj, and return the result.
#define TF_RUNTIME_ERROR(fmt, args)
Issue a generic runtime error, but continue execution.
Definition: diagnostic.h:83