mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #2426 from ethereum/miniMoustache
Whiskers template system
This commit is contained in:
commit
08a5d144ac
@ -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.
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
===============
|
||||
|
||||
|
@ -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
127
libdevcore/Whiskers.cpp
Normal 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
87
libdevcore/Whiskers.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
127
test/libdevcore/MiniMoustache.cpp
Normal file
127
test/libdevcore/MiniMoustache.cpp
Normal 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()
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user