Add lambda expressions with closures to Zeek.

This allows anonymous functions in Zeek to capture their closures.
they do so by creating a copy of their enclosing frame and joining
that with their own frame.

There is no way to specify what specific items to capture from the
closure like C++, nor is there a nonlocal keyword like Python.
Attemptying to declare a local variable that has already been caught
by the closure will error nicely. At the worst this is an inconvenience
for people who are using lambdas which use the same variable names
as their closures.

As a result of functions copying their enclosing frames there is no
way for a function with a closure to reach back up and modify the
state of the frame that it was created in. This lets functions that
generate functions work as expected. The function can reach back and
modify its copy of the frame that it is captured in though.

Implementation wise this is done by creating two new subclasses in
Zeek. The first is a LambdaExpression which can be thought of as a
function generator. It gathers all of the ingredients for a function
at parse time, and then when evaluated creats a new version of that
function with the frame it is being evaluated in as a closure. The
second subclass is a ClosureFrame. This acts for most intents and
purposes like a regular Frame, but it routes lookups of values to its
closure as needed.
This commit is contained in:
Zeke Medley 2019-06-12 14:40:40 -07:00
parent eef669f048
commit a3001f1b2b
17 changed files with 636 additions and 52 deletions

View file

@ -7,6 +7,8 @@
#include "Func.h"
#include "Trigger.h"
#include <string>
vector<Frame*> g_frame_stack;
Frame::Frame(int arg_size, const BroFunc* func, const val_list* fn_args)
@ -27,12 +29,54 @@ Frame::Frame(int arg_size, const BroFunc* func, const val_list* fn_args)
Clear();
}
Frame::Frame(const Frame* other)
{
this->size = other->size;
this->frame = other->frame;
this->function = other->function;
this->func_args = other->func_args;
this->next_stmt = 0;
this->break_before_next_stmt = false;
this->break_on_return = false;
this->delayed = false;
// We need to Ref this because the
// destructor will Unref.
if ( other->trigger )
Ref(other->trigger);
this->trigger = other->trigger;
this->call = other->call;
}
Frame::~Frame()
{
Unref(trigger);
Release();
}
void Frame::SetElement(int n, Val* v)
{
Unref(frame[n]);
frame[n] = v;
}
void Frame::SetElement(const ID* id, Val* v)
{
SetElement(id->Offset(), v);
}
Val* Frame::GetElement(ID* id) const
{
return this->frame[id->Offset()];
}
void Frame::AddElement(ID* id, Val* v)
{
this->SetElement(id, v);
}
void Frame::Reset(int startIdx)
{
for ( int i = startIdx; i < size; ++i )
@ -109,3 +153,101 @@ void Frame::ClearTrigger()
Unref(trigger);
trigger = 0;
}
ClosureFrame::ClosureFrame(Frame* closure, Frame* not_closure,
std::shared_ptr<id_list> outer_ids) : Frame(not_closure)
{
assert(closure);
this->closure = closure;
Ref(this->closure);
this->body = not_closure;
Ref(this->body);
// To clone a ClosureFrame we null outer_ids and then copy
// the set over directly, hence the check.
if (outer_ids)
{
// Install the closure IDs
id_list* tmp = outer_ids.get();
loop_over_list(*tmp, i)
{
ID* id = (*tmp)[i];
if (id)
this->closure_elements.insert(id->Name());
}
}
}
ClosureFrame::~ClosureFrame()
{
Unref(this->closure);
Unref(this->body);
}
Val* ClosureFrame::GetElement(ID* id) const
{
if (this->closure_elements.find(id->Name()) != this->closure_elements.end())
return ClosureFrame::GatherFromClosure(this, id);
return this->NthElement(id->Offset());
}
void ClosureFrame::SetElement(const ID* id, Val* v)
{
if (this->closure_elements.find(id->Name()) != this->closure_elements.end())
ClosureFrame::SetInClosure(this, id, v);
else
this->Frame::SetElement(id->Offset(), v);
}
Frame* ClosureFrame::Clone()
{
Frame* new_closure = this->closure->Clone();
Frame* new_regular = this->body->Clone();
ClosureFrame* cf = new ClosureFrame(new_closure, new_regular, nullptr);
cf->closure_elements = this->closure_elements;
return cf;
}
// Each ClosureFrame knows all of the outer IDs that are used inside of it. This is known at
// parse time. These leverage that. If frame_1 encloses frame_2 then the location of a lookup
// for an outer id in frame_2 can be determined by checking if that id is also an outer id in
// frame_2. If it is not, then frame_2 owns the id and the lookup is done there, otherwise,
// go deeper.
// Note the useage of dynamic_cast.
Val* ClosureFrame::GatherFromClosure(const Frame* start, const ID* id)
{
const ClosureFrame* conductor = dynamic_cast<const ClosureFrame*>(start);
auto closure_contains = [] (const ClosureFrame* cf, const ID* i)
{ return cf->closure_elements.find(i->Name()) != cf->closure_elements.end(); };
if ( ! conductor )
return start->NthElement(id->Offset());
if (closure_contains(conductor, id))
return ClosureFrame::GatherFromClosure(conductor->closure, id);
return conductor->NthElement(id->Offset());
}
void ClosureFrame::SetInClosure(Frame* start, const ID* id, Val* val)
{
ClosureFrame* conductor = dynamic_cast<ClosureFrame*>(start);
auto closure_contains = [] (const ClosureFrame* cf, const ID* i)
{ return cf->closure_elements.find(i->Name()) != cf->closure_elements.end(); };
if ( ! conductor )
start->SetElement(id->Offset(), val);
else if (closure_contains(conductor, id))
ClosureFrame::SetInClosure(conductor->closure, id, val);
else
conductor->Frame::SetElement(id->Offset(), val);
}