diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index 59e836443..1cdf0a409 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -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 diff --git a/lib/base/array-script.cpp b/lib/base/array-script.cpp index a97668350..f656b2fab 100644 --- a/lib/base/array-script.cpp +++ b/lib/base/array-script.cpp @@ -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(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; diff --git a/lib/base/generator-array.cpp b/lib/base/generator-array.cpp new file mode 100644 index 000000000..04f2d7d31 --- /dev/null +++ b/lib/base/generator-array.cpp @@ -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; +} diff --git a/lib/base/generator-array.hpp b/lib/base/generator-array.hpp new file mode 100644 index 000000000..6b3d3f2f0 --- /dev/null +++ b/lib/base/generator-array.hpp @@ -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 +#include + +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 */ diff --git a/lib/base/generator-filter.cpp b/lib/base/generator-filter.cpp new file mode 100644 index 000000000..2b32b684c --- /dev/null +++ b/lib/base/generator-filter.cpp @@ -0,0 +1,21 @@ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ + +#include "base/generator-filter.hpp" +#include "base/value.hpp" +#include + +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; +} diff --git a/lib/base/generator-filter.hpp b/lib/base/generator-filter.hpp new file mode 100644 index 000000000..f6ec6008e --- /dev/null +++ b/lib/base/generator-filter.hpp @@ -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 + +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 */ diff --git a/lib/base/generator-map.cpp b/lib/base/generator-map.cpp new file mode 100644 index 000000000..dd245d603 --- /dev/null +++ b/lib/base/generator-map.cpp @@ -0,0 +1,19 @@ +/* Icinga 2 | (c) 2020 Icinga GmbH | GPLv2+ */ + +#include "base/generator-map.hpp" +#include "base/value.hpp" +#include + +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; +} diff --git a/lib/base/generator-map.hpp b/lib/base/generator-map.hpp new file mode 100644 index 000000000..b4e963b42 --- /dev/null +++ b/lib/base/generator-map.hpp @@ -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 + +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 */ diff --git a/lib/base/generator-script.cpp b/lib/base/generator-script.cpp new file mode 100644 index 000000000..16dceb956 --- /dev/null +++ b/lib/base/generator-script.cpp @@ -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 +#include +#include +#include + +using namespace icinga; + +static bool GeneratorNext(const Reference::Ptr& out) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Generator::Ptr self = static_cast(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(vframe->Self); + REQUIRE_NOT_NULL(self); + + return self->GetLength(); +} + +static bool GeneratorContains(const Value& value) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Generator::Ptr self = static_cast(vframe->Self); + REQUIRE_NOT_NULL(self); + + return self->Contains(value); +} + +static Array::Ptr GeneratorSort(const std::vector& args) +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Generator::Ptr self = static_cast(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(vframe->Self); + REQUIRE_NOT_NULL(self); + + return self->Join(separator); +} + +static Array::Ptr GeneratorReverse() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Generator::Ptr self = static_cast(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(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(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(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(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(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(vframe->Self); + REQUIRE_NOT_NULL(self); + + return self->Unique(); +} + +static Array::Ptr GeneratorToArray() +{ + ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); + Generator::Ptr self = static_cast(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; +} diff --git a/lib/base/generator-unique.cpp b/lib/base/generator-unique.cpp new file mode 100644 index 000000000..e5d6f6362 --- /dev/null +++ b/lib/base/generator-unique.cpp @@ -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 + +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; +} diff --git a/lib/base/generator-unique.hpp b/lib/base/generator-unique.hpp new file mode 100644 index 000000000..69ed503ea --- /dev/null +++ b/lib/base/generator-unique.hpp @@ -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 +#include + +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 m_Unique; +}; + +} + +#endif /* GENERATOR_UNIQUE_H */ diff --git a/lib/base/generator.cpp b/lib/base/generator.cpp new file mode 100644 index 000000000..6b9301499 --- /dev/null +++ b/lib/base/generator.cpp @@ -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 +#include + +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); +} diff --git a/lib/base/generator.hpp b/lib/base/generator.hpp new file mode 100644 index 000000000..ec23ecf9b --- /dev/null +++ b/lib/base/generator.hpp @@ -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 + +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 */