Adds support for tuples in test file parser.

This commit is contained in:
Erik Kundt 2019-02-05 16:52:19 +01:00
parent 161b22bd13
commit c9c4578023
3 changed files with 258 additions and 102 deletions

View File

@ -31,6 +31,7 @@ using namespace langutil;
using namespace solidity;
using namespace dev::solidity::test;
using namespace std;
using namespace soltest;
namespace
{
@ -47,14 +48,14 @@ namespace
vector<dev::solidity::test::FunctionCall> TestFileParser::parseFunctionCalls()
{
vector<FunctionCall> calls;
if (!accept(SoltToken::EOS))
if (!accept(Token::EOS))
{
assert(m_scanner.currentToken() == SoltToken::Unknown);
assert(m_scanner.currentToken() == Token::Unknown);
m_scanner.scanNextToken();
while (!accept(SoltToken::EOS))
while (!accept(Token::EOS))
{
if (!accept(SoltToken::Whitespace))
if (!accept(Token::Whitespace))
{
FunctionCall call;
@ -64,63 +65,47 @@ vector<dev::solidity::test::FunctionCall> TestFileParser::parseFunctionCalls()
/// token lookahead that checks parseParameter
/// if the next token is an identifier.
if (calls.empty())
expect(SoltToken::Newline);
expect(Token::Newline);
else
accept(SoltToken::Newline, true);
accept(Token::Newline, true);
call.signature = parseFunctionSignature();
if (accept(SoltToken::Comma, true))
if (accept(Token::Comma, true))
call.value = parseFunctionCallValue();
if (accept(SoltToken::Colon, true))
if (accept(Token::Colon, true))
call.arguments = parseFunctionCallArguments();
if (accept(SoltToken::Newline, true))
if (accept(Token::Newline, true))
call.displayMode = FunctionCall::DisplayMode::MultiLine;
call.arguments.comment = parseComment();
if (accept(SoltToken::Newline, true))
if (accept(Token::Newline, true))
call.displayMode = FunctionCall::DisplayMode::MultiLine;
expect(SoltToken::Arrow);
expect(Token::Arrow);
call.expectations = parseFunctionCallExpectations();
call.expectations.comment = parseComment();
calls.emplace_back(std::move(call));
}
else
m_scanner.scanNextToken();
}
}
return calls;
}
string TestFileParser::formatToken(SoltToken _token)
{
switch (_token)
{
#define T(name, string, precedence) case SoltToken::name: return string;
SOLT_TOKEN_LIST(T, T)
#undef T
default: // Token::NUM_TOKENS:
return "";
}
}
bool TestFileParser::accept(SoltToken _token, bool const _expect)
{
if (m_scanner.currentToken() == _token)
{
if (_expect)
expect(_token);
return true;
}
return false;
}
bool TestFileParser::expect(SoltToken _token, bool const _advance)
bool TestFileParser::accept(soltest::Token _token, bool const _expect)
{
if (m_scanner.currentToken() != _token)
return false;
if (_expect)
return expect(_token);
return true;
}
bool TestFileParser::expect(soltest::Token _token, bool const _advance)
{
if (m_scanner.currentToken() != _token || m_scanner.currentToken() == Token::Invalid)
throw Error(
Error::Type::ParserError,
"Unexpected " + formatToken(m_scanner.currentToken()) + ": \"" +
@ -135,32 +120,35 @@ bool TestFileParser::expect(SoltToken _token, bool const _advance)
string TestFileParser::parseFunctionSignature()
{
string signature = m_scanner.currentLiteral();
expect(SoltToken::Identifier);
expect(Token::Identifier);
signature += formatToken(SoltToken::LParen);
expect(SoltToken::LParen);
signature += formatToken(Token::LParen);
expect(Token::LParen);
while (!accept(SoltToken::RParen))
string parameters;
if (!accept(Token::RParen, false))
parameters = parseIdentifierOrTuple();
while (accept(Token::Comma))
{
signature += m_scanner.currentLiteral();
expect(SoltToken::Identifier);
while (accept(SoltToken::Comma))
{
signature += m_scanner.currentLiteral();
expect(SoltToken::Comma);
signature += m_scanner.currentLiteral();
expect(SoltToken::Identifier);
}
parameters += formatToken(Token::Comma);
expect(Token::Comma);
parameters += parseIdentifierOrTuple();
}
signature += formatToken(SoltToken::RParen);
expect(SoltToken::RParen);
if (accept(Token::Arrow, true))
throw Error(Error::Type::ParserError, "Invalid signature detected: " + signature);
signature += parameters;
expect(Token::RParen);
signature += formatToken(Token::RParen);
return signature;
}
u256 TestFileParser::parseFunctionCallValue()
{
u256 value = convertNumber(parseNumber());
expect(SoltToken::Ether);
expect(Token::Ether);
return value;
}
@ -173,7 +161,7 @@ FunctionCallArgs TestFileParser::parseFunctionCallArguments()
throw Error(Error::Type::ParserError, "No argument provided.");
arguments.parameters.emplace_back(param);
while (accept(SoltToken::Comma, true))
while (accept(Token::Comma, true))
arguments.parameters.emplace_back(parseParameter());
return arguments;
}
@ -190,7 +178,7 @@ FunctionCallExpectations TestFileParser::parseFunctionCallExpectations()
}
expectations.result.emplace_back(param);
while (accept(SoltToken::Comma, true))
while (accept(Token::Comma, true))
expectations.result.emplace_back(parseParameter());
/// We have always one virtual parameter in the parameter list.
@ -203,7 +191,7 @@ FunctionCallExpectations TestFileParser::parseFunctionCallExpectations()
Parameter TestFileParser::parseParameter()
{
Parameter parameter;
if (accept(SoltToken::Newline, true))
if (accept(Token::Newline, true))
parameter.format.newline = true;
auto literal = parseABITypeLiteral();
parameter.rawBytes = literal.first;
@ -218,20 +206,20 @@ pair<bytes, ABIType> TestFileParser::parseABITypeLiteral()
u256 number{0};
ABIType abiType{ABIType::None, 0};
if (accept(SoltToken::Sub))
if (accept(Token::Sub))
{
abiType = ABIType{ABIType::SignedDec, 32};
expect(SoltToken::Sub);
expect(Token::Sub);
number = convertNumber(parseNumber()) * -1;
}
else
{
if (accept(SoltToken::Number))
if (accept(Token::Number))
{
abiType = ABIType{ABIType::UnsignedDec, 32};
number = convertNumber(parseNumber());
}
else if (accept(SoltToken::Failure, true))
else if (accept(Token::Failure, true))
{
abiType = ABIType{ABIType::Failure, 0};
return make_pair(bytes{}, abiType);
@ -245,10 +233,35 @@ pair<bytes, ABIType> TestFileParser::parseABITypeLiteral()
}
}
string TestFileParser::parseIdentifierOrTuple()
{
string identOrTuple;
if (accept(Token::Identifier))
{
identOrTuple = m_scanner.currentLiteral();
expect(Token::Identifier);
return identOrTuple;
}
expect(Token::LParen);
identOrTuple += formatToken(Token::LParen);
identOrTuple += parseIdentifierOrTuple();
while (accept(Token::Comma))
{
identOrTuple += formatToken(Token::Comma);
expect(Token::Comma);
identOrTuple += parseIdentifierOrTuple();
}
expect(Token::RParen);
identOrTuple += formatToken(Token::RParen);
return identOrTuple;
}
string TestFileParser::parseComment()
{
string comment = m_scanner.currentLiteral();
if (accept(SoltToken::Comment, true))
if (accept(Token::Comment, true))
return comment;
return string{};
}
@ -256,7 +269,7 @@ string TestFileParser::parseComment()
string TestFileParser::parseNumber()
{
string literal = m_scanner.currentLiteral();
expect(SoltToken::Number);
expect(Token::Number);
return literal;
}
@ -281,18 +294,21 @@ void TestFileParser::Scanner::readStream(istream& _stream)
void TestFileParser::Scanner::scanNextToken()
{
auto detectToken = [](std::string const& _literal = "") -> TokenDesc {
if (_literal == "ether") return TokenDesc{SoltToken::Ether, _literal};
if (_literal == "FAILURE") return TokenDesc{SoltToken::Failure, _literal};
return TokenDesc{SoltToken::Identifier, _literal};
// Make code coverage happy.
assert(formatToken(Token::NUM_TOKENS) == "");
auto detectKeyword = [](std::string const& _literal = "") -> TokenDesc {
if (_literal == "ether") return TokenDesc{Token::Ether, _literal};
if (_literal == "FAILURE") return TokenDesc{Token::Failure, _literal};
return TokenDesc{Token::Identifier, _literal};
};
auto selectToken = [this](SoltToken _token, std::string const& _literal = "") -> TokenDesc {
auto selectToken = [this](Token _token, std::string const& _literal = "") -> TokenDesc {
advance();
return make_pair(_token, !_literal.empty() ? _literal : formatToken(_token));
};
TokenDesc token = make_pair(SoltToken::Unknown, "");
TokenDesc token = make_pair(Token::Unknown, "");
do
{
switch(current())
@ -300,50 +316,50 @@ void TestFileParser::Scanner::scanNextToken()
case '/':
advance();
if (current() == '/')
token = selectToken(SoltToken::Newline);
token = selectToken(Token::Newline);
else
token = selectToken(Token::Invalid);
break;
case '-':
if (peek() == '>')
{
advance();
token = selectToken(SoltToken::Arrow);
token = selectToken(Token::Arrow);
}
else
token = selectToken(SoltToken::Sub);
token = selectToken(Token::Sub);
break;
case ':':
token = selectToken(SoltToken::Colon);
token = selectToken(Token::Colon);
break;
case '#':
token = selectToken(SoltToken::Comment, scanComment());
token = selectToken(Token::Comment, scanComment());
break;
case ',':
token = selectToken(SoltToken::Comma);
token = selectToken(Token::Comma);
break;
case '(':
token = selectToken(SoltToken::LParen);
token = selectToken(Token::LParen);
break;
case ')':
token = selectToken(SoltToken::RParen);
token = selectToken(Token::RParen);
break;
default:
if (isIdentifierStart(current()))
{
TokenDesc detectedToken = detectToken(scanIdentifierOrKeyword());
TokenDesc detectedToken = detectKeyword(scanIdentifierOrKeyword());
token = selectToken(detectedToken.first, detectedToken.second);
}
else if (isdigit(current()))
token = selectToken(SoltToken::Number, scanNumber());
token = selectToken(Token::Number, scanNumber());
else if (isspace(current()))
token = selectToken(SoltToken::Whitespace);
token = selectToken(Token::Whitespace);
else if (isEndOfLine())
token = selectToken(SoltToken::EOS);
else
token = selectToken(SoltToken::Invalid);
token = selectToken(Token::EOS);
break;
}
}
while (token.first == SoltToken::Whitespace);
while (token.first == Token::Whitespace);
m_currentToken = token;
}

View File

@ -33,7 +33,7 @@ namespace test
{
/**
* All SOLT (or SOLTest) tokens.
* All soltest tokens.
*/
#define SOLT_TOKEN_LIST(T, K) \
T(Unknown, "unknown", 0) \
@ -62,12 +62,30 @@ namespace test
/* special keywords */ \
K(Failure, "FAILURE", 0) \
enum class SoltToken : unsigned int {
#define T(name, string, precedence) name,
SOLT_TOKEN_LIST(T, T)
NUM_TOKENS
#undef T
};
namespace soltest
{
enum class Token : unsigned int {
#define T(name, string, precedence) name,
SOLT_TOKEN_LIST(T, T)
NUM_TOKENS
#undef T
};
/// Prints a friendly string representation of \param _token.
inline std::string formatToken(Token _token)
{
switch (_token)
{
#define T(name, string, precedence) case Token::name: return string;
SOLT_TOKEN_LIST(T, T)
#undef T
default: // Token::NUM_TOKENS:
return "";
}
}
}
/**
* The purpose of the ABI type is the storage of type information
@ -92,7 +110,7 @@ struct ABIType
/**
* Helper that can hold format information retrieved
* while scanning through a parameter list in sol_t.
* while scanning through a parameter list in soltest.
*/
struct FormatInfo
{
@ -133,7 +151,7 @@ using ParameterList = std::vector<Parameter>;
*/
struct FunctionCallExpectations
{
/// Representation of the comma-separated (or empty) list of expectated result values
/// Representation of the comma-separated (or empty) list of expected result values
/// attached to the function call object. It is checked against the actual result of
/// a function call when used in test framework.
ParameterList result;
@ -237,10 +255,8 @@ public:
/// of its arguments or expected results.
std::vector<FunctionCall> parseFunctionCalls();
/// Prints a friendly string representation of \param _token.
static std::string formatToken(SoltToken _token);
private:
using Token = soltest::Token;
/**
* Token scanner that is used internally to abstract away character traversal.
*/
@ -257,7 +273,7 @@ private:
/// Reads character stream and creates token.
void scanNextToken();
SoltToken currentToken() { return m_currentToken.first; }
soltest::Token currentToken() { return m_currentToken.first; }
std::string currentLiteral() { return m_currentToken.second; }
std::string scanComment();
@ -265,7 +281,7 @@ private:
std::string scanNumber();
private:
using TokenDesc = std::pair<SoltToken, std::string>;
using TokenDesc = std::pair<Token, std::string>;
/// Advances current position in the input stream.
void advance() { ++m_char; }
@ -284,8 +300,8 @@ private:
TokenDesc m_currentToken;
};
bool accept(SoltToken _token, bool const _expect = false);
bool expect(SoltToken _token, bool const _advance = true);
bool accept(soltest::Token _token, bool const _expect = false);
bool expect(soltest::Token _token, bool const _advance = true);
/// Parses a function call signature in the form of f(uint256, ...).
std::string parseFunctionSignature();
@ -319,7 +335,12 @@ private:
/// if data type is not supported.
std::pair<bytes, ABIType> parseABITypeLiteral();
/// Parses a comment
/// Recursively parses an identifier or a tuple definition that contains identifiers
/// and / or parentheses like `((uint, uint), (uint, (uint, uint)), uint)`.
std::string parseIdentifierOrTuple();
/// Parses a comment that is defined like this:
/// # A nice comment. #
std::string parseComment();
/// Parses the current number literal.

View File

@ -265,6 +265,61 @@ BOOST_AUTO_TEST_CASE(call_arguments)
);
}
BOOST_AUTO_TEST_CASE(call_arguments_tuple)
{
char const* source = R"(
// f((uint256, bytes32), uint256) ->
// f((uint8), uint8) ->
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 2);
testFunctionCall(calls.at(0), Mode::SingleLine, "f((uint256,bytes32),uint256)", false);
testFunctionCall(calls.at(1), Mode::SingleLine, "f((uint8),uint8)", false);
}
BOOST_AUTO_TEST_CASE(call_arguments_tuple_of_tuples)
{
char const* source = R"(
// f(((uint256, bytes32), bytes32), uint256)
// # f(S memory s, uint256 b) #
// ->
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 1);
testFunctionCall(
calls.at(0),
Mode::MultiLine,
"f(((uint256,bytes32),bytes32),uint256)",
false,
fmt::encodeArgs(),
fmt::encodeArgs(),
0,
" f(S memory s, uint256 b) "
);
}
BOOST_AUTO_TEST_CASE(call_arguments_recursive_tuples)
{
char const* source = R"(
// f(((((bytes, bytes, bytes), bytes), bytes), bytes), bytes) ->
// f(((((bytes, bytes, (bytes)), bytes), bytes), (bytes, bytes)), (bytes, bytes)) ->
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 2);
testFunctionCall(
calls.at(0),
Mode::SingleLine,
"f(((((bytes,bytes,bytes),bytes),bytes),bytes),bytes)",
false
);
testFunctionCall(
calls.at(1),
Mode::SingleLine,
"f(((((bytes,bytes,(bytes)),bytes),bytes),(bytes,bytes)),(bytes,bytes))",
false
);
}
BOOST_AUTO_TEST_CASE(call_arguments_mismatch)
{
char const* source = R"(
@ -333,18 +388,58 @@ BOOST_AUTO_TEST_CASE(call_signature)
char const* source = R"(
// f(uint256, uint8, string) -> FAILURE
// f(invalid, xyz, foo) -> FAILURE
)";
)";
auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 2);
testFunctionCall(calls.at(0), Mode::SingleLine, "f(uint256,uint8,string)", true);
testFunctionCall(calls.at(1), Mode::SingleLine, "f(invalid,xyz,foo)", true);
}
BOOST_AUTO_TEST_CASE(call_newline_invalid)
{
char const* source = R"(
/
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_invalid)
{
char const* source = R"(
/ f() ->
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_signature_invalid)
{
char const* source = R"(
// f(uint8,) -> FAILURE
)";
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_arguments_tuple_invalid)
{
char const* source = R"(
// f((uint8,) -> FAILURE
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_arguments_tuple_invalid_empty)
{
char const* source = R"(
// f(uint8, ()) -> FAILURE
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_arguments_tuple_invalid_parantheses)
{
char const* source = R"(
// f((uint8,() -> FAILURE
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
@ -370,6 +465,14 @@ BOOST_AUTO_TEST_CASE(call_arguments_invalid)
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_arguments_invalid_decimal)
{
char const* source = R"(
// sig(): 0.h3 ->
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_ether_value_invalid)
{
char const* source = R"(
@ -378,6 +481,14 @@ BOOST_AUTO_TEST_CASE(call_ether_value_invalid)
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_ether_value_invalid_decimal)
{
char const* source = R"(
// sig(): 0.1hd ether ->
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_ether_type_invalid)
{
char const* source = R"(
@ -405,6 +516,14 @@ BOOST_AUTO_TEST_CASE(call_arguments_newline_colon)
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_CASE(call_arrow_missing)
{
char const* source = R"(
// h256()
)";
BOOST_REQUIRE_THROW(parse(source), langutil::Error);
}
BOOST_AUTO_TEST_SUITE_END()
}