diff --git a/src/gsi/gsi/gsiDeclTl.cc b/src/gsi/gsi/gsiDeclTl.cc index c282c33c8..27f98a315 100644 --- a/src/gsi/gsi/gsiDeclTl.cc +++ b/src/gsi/gsi/gsiDeclTl.cc @@ -27,6 +27,7 @@ #include "tlProgress.h" #include "tlExpression.h" #include "tlGlobPattern.h" +#include "tlRecipe.h" // ---------------------------------------------------------------- // Logger binding @@ -640,4 +641,83 @@ Class decl_GlobPattern ("tl", "GlobPattern", "This class has been introduced in version 0.26." ); +class Recipe_Impl + : public tl::Recipe +{ +public: + Recipe_Impl (const std::string &name, const std::string &description) + : tl::Recipe (name, description) + { + // .. nothing yet .. + } + + virtual tl::Variant execute (const std::map ¶ms) const + { + if (execute_cb.can_issue ()) { + return execute_cb.issue &> (&tl::Recipe::execute, params); + } else { + return tl::Variant (); + } + } + + gsi::Callback execute_cb; +}; + +} + +namespace tl +{ + template <> struct type_traits : public type_traits { }; +} + +namespace gsi +{ + +static Recipe_Impl *make_recipe (const std::string &name, const std::string &description) +{ + return new Recipe_Impl (name, description); +} + +Class decl_Recipe_Impl ("tl", "Recipe", + gsi::constructor ("new", &make_recipe, gsi::arg ("name"), gsi::arg ("description", std::string ()), + "@brief Creates a new recipe object with the given name and (optional) description" + ) + + gsi::method ("name", &Recipe_Impl::name, + "@brief Gets the name of the recipe." + ) + + gsi::method ("description", &Recipe_Impl::description, + "@brief Gets the description of the recipe." + ) + + gsi::method ("make", &Recipe_Impl::make, gsi::arg ("generator"), + "@brief Executes the recipe given by the generator string.\n" + "The generator string is the one delivered with \\generator." + ) + + gsi::method ("generator", &Recipe_Impl::generator, gsi::arg ("params"), + "@brief Delivers the generator string from the given parameters.\n" + "The generator string can be used with \\make to re-run the recipe." + ) + + gsi::callback ("execute", &Recipe_Impl::execute, &Recipe_Impl::execute_cb, + "@brief Reimplement this method to provide the functionality of the recipe.\n" + "This method is supposed to re-run the recipe with the given parameters and deliver the " + "the intended output object." + ), + "@brief A facility for providing reproducable recipes\n" + "The idea of this facility is to provide a service by which an object\n" + "can be reproduced in a parametrized way. The intended use case is a \n" + "DRC report for example, where the DRC script is the generator.\n" + "\n" + "In this use case, the DRC engine will register a recipe. It will \n" + "put the serialized version of the recipe into the DRC report. If the \n" + "user requests a re-run of the DRC, the recipe will be called and \n" + "the implementation is supposed to deliver a new database.\n" + "\n" + "To register a recipe, reimplement tl::Recipe and create a singleton\n" + "instance. To serialize a recipe, use \"generator\", to execute the\n" + "recipe, use \"make\". \n" + "\n" + "Parameters are kept as a generic key/value map.\n" + "\n" + "This class has been introduced in version 0.26." +); + } diff --git a/src/tl/tl/tl.pro b/src/tl/tl/tl.pro index b481592d9..39184f182 100644 --- a/src/tl/tl/tl.pro +++ b/src/tl/tl/tl.pro @@ -45,7 +45,8 @@ SOURCES = \ tlUniqueId.cc \ tlList.cc \ tlEquivalenceClusters.cc \ - tlUniqueName.cc + tlUniqueName.cc \ + tlRecipe.cc HEADERS = \ tlAlgorithm.h \ @@ -100,7 +101,8 @@ HEADERS = \ tlUniqueId.h \ tlList.h \ tlEquivalenceClusters.h \ - tlUniqueName.h + tlUniqueName.h \ + tlRecipe.h equals(HAVE_CURL, "1") { diff --git a/src/tl/tl/tlRecipe.cc b/src/tl/tl/tlRecipe.cc new file mode 100644 index 000000000..3d02bb8ac --- /dev/null +++ b/src/tl/tl/tlRecipe.cc @@ -0,0 +1,87 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "tlRecipe.h" +#include "tlString.h" + +namespace tl +{ + +Recipe::Recipe (const std::string &name, const std::string &description) + : tl::RegisteredClass (this, 0, name.c_str (), false) +{ + m_name = name; + m_description = description; +} + +std::string Recipe::generator (const std::map ¶ms) +{ + std::string g; + g += tl::to_word_or_quoted_string (name ()); + g += ": "; + + for (std::map::const_iterator p = params.begin (); p != params.end (); ++p) { + if (p != params.begin ()) { + g += ","; + } + g += tl::to_word_or_quoted_string (p->first); + g += "="; + g += p->second.to_parsable_string (); + } + + return g; +} + +tl::Variant Recipe::make (const std::string &generator) +{ + tl::Extractor ex (generator.c_str ()); + + std::string recipe; + ex.read_word_or_quoted (recipe); + ex.test (":"); + + std::map params; + while (! ex.at_end ()) { + std::string key; + ex.read_word_or_quoted (key); + ex.test ("="); + tl::Variant v; + ex.read (v); + ex.test (","); + params.insert (std::make_pair (key, v)); + } + + tl::Recipe *recipe_obj = 0; + for (tl::Registrar::iterator r = tl::Registrar::begin (); r != tl::Registrar::end (); ++r) { + if (r->name () == recipe) { + recipe_obj = r.operator-> (); + } + } + + if (! recipe_obj) { + return tl::Variant (); + } else { + return recipe_obj->execute (params); + } +} + +} // namespace tl diff --git a/src/tl/tl/tlRecipe.h b/src/tl/tl/tlRecipe.h new file mode 100644 index 000000000..700df550e --- /dev/null +++ b/src/tl/tl/tlRecipe.h @@ -0,0 +1,132 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_tlRecipe +#define HDR_tlRecipe + +#include "tlCommon.h" +#include "tlVariant.h" +#include "tlTypeTraits.h" +#include "tlClassRegistry.h" + +namespace tl +{ + +/** + * @brief A facility for providing reproducable recipes + * + * The idea of this facility is to provide a service by which an object + * can be reproduced in a parametrized way. The intended use case is a + * DRC report for example, where the DRC script is the generator. + * + * In this use case, the DRC engine will register a recipe. It will + * put the serialized version of the recipe into the DRC report. If the + * user requests a re-run of the DRC, the recipe will be called and + * the implementation is supposed to deliver a new database. + * + * To register a recipe, reimplement tl::Recipe and create a singleton + * instance. To serialize a recipe, use "generator", to execute the + * recipe, use "make". + * + * Parameters are kept as a generic key/value map. + */ +class TL_PUBLIC Recipe + : public tl::RegisteredClass +{ +public: + /** + * @brief @brief Creates a new recipe object + */ + Recipe (const std::string &name, const std::string &description = std::string ()); + + /** + * @brief Destructor + */ + virtual ~Recipe () { } + + /** + * @brief Gets the recipes name (a unique identifier) + */ + const std::string &name () const + { + return m_name; + } + + /** + * @brief Gets the description text + */ + const std::string &description () const + { + return m_description; + } + + /** + * @brief An utility function to get a parameters + */ + template + static T get_value (const std::map ¶ms, const std::string &pname, const T &def_value) + { + std::map::const_iterator p = params.find (pname); + if (p != params.end ()) { + const tl::Variant &v = p->second; + return v.to (); + } else { + return def_value; + } + } + + /** + * @brief Serializes the given recipe + */ + std::string generator (const std::map ¶ms); + + /** + * @brief Executes the recipe from the generator + * + * Returns nil if the recipe can't be executed, e.g. because the recipe isn't known. + */ + static tl::Variant make (const std::string &generator); + + /** + * @brief Recipe interface: executes the recipe with the given parameters + */ + virtual tl::Variant execute (const std::map ¶ms) const = 0; + +private: + Recipe (const Recipe &) : tl::RegisteredClass (this) { } + Recipe &operator= (const Recipe &) { return *this; } + + std::string m_name; + std::string m_description; +}; + +template<> struct type_traits : public type_traits +{ + typedef tl::false_tag has_copy_constructor; + typedef tl::false_tag has_default_constructor; +}; + +} // namespace tl + +#endif + diff --git a/src/tl/unit_tests/tlRecipeTests.cc b/src/tl/unit_tests/tlRecipeTests.cc new file mode 100644 index 000000000..bf16eba78 --- /dev/null +++ b/src/tl/unit_tests/tlRecipeTests.cc @@ -0,0 +1,58 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2019 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "tlRecipe.h" +#include "tlUnitTest.h" + +#include + +namespace { + + class MyRecipe : public tl::Recipe + { + public: + MyRecipe () : tl::Recipe ("test_recipe", "description") { } + + tl::Variant execute (const std::map ¶ms) const + { + int a = get_value (params, "A", 0); + double b = get_value (params, "B", 0.0); + return tl::Variant (b * a); + } + }; + + static MyRecipe my_recipe; + +} + +// basic abilities +TEST(1) +{ + std::map params; + params["A"] = tl::Variant (7); + params["B"] = tl::Variant (6.0); + std::string g = my_recipe.generator (params); + EXPECT_EQ (g, "test_recipe: A=#7,B=##6"); + + tl::Variant res = tl::Recipe::make (g); + EXPECT_EQ (res.to_double (), 42.0); +} diff --git a/src/tl/unit_tests/unit_tests.pro b/src/tl/unit_tests/unit_tests.pro index 092699eef..989902872 100644 --- a/src/tl/unit_tests/unit_tests.pro +++ b/src/tl/unit_tests/unit_tests.pro @@ -38,7 +38,8 @@ SOURCES = \ tlListTests.cc \ tlEquivalenceClustersTests.cc \ tlUniqueNameTests.cc \ - tlGlobPatternTests.cc + tlGlobPatternTests.cc \ + tlRecipeTests.cc !equals(HAVE_QT, "0") { diff --git a/testdata/ruby/tlTest.rb b/testdata/ruby/tlTest.rb index eb439b1bd..e0369a353 100644 --- a/testdata/ruby/tlTest.rb +++ b/testdata/ruby/tlTest.rb @@ -262,6 +262,42 @@ class Tl_TestClass < TestBase end + class MyRecipe < RBA::Recipe + + def initialize + super("test_recipe", "description") + end + + def execute(params) + a = params["A"] || 0 + b = params["B"] || 0.0 + b * a + end + + end + + # Recipe + def test_4_Recipe + + # make sure there isn't a second instance + GC.start + + my_recipe = MyRecipe::new + my_recipe._create # makes debugging easier + + assert_equal(my_recipe.name, "test_recipe") + assert_equal(my_recipe.description, "description") + + g = my_recipe.generator("A" => 6, "B" => 7.0) + assert_equal(g, "test_recipe: A=#6,B=##7") + assert_equal("%g" % RBA::Recipe::make(g).to_s, "42") + + my_recipe._destroy + my_recipe = nil + GC.start + + end + end load("test_epilogue.rb")