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

@ -56,24 +56,6 @@ TraversalCode trigger::TriggerTraversalCallback::PreExpr(const Expr* expr)
break;
};
case EXPR_INDEX:
{
const auto* e = static_cast<const IndexExpr*>(expr);
Obj::SuppressErrors no_errors;
try
{
auto v = e->Eval(trigger->frame);
if ( v )
trigger->Register(v.get());
}
catch ( InterpreterException& )
{ /* Already reported */ }
break;
}
default:
// All others are uninteresting.
break;
@ -217,12 +199,15 @@ Trigger::~Trigger()
// point.
}
void Trigger::Init()
void Trigger::Init(std::vector<ValPtr> index_expr_results)
{
assert(! disabled);
UnregisterAll();
TriggerTraversalCallback cb(this);
cond->Traverse(&cb);
for ( const auto& v : index_expr_results )
Register(v.get());
}
bool Trigger::Eval()
@ -265,6 +250,7 @@ bool Trigger::Eval()
f->SetTrigger({NewRef{}, this});
ValPtr v;
IndexExprWhen::StartEval();
try
{
@ -273,6 +259,9 @@ bool Trigger::Eval()
catch ( InterpreterException& )
{ /* Already reported */ }
IndexExprWhen::EndEval();
auto index_expr_results = IndexExprWhen::TakeAllResults();
f->ClearTrigger();
if ( f->HasDelayed() )
@ -288,7 +277,7 @@ bool Trigger::Eval()
// Not true. Perhaps next time...
DBG_LOG(DBG_NOTIFIERS, "%s: trigger condition is false", Name());
Unref(f);
Init();
Init(std::move(index_expr_results));
return false;
}