DSL: add generators

This commit is contained in:
Alexander A. Klimov 2020-01-24 11:47:16 +01:00
parent a65f2d6b41
commit cbd94e060d
13 changed files with 622 additions and 1 deletions

View File

@ -37,6 +37,11 @@ set(base_SOURCES
fifo.cpp fifo.hpp
filelogger.cpp filelogger.hpp filelogger-ti.hpp
function.cpp function.hpp function-ti.hpp function-script.cpp functionwrapper.hpp
generator.cpp generator.hpp generator-script.cpp
generator-array.cpp generator-array.hpp
generator-filter.cpp generator-filter.hpp
generator-map.cpp generator-map.hpp
generator-unique.cpp generator-unique.hpp
initialize.cpp initialize.hpp
intrusive-ptr.hpp
io-engine.cpp io-engine.hpp

View File

@ -3,6 +3,8 @@
#include "base/array.hpp"
#include "base/function.hpp"
#include "base/functionwrapper.hpp"
#include "base/generator.hpp"
#include "base/generator-array.hpp"
#include "base/scriptframe.hpp"
#include "base/objectlock.hpp"
#include "base/exception.hpp"
@ -233,6 +235,15 @@ static void ArrayFreeze()
self->Freeze();
}
static Generator::Ptr ArrayIter()
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Array::Ptr self = static_cast<Array::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
return new GeneratorArray(self);
}
Object::Ptr Array::GetPrototype()
{
static Dictionary::Ptr prototype = new Dictionary({
@ -253,7 +264,8 @@ Object::Ptr Array::GetPrototype()
{ "any", new Function("Array#any", ArrayAny, { "func" }, true) },
{ "all", new Function("Array#all", ArrayAll, { "func" }, true) },
{ "unique", new Function("Array#unique", ArrayUnique, {}, true) },
{ "freeze", new Function("Array#freeze", ArrayFreeze, {}) }
{ "freeze", new Function("Array#freeze", ArrayFreeze, {}) },
{ "iter", new Function("Array#iter", ArrayIter, {}, true) }
});
return prototype;

View File

@ -0,0 +1,19 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#include "base/generator-array.hpp"
#include "base/objectlock.hpp"
#include "base/value.hpp"
using namespace icinga;
bool GeneratorArray::GetNext(Value& out)
{
ObjectLock oLock (m_Source);
if (m_Next < m_Source->GetLength()) {
out = m_Source->Get(m_Next++);
return true;
}
return false;
}

View File

@ -0,0 +1,37 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#ifndef GENERATOR_ARRAY_H
#define GENERATOR_ARRAY_H
#include "base/array.hpp"
#include "base/generator.hpp"
#include "base/value.hpp"
#include <cstddef>
#include <utility>
namespace icinga
{
/**
* A lazy Value supplier of Array items.
*
* @ingroup base
*/
class GeneratorArray final : public Generator
{
public:
inline GeneratorArray(Array::Ptr source)
: m_Source(std::move(source)), m_Next(0)
{
}
bool GetNext(Value& out) override;
private:
Array::Ptr m_Source;
size_t m_Next;
};
}
#endif /* GENERATOR_ARRAY_H */

View File

@ -0,0 +1,21 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#include "base/generator-filter.hpp"
#include "base/value.hpp"
#include <utility>
using namespace icinga;
bool GeneratorFilter::GetNext(Value& out)
{
Value buf;
while (m_Source->GetNext(buf)) {
if (m_Function->Invoke({ buf })) {
out = std::move(buf);
return true;
}
}
return false;
}

View File

@ -0,0 +1,36 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#ifndef GENERATOR_FILTER_H
#define GENERATOR_FILTER_H
#include "base/function.hpp"
#include "base/generator.hpp"
#include "base/value.hpp"
#include <utility>
namespace icinga
{
/**
* Filters another generator's items based on a function.
*
* @ingroup base
*/
class GeneratorFilter final : public Generator
{
public:
inline GeneratorFilter(Generator::Ptr source, Function::Ptr function)
: m_Source(std::move(source)), m_Function(std::move(function))
{
}
bool GetNext(Value& out) override;
private:
Generator::Ptr m_Source;
Function::Ptr m_Function;
};
}
#endif /* GENERATOR_FILTER_H */

View File

@ -0,0 +1,19 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#include "base/generator-map.hpp"
#include "base/value.hpp"
#include <utility>
using namespace icinga;
bool GeneratorMap::GetNext(Value& out)
{
Value buf;
if (m_Source->GetNext(buf)) {
out = m_Function->Invoke({ std::move(buf) });
return true;
}
return false;
}

View File

@ -0,0 +1,36 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#ifndef GENERATOR_MAP_H
#define GENERATOR_MAP_H
#include "base/function.hpp"
#include "base/generator.hpp"
#include "base/value.hpp"
#include <utility>
namespace icinga
{
/**
* Applies a function to another generator's items.
*
* @ingroup base
*/
class GeneratorMap final : public Generator
{
public:
inline GeneratorMap(Generator::Ptr source, Function::Ptr function)
: m_Source(std::move(source)), m_Function(std::move(function))
{
}
bool GetNext(Value& out) override;
private:
Generator::Ptr m_Source;
Function::Ptr m_Function;
};
}
#endif /* GENERATOR_MAP_H */

View File

@ -0,0 +1,227 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#include "base/array.hpp"
#include "base/exception.hpp"
#include "base/function.hpp"
#include "base/generator.hpp"
#include "base/generator-filter.hpp"
#include "base/generator-map.hpp"
#include "base/objectlock.hpp"
#include "base/reference.hpp"
#include "base/scriptframe.hpp"
#include "base/value.hpp"
#include <algorithm>
#include <functional>
#include <utility>
#include <vector>
using namespace icinga;
static bool GeneratorNext(const Reference::Ptr& out)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
Value buf;
if (self->GetNext(buf)) {
if (out) {
out->Set(std::move(buf));
}
return true;
}
return false;
}
static double GeneratorLen()
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
return self->GetLength();
}
static bool GeneratorContains(const Value& value)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
return self->Contains(value);
}
static Array::Ptr GeneratorSort(const std::vector<Value>& args)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
auto arr (self->ToArray());
if (args.empty()) {
ObjectLock olock(arr);
std::sort(arr->Begin(), arr->End());
} else {
Function::Ptr function = args[0];
if (vframe->Sandboxed && !function->IsSideEffectFree())
BOOST_THROW_EXCEPTION(ScriptError("Sort function must be side-effect free."));
ObjectLock olock(arr);
std::sort(arr->Begin(), arr->End(), [&args](const Value& a, const Value& b) -> bool {
Function::Ptr cmp = args[0];
return cmp->Invoke({ a, b });
});
}
return arr;
}
static Value GeneratorJoin(const Value& separator)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
return self->Join(separator);
}
static Array::Ptr GeneratorReverse()
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
return self->Reverse();
}
static Generator::Ptr GeneratorMap(const Function::Ptr& function)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
if (vframe->Sandboxed && !function->IsSideEffectFree())
BOOST_THROW_EXCEPTION(ScriptError("Map function must be side-effect free."));
return new icinga::GeneratorMap(std::move(self), function);
}
static Value GeneratorReduce(const Function::Ptr& function)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
if (vframe->Sandboxed && !function->IsSideEffectFree())
BOOST_THROW_EXCEPTION(ScriptError("Reduce function must be side-effect free."));
Value result;
if (!self->GetNext(result)) {
return Empty;
}
Value buf;
while (self->GetNext(buf)) {
result = function->Invoke({ std::move(result), std::move(buf) });
}
return result;
}
static Generator::Ptr GeneratorFilter(const Function::Ptr& function)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
if (vframe->Sandboxed && !function->IsSideEffectFree())
BOOST_THROW_EXCEPTION(ScriptError("Filter function must be side-effect free."));
return new icinga::GeneratorFilter(std::move(self), function);
}
static bool GeneratorAny(const Function::Ptr& function)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
if (vframe->Sandboxed && !function->IsSideEffectFree())
BOOST_THROW_EXCEPTION(ScriptError("Filter function must be side-effect free."));
Value buf;
while (self->GetNext(buf)) {
if (function->Invoke({ std::move(buf) })) {
return true;
}
}
return false;
}
static bool GeneratorAll(const Function::Ptr& function)
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
if (vframe->Sandboxed && !function->IsSideEffectFree())
BOOST_THROW_EXCEPTION(ScriptError("Filter function must be side-effect free."));
Value buf;
while (self->GetNext(buf)) {
if (!function->Invoke({ std::move(buf) })) {
return false;
}
}
return true;
}
static Generator::Ptr GeneratorUnique()
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
return self->Unique();
}
static Array::Ptr GeneratorToArray()
{
ScriptFrame *vframe = ScriptFrame::GetCurrentFrame();
Generator::Ptr self = static_cast<Generator::Ptr>(vframe->Self);
REQUIRE_NOT_NULL(self);
return self->ToArray();
}
Object::Ptr Generator::GetPrototype()
{
static Dictionary::Ptr prototype = new Dictionary({
{ "next", new Function("Generator#next", GeneratorNext, { "out" }) },
{ "len", new Function("Generator#len", GeneratorLen, {}, true) },
{ "contains", new Function("Generator#contains", GeneratorContains, { "value" }, true) },
{ "sort", new Function("Generator#sort", GeneratorSort, { "less_cmp" }, true) },
{ "join", new Function("Generator#join", GeneratorJoin, { "separator" }, true) },
{ "reverse", new Function("Generator#reverse", GeneratorReverse, {}, true) },
{ "map", new Function("Generator#map", ::GeneratorMap, { "func" }, true) },
{ "reduce", new Function("Generator#reduce", GeneratorReduce, { "reduce" }, true) },
{ "filter", new Function("Generator#filter", ::GeneratorFilter, { "func" }, true) },
{ "any", new Function("Generator#any", GeneratorAny, { "func" }, true) },
{ "all", new Function("Generator#all", GeneratorAll, { "func" }, true) },
{ "unique", new Function("Generator#unique", ::GeneratorUnique, {}, true) },
{ "to_array", new Function("Generator#to_array", GeneratorToArray, {}, true) }
});
return prototype;
}

View File

@ -0,0 +1,26 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#include "base/generator-unique.hpp"
#include "base/objectlock.hpp"
#include "base/value.hpp"
#include <utility>
using namespace icinga;
bool GeneratorUnique::GetNext(Value& out)
{
ObjectLock oLock (this);
Value buf;
while (m_Source->GetNext(buf)) {
auto res (m_Unique.emplace(std::move(buf)));
if (res.second) {
out = *res.first;
return true;
}
}
return false;
}

View File

@ -0,0 +1,36 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#ifndef GENERATOR_UNIQUE_H
#define GENERATOR_UNIQUE_H
#include "base/generator.hpp"
#include "base/value.hpp"
#include <set>
#include <utility>
namespace icinga
{
/**
* Filters another generator's unique items.
*
* @ingroup base
*/
class GeneratorUnique final : public Generator
{
public:
inline GeneratorUnique(Generator::Ptr source)
: m_Source(std::move(source))
{
}
bool GetNext(Value& out) override;
private:
Generator::Ptr m_Source;
std::set<Value> m_Unique;
};
}
#endif /* GENERATOR_UNIQUE_H */

100
lib/base/generator.cpp Normal file
View File

@ -0,0 +1,100 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#include "base/array.hpp"
#include "base/generator.hpp"
#include "base/generator-unique.hpp"
#include "base/objectlock.hpp"
#include "base/primitivetype.hpp"
#include <algorithm>
#include <utility>
using namespace icinga;
REGISTER_PRIMITIVE_TYPE(Generator, Object, Generator::GetPrototype());
bool Generator::GetNext(Value& out)
{
return false;
}
/**
* Counts the items.
*
* @returns Amount of items.
*/
size_t Generator::GetLength()
{
size_t len = 0;
Value buf;
while (GetNext(buf)) {
++len;
}
return len;
}
/**
* Checks whether the items include the specified value.
*
* @param value The value.
* @returns true if the items include the value, false otherwise.
*/
bool Generator::Contains(const Value& value)
{
Value buf;
while (GetNext(buf)) {
if (buf == value) {
return true;
}
}
return false;
}
Array::Ptr Generator::Reverse()
{
auto result (ToArray());
ObjectLock oLock (result);
std::reverse(result->Begin(), result->End());
return std::move(result);
}
Value Generator::Join(const Value& separator)
{
Value result;
Value buf;
bool first = true;
while (GetNext(buf)) {
if (first) {
first = false;
} else {
result = std::move(result) + separator;
}
result = std::move(result) + std::move(buf);
}
return std::move(result);
}
Generator::Ptr Generator::Unique()
{
return new GeneratorUnique(this);
}
Array::Ptr Generator::ToArray()
{
Array::Ptr result (new Array());
Value buf;
while (GetNext(buf)) {
result->Add(std::move(buf));
}
return std::move(result);
}

47
lib/base/generator.hpp Normal file
View File

@ -0,0 +1,47 @@
/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */
#ifndef GENERATOR_H
#define GENERATOR_H
#include "base/array.hpp"
#include "base/object.hpp"
#include "base/value.hpp"
#include <cstddef>
namespace icinga
{
/**
* A lazy Value items supplier.
*
* @ingroup base
*/
class Generator : public Object
{
public:
DECLARE_OBJECT(Generator);
static Object::Ptr GetPrototype();
Generator() = default;
Generator(const Generator&) = delete;
Generator(Generator&&) = delete;
Generator& operator=(const Generator&) = delete;
Generator& operator=(Generator&&) = delete;
virtual ~Generator() = default;
virtual bool GetNext(Value& out);
size_t GetLength();
bool Contains(const Value& value);
Array::Ptr Reverse();
Value Join(const Value& separator);
Generator::Ptr Unique();
Array::Ptr ToArray();
};
}
#endif /* GENERATOR_H */