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: ``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.

View File

@ -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})

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
of libraries. Furthermore, write about the symbols defined by the compiler.
.. _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 ``*``.
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()
}
}