From cb7021881a3ccd97311a580b903dc82bebbc092d Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 20 Jun 2017 14:26:19 +0200 Subject: [PATCH 1/3] Whiskers template system --- cmake/UseDev.cmake | 1 + libdevcore/Whiskers.cpp | 127 ++++++++++++++++++++++++++++++ libdevcore/Whiskers.h | 87 ++++++++++++++++++++ test/libdevcore/MiniMoustache.cpp | 127 ++++++++++++++++++++++++++++++ 4 files changed, 342 insertions(+) create mode 100644 libdevcore/Whiskers.cpp create mode 100644 libdevcore/Whiskers.h create mode 100644 test/libdevcore/MiniMoustache.cpp diff --git a/cmake/UseDev.cmake b/cmake/UseDev.cmake index 4461a8a0d..68df691ae 100644 --- a/cmake/UseDev.cmake +++ b/cmake/UseDev.cmake @@ -10,6 +10,7 @@ function(eth_apply TARGET REQUIRED SUBMODULE) target_link_libraries(${TARGET} ${Boost_RANDOM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_FILESYSTEM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_SYSTEM_LIBRARIES}) + target_link_libraries(${TARGET} ${Boost_REGEX_LIBRARIES}) if (DEFINED MSVC) target_link_libraries(${TARGET} ${Boost_CHRONO_LIBRARIES}) diff --git a/libdevcore/Whiskers.cpp b/libdevcore/Whiskers.cpp new file mode 100644 index 000000000..4bad8476c --- /dev/null +++ b/libdevcore/Whiskers.cpp @@ -0,0 +1,127 @@ +/* + This file is part of solidity. + + solidity 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 3 of the License, or + (at your option) any later version. + + solidity 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 solidity. If not, see . +*/ +/** @file Whiskers.cpp + * @author Chris + * @date 2017 + * + * Moustache-like templates. + */ + +#include + +#include + +#include + +using namespace std; +using namespace dev; + +Whiskers::Whiskers(string const& _template): +m_template(_template) +{ +} + +Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value) +{ + assertThrow( + m_parameters.count(_parameter) == 0, + WhiskersError, + _parameter + " already set." + ); + assertThrow( + m_listParameters.count(_parameter) == 0, + WhiskersError, + _parameter + " already set as list parameter." + ); + m_parameters[_parameter] = _value; + + return *this; +} + +Whiskers& Whiskers::operator ()( + string const& _listParameter, + vector> const& _values +) +{ + assertThrow( + m_listParameters.count(_listParameter) == 0, + WhiskersError, + _listParameter + " already set." + ); + assertThrow( + m_parameters.count(_listParameter) == 0, + WhiskersError, + _listParameter + " already set as value parameter." + ); + m_listParameters[_listParameter] = _values; + + return *this; +} + +string Whiskers::render() const +{ + return replace(m_template, m_parameters, m_listParameters); +} + +string Whiskers::replace( + string const& _template, + StringMap const& _parameters, + map> const& _listParameters +) +{ + using namespace boost; + static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)"); + return regex_replace(_template, listOrTag, [&](match_results _match) -> string + { + string tagName(_match[1]); + if (!tagName.empty()) + { + assertThrow(_parameters.count(tagName), WhiskersError, "Tag " + tagName + " not found."); + return _parameters.at(tagName); + } + else + { + string listName(_match[2]); + string templ(_match[3]); + assertThrow(!listName.empty(), WhiskersError, ""); + assertThrow( + _listParameters.count(listName), + WhiskersError, "List parameter " + listName + " not set." + ); + string replacement; + for (auto const& parameters: _listParameters.at(listName)) + replacement += replace(templ, joinMaps(_parameters, parameters)); + return replacement; + } + }); +} + +Whiskers::StringMap Whiskers::joinMaps( + Whiskers::StringMap const& _a, + Whiskers::StringMap const& _b +) +{ + Whiskers::StringMap ret = _a; + for (auto const& x: _b) + assertThrow( + ret.insert(x).second, + WhiskersError, + "Parameter collision" + ); + return ret; +} + diff --git a/libdevcore/Whiskers.h b/libdevcore/Whiskers.h new file mode 100644 index 000000000..21d46af4c --- /dev/null +++ b/libdevcore/Whiskers.h @@ -0,0 +1,87 @@ +/* + This file is part of solidity. + + solidity 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 3 of the License, or + (at your option) any later version. + + solidity 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 solidity. If not, see . +*/ +/** @file Whiskers.h + * @author Chris + * @date 2017 + * + * Moustache-like templates. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace dev +{ + +DEV_SIMPLE_EXCEPTION(WhiskersError); + +/// +/// Moustache-like templates. +/// +/// Usage: +/// std::vector> listValues(2); +/// listValues[0]["k"] = "key1"; +/// listValues[0]["v"] = "value1"; +/// listValues[1]["k"] = "key2"; +/// listValues[1]["v"] = "value2"; +/// auto s = Whiskers("\n<#list> -> \n") +/// ("p1", "HEAD") +/// ("list", listValues) +/// .render(); +/// +/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" +/// +/// Note that lists cannot themselves contain lists - this would be a future feature. +class Whiskers +{ +public: + using StringMap = std::map; + using StringListMap = std::map>; + + explicit Whiskers(std::string const& _template); + + /// Sets a single parameter, . + Whiskers& operator()(std::string const& _parameter, std::string const& _value); + /// Sets a list parameter, <#listName> . + Whiskers& operator()( + std::string const& _listParameter, + std::vector const& _values + ); + + std::string render() const; + +private: + static std::string replace( + std::string const& _template, + StringMap const& _parameters, + StringListMap const& _listParameters = StringListMap() + ); + + /// Joins the two maps throwing an exception if two keys are equal. + static StringMap joinMaps(StringMap const& _a, StringMap const& _b); + + std::string m_template; + StringMap m_parameters; + StringListMap m_listParameters; +}; + +} diff --git a/test/libdevcore/MiniMoustache.cpp b/test/libdevcore/MiniMoustache.cpp new file mode 100644 index 000000000..84149173e --- /dev/null +++ b/test/libdevcore/MiniMoustache.cpp @@ -0,0 +1,127 @@ +/* + This file is part of solidity. + + solidity 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 3 of the License, or + (at your option) any later version. + + solidity 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 solidity. If not, see . +*/ +/** + * Unit tests for the mini moustache class. + */ + +#include + +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(WhiskersTest) + +BOOST_AUTO_TEST_CASE(no_templates) +{ + string templ = "this text does not contain templates"; + BOOST_CHECK_EQUAL(Whiskers(templ).render(), templ); +} + +BOOST_AUTO_TEST_CASE(basic_replacement) +{ + string templ = "a x -> ."; + string result = Whiskers(templ) + ("b", "BE") + ("c", "CE") + ("d", "DE") + .render(); + BOOST_CHECK_EQUAL(result, "a BE x CE -> DE."); +} + +BOOST_AUTO_TEST_CASE(tag_unavailable) +{ + string templ = ""; + Whiskers m(templ); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(complicated_replacement) +{ + string templ = "a x \n >."; + string result = Whiskers(templ) + ("b", "BE") + ("complicated", "COPL") + ("nesPL \n NEST>."); +} + +BOOST_AUTO_TEST_CASE(non_existing_list) +{ + string templ = "a <#b>"; + Whiskers m(templ); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(empty_list) +{ + string templ = "a <#b>x"; + string result = Whiskers(templ)("b", vector{}).render(); + BOOST_CHECK_EQUAL(result, "a x"); +} + +BOOST_AUTO_TEST_CASE(list) +{ + string templ = "a<#b>( - )x"; + vector> list(2); + list[0]["g"] = "GE"; + list[0]["h"] = "H"; + list[1]["g"] = "2GE"; + list[1]["h"] = "2H"; + string result = Whiskers(templ)("b", list).render(); + BOOST_CHECK_EQUAL(result, "a( GE - H )( 2GE - 2H )x"); +} + +BOOST_AUTO_TEST_CASE(recursive_list) +{ + // Check that templates resulting from lists are not expanded again + string templ = "a<#b> 13 "; + vector> list(1); + list[0]["g"] = ""; + string result = Whiskers(templ)("x", "X")("b", list).render(); + BOOST_CHECK_EQUAL(result, "a 13 X"); +} + +BOOST_AUTO_TEST_CASE(list_can_access_upper) +{ + string templ = "<#b>()"; + vector> list(2); + Whiskers m(templ); + string result = m("a", "A")("b", list).render(); + BOOST_CHECK_EQUAL(result, "(A)(A)"); +} + +BOOST_AUTO_TEST_CASE(parameter_collision) +{ + string templ = "a <#b>"; + vector> list(1); + list[0]["a"] = "x"; + Whiskers m(templ); + m("a", "X")("b", list); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} From 1d4f40e3a2fa4e4316847a143d64916baf76580f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 21 Jun 2017 20:46:42 +0100 Subject: [PATCH 2/3] Document Whiskers. --- docs/assembly.rst | 2 ++ docs/contributing.rst | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/docs/assembly.rst b/docs/assembly.rst index 7ef414839..836436348 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -13,6 +13,8 @@ TODO: Write about how scoping rules of inline assembly are a bit different and the complications that arise when for example using internal functions of libraries. Furthermore, write about the symbols defined by the compiler. +.. _inline-assembly: + Inline Assembly =============== diff --git a/docs/contributing.rst b/docs/contributing.rst index 1f869dbb2..559f9f6a7 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -74,3 +74,22 @@ To run a subset of tests, filters can be used: ``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``. Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests. + +Whiskers +======== + +*Whiskers* is a templating system similar to `Moustache `_. It is used by the +compiler in various places to aid readability, and thus maintainability and verifiability, of the code. + +The syntax comes with a substantial difference to Moustache: the template markers ``{{`` and ``}}`` are +replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`inline-assembly` +(The symbols ``<`` and ``>`` are invalid in inline assembly, while ``{`` and ``}`` are used to delimit blocks). +Another limitation is that lists are only resolved one depth and they will not recurse. This may change in the future. + +A rough specification is the following: + +Any occurrence of ```` is replaced by the string-value of the supplied variable ``name`` without any +escaping and without iterated replacements. An area can be delimited by ``<#name>...``. It is replaced +by as many concatenations of its contents as there were sets of variables supplied to the template system, +each time replacing any ```` items by their respective value. Top-level variales can also be used +inside such areas. \ No newline at end of file From e58cff3f37602db61ab9e70820367a5aff14f1b8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 22 Jun 2017 15:14:26 +0200 Subject: [PATCH 3/3] Changelog entry for Whiskers. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 0fb2fe5ce..c7b8ac356 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ Features: * Inline Assembly: introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias. * Inline Assembly: ``for`` and ``switch`` statements. * Inline Assembly: function definitions and function calls. + * Code Generator: Added the Whiskers template system. Bugfixes: * Type Checker: Make UTF8-validation a bit more sloppy to include more valid sequences.