GH-927: Fix circumvention of evaluation order in 'when' conditions

Historically, a 'when' condition performed an AST-traversal to locate
any index-expressions like `x[9]` and evaluated them so that it could
register the associated value as something for which it needs to receive
"modification" notifications.

Evaluating arbitrary expressions during an AST-traversal like that ignores
the typical order-of-evaluation/short-circuiting you'd expect if the
condition was evaluated normally, from its root expression.

Now, a new subclass of IndexExpr is used to keep track of all IndexExpr
results in the context of evaluating a 'when' condition without having
to do a secondary AST-traversal-and-eval.  i.e. the first evaluation of
the full 'when' condition follows the typical expression-evaluation
semantics (as always), but additionally now captures all the values
a Trigger needs to monitor for modifications.
This commit is contained in:
Jon Siwek 2020-09-15 16:50:24 -07:00
parent 0771dbcec6
commit 33ca675515
6 changed files with 141 additions and 29 deletions

View file

@ -519,7 +519,7 @@ public:
ValPtr Eval(Frame* f) const override;
};
class IndexExpr final : public BinaryExpr {
class IndexExpr : public BinaryExpr {
public:
IndexExpr(ExprPtr op1,
ListExprPtr op2, bool is_slice = false);
@ -549,6 +549,39 @@ protected:
bool is_slice;
};
class IndexExprWhen final : public IndexExpr {
public:
static inline std::vector<ValPtr> results = {};
static inline int evaluating = 0;
static void StartEval()
{ ++evaluating; }
static void EndEval()
{ --evaluating; }
static std::vector<ValPtr> TakeAllResults()
{
auto rval = std::move(results);
results = {};
return rval;
}
IndexExprWhen(ExprPtr op1, ListExprPtr op2, bool is_slice = false)
: IndexExpr(std::move(op1), std::move(op2), is_slice)
{ }
ValPtr Eval(Frame* f) const override
{
auto v = IndexExpr::Eval(f);
if ( v && evaluating > 0 )
results.emplace_back(v);
return v;
}
};
class FieldExpr final : public UnaryExpr {
public:
FieldExpr(ExprPtr op, const char* field_name);