Merge pull request #2426 from ethereum/miniMoustache

Whiskers template system
This commit is contained in:
Alex Beregszaszi 2017-06-22 22:17:26 +01:00 committed by GitHub
commit 08a5d144ac
7 changed files with 364 additions and 0 deletions

View File

@ -10,6 +10,7 @@ Features:
* Inline Assembly: introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias. * Inline Assembly: introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias.
* Inline Assembly: ``for`` and ``switch`` statements. * Inline Assembly: ``for`` and ``switch`` statements.
* Inline Assembly: function definitions and function calls. * Inline Assembly: function definitions and function calls.
* Code Generator: Added the Whiskers template system.
Bugfixes: Bugfixes:
* Type Checker: Make UTF8-validation a bit more sloppy to include more valid sequences. * Type Checker: Make UTF8-validation a bit more sloppy to include more valid sequences.

View File

@ -10,6 +10,7 @@ function(eth_apply TARGET REQUIRED SUBMODULE)
target_link_libraries(${TARGET} ${Boost_RANDOM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_RANDOM_LIBRARIES})
target_link_libraries(${TARGET} ${Boost_FILESYSTEM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_FILESYSTEM_LIBRARIES})
target_link_libraries(${TARGET} ${Boost_SYSTEM_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_SYSTEM_LIBRARIES})
target_link_libraries(${TARGET} ${Boost_REGEX_LIBRARIES})
if (DEFINED MSVC) if (DEFINED MSVC)
target_link_libraries(${TARGET} ${Boost_CHRONO_LIBRARIES}) target_link_libraries(${TARGET} ${Boost_CHRONO_LIBRARIES})

View File

@ -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 and the complications that arise when for example using internal functions
of libraries. Furthermore, write about the symbols defined by the compiler. of libraries. Furthermore, write about the symbols defined by the compiler.
.. _inline-assembly:
Inline Assembly Inline Assembly
=============== ===============

View File

@ -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 ``*``. ``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. Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests.
Whiskers
========
*Whiskers* is a templating system similar to `Moustache <https://mustache.github.io>`_. 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 ``<name>`` 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>...</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 ``<inner>`` items by their respective value. Top-level variales can also be used
inside such areas.

127
libdevcore/Whiskers.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file Whiskers.cpp
* @author Chris <chis@ethereum.org>
* @date 2017
*
* Moustache-like templates.
*/
#include <libdevcore/Whiskers.h>
#include <libdevcore/Assertions.h>
#include <boost/regex.hpp>
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<map<string, string>> 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<string, vector<StringMap>> const& _listParameters
)
{
using namespace boost;
static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)</\\2>");
return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _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;
}

87
libdevcore/Whiskers.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file Whiskers.h
* @author Chris <chis@ethereum.org>
* @date 2017
*
* Moustache-like templates.
*/
#pragma once
#include <libdevcore/Exceptions.h>
#include <string>
#include <map>
#include <vector>
namespace dev
{
DEV_SIMPLE_EXCEPTION(WhiskersError);
///
/// Moustache-like templates.
///
/// Usage:
/// std::vector<std::map<std::string, std::string>> listValues(2);
/// listValues[0]["k"] = "key1";
/// listValues[0]["v"] = "value1";
/// listValues[1]["k"] = "key2";
/// listValues[1]["v"] = "value2";
/// auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>")
/// ("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<std::string, std::string>;
using StringListMap = std::map<std::string, std::vector<StringMap>>;
explicit Whiskers(std::string const& _template);
/// Sets a single parameter, <paramName>.
Whiskers& operator()(std::string const& _parameter, std::string const& _value);
/// Sets a list parameter, <#listName> </listName>.
Whiskers& operator()(
std::string const& _listParameter,
std::vector<StringMap> 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;
};
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* Unit tests for the mini moustache class.
*/
#include <libdevcore/Whiskers.h>
#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 <b> x <c> -> <d>.";
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 = "<b>";
Whiskers m(templ);
BOOST_CHECK_THROW(m.render(), WhiskersError);
}
BOOST_AUTO_TEST_CASE(complicated_replacement)
{
string templ = "a <b> x <complicated> \n <nes<ted>>.";
string result = Whiskers(templ)
("b", "BE")
("complicated", "CO<M>PL")
("nes<ted", "NEST")
.render();
BOOST_CHECK_EQUAL(result, "a BE x CO<M>PL \n NEST>.");
}
BOOST_AUTO_TEST_CASE(non_existing_list)
{
string templ = "a <#b></b>";
Whiskers m(templ);
BOOST_CHECK_THROW(m.render(), WhiskersError);
}
BOOST_AUTO_TEST_CASE(empty_list)
{
string templ = "a <#b></b>x";
string result = Whiskers(templ)("b", vector<Whiskers::StringMap>{}).render();
BOOST_CHECK_EQUAL(result, "a x");
}
BOOST_AUTO_TEST_CASE(list)
{
string templ = "a<#b>( <g> - <h> )</b>x";
vector<map<string, string>> 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> 1<g>3 </b><x>";
vector<map<string, string>> list(1);
list[0]["g"] = "<x>";
string result = Whiskers(templ)("x", "X")("b", list).render();
BOOST_CHECK_EQUAL(result, "a 1<x>3 X");
}
BOOST_AUTO_TEST_CASE(list_can_access_upper)
{
string templ = "<#b>(<a>)</b>";
vector<map<string, string>> 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></b>";
vector<map<string, string>> 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()
}
}