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 solidity;
using namespace dev::solidity::test; using namespace dev::solidity::test;
using namespace std; using namespace std;
using namespace soltest;
namespace namespace
{ {
@ -47,14 +48,14 @@ namespace
vector<dev::solidity::test::FunctionCall> TestFileParser::parseFunctionCalls() vector<dev::solidity::test::FunctionCall> TestFileParser::parseFunctionCalls()
{ {
vector<FunctionCall> calls; 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(); m_scanner.scanNextToken();
while (!accept(SoltToken::EOS)) while (!accept(Token::EOS))
{ {
if (!accept(SoltToken::Whitespace)) if (!accept(Token::Whitespace))
{ {
FunctionCall call; FunctionCall call;
@ -64,63 +65,47 @@ vector<dev::solidity::test::FunctionCall> TestFileParser::parseFunctionCalls()
/// token lookahead that checks parseParameter /// token lookahead that checks parseParameter
/// if the next token is an identifier. /// if the next token is an identifier.
if (calls.empty()) if (calls.empty())
expect(SoltToken::Newline); expect(Token::Newline);
else else
accept(SoltToken::Newline, true); accept(Token::Newline, true);
call.signature = parseFunctionSignature(); call.signature = parseFunctionSignature();
if (accept(SoltToken::Comma, true)) if (accept(Token::Comma, true))
call.value = parseFunctionCallValue(); call.value = parseFunctionCallValue();
if (accept(SoltToken::Colon, true)) if (accept(Token::Colon, true))
call.arguments = parseFunctionCallArguments(); call.arguments = parseFunctionCallArguments();
if (accept(SoltToken::Newline, true)) if (accept(Token::Newline, true))
call.displayMode = FunctionCall::DisplayMode::MultiLine; call.displayMode = FunctionCall::DisplayMode::MultiLine;
call.arguments.comment = parseComment(); call.arguments.comment = parseComment();
if (accept(SoltToken::Newline, true)) if (accept(Token::Newline, true))
call.displayMode = FunctionCall::DisplayMode::MultiLine; call.displayMode = FunctionCall::DisplayMode::MultiLine;
expect(SoltToken::Arrow); expect(Token::Arrow);
call.expectations = parseFunctionCallExpectations(); call.expectations = parseFunctionCallExpectations();
call.expectations.comment = parseComment(); call.expectations.comment = parseComment();
calls.emplace_back(std::move(call)); calls.emplace_back(std::move(call));
} }
else
m_scanner.scanNextToken();
} }
} }
return calls; return calls;
} }
string TestFileParser::formatToken(SoltToken _token) bool TestFileParser::accept(soltest::Token _token, bool const _expect)
{
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)
{ {
if (m_scanner.currentToken() != _token) 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( throw Error(
Error::Type::ParserError, Error::Type::ParserError,
"Unexpected " + formatToken(m_scanner.currentToken()) + ": \"" + "Unexpected " + formatToken(m_scanner.currentToken()) + ": \"" +
@ -135,32 +120,35 @@ bool TestFileParser::expect(SoltToken _token, bool const _advance)
string TestFileParser::parseFunctionSignature() string TestFileParser::parseFunctionSignature()
{ {
string signature = m_scanner.currentLiteral(); string signature = m_scanner.currentLiteral();
expect(SoltToken::Identifier); expect(Token::Identifier);
signature += formatToken(SoltToken::LParen); signature += formatToken(Token::LParen);
expect(SoltToken::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(); parameters += formatToken(Token::Comma);
expect(SoltToken::Identifier); expect(Token::Comma);
while (accept(SoltToken::Comma)) parameters += parseIdentifierOrTuple();
{
signature += m_scanner.currentLiteral();
expect(SoltToken::Comma);
signature += m_scanner.currentLiteral();
expect(SoltToken::Identifier);
}
} }
signature += formatToken(SoltToken::RParen); if (accept(Token::Arrow, true))
expect(SoltToken::RParen); throw Error(Error::Type::ParserError, "Invalid signature detected: " + signature);
signature += parameters;
expect(Token::RParen);
signature += formatToken(Token::RParen);
return signature; return signature;
} }
u256 TestFileParser::parseFunctionCallValue() u256 TestFileParser::parseFunctionCallValue()
{ {
u256 value = convertNumber(parseNumber()); u256 value = convertNumber(parseNumber());
expect(SoltToken::Ether); expect(Token::Ether);
return value; return value;
} }
@ -173,7 +161,7 @@ FunctionCallArgs TestFileParser::parseFunctionCallArguments()
throw Error(Error::Type::ParserError, "No argument provided."); throw Error(Error::Type::ParserError, "No argument provided.");
arguments.parameters.emplace_back(param); arguments.parameters.emplace_back(param);
while (accept(SoltToken::Comma, true)) while (accept(Token::Comma, true))
arguments.parameters.emplace_back(parseParameter()); arguments.parameters.emplace_back(parseParameter());
return arguments; return arguments;
} }
@ -190,7 +178,7 @@ FunctionCallExpectations TestFileParser::parseFunctionCallExpectations()
} }
expectations.result.emplace_back(param); expectations.result.emplace_back(param);
while (accept(SoltToken::Comma, true)) while (accept(Token::Comma, true))
expectations.result.emplace_back(parseParameter()); expectations.result.emplace_back(parseParameter());
/// We have always one virtual parameter in the parameter list. /// We have always one virtual parameter in the parameter list.
@ -203,7 +191,7 @@ FunctionCallExpectations TestFileParser::parseFunctionCallExpectations()
Parameter TestFileParser::parseParameter() Parameter TestFileParser::parseParameter()
{ {
Parameter parameter; Parameter parameter;
if (accept(SoltToken::Newline, true)) if (accept(Token::Newline, true))
parameter.format.newline = true; parameter.format.newline = true;
auto literal = parseABITypeLiteral(); auto literal = parseABITypeLiteral();
parameter.rawBytes = literal.first; parameter.rawBytes = literal.first;
@ -218,20 +206,20 @@ pair<bytes, ABIType> TestFileParser::parseABITypeLiteral()
u256 number{0}; u256 number{0};
ABIType abiType{ABIType::None, 0}; ABIType abiType{ABIType::None, 0};
if (accept(SoltToken::Sub)) if (accept(Token::Sub))
{ {
abiType = ABIType{ABIType::SignedDec, 32}; abiType = ABIType{ABIType::SignedDec, 32};
expect(SoltToken::Sub); expect(Token::Sub);
number = convertNumber(parseNumber()) * -1; number = convertNumber(parseNumber()) * -1;
} }
else else
{ {
if (accept(SoltToken::Number)) if (accept(Token::Number))
{ {
abiType = ABIType{ABIType::UnsignedDec, 32}; abiType = ABIType{ABIType::UnsignedDec, 32};
number = convertNumber(parseNumber()); number = convertNumber(parseNumber());
} }
else if (accept(SoltToken::Failure, true)) else if (accept(Token::Failure, true))
{ {
abiType = ABIType{ABIType::Failure, 0}; abiType = ABIType{ABIType::Failure, 0};
return make_pair(bytes{}, abiType); 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 TestFileParser::parseComment()
{ {
string comment = m_scanner.currentLiteral(); string comment = m_scanner.currentLiteral();
if (accept(SoltToken::Comment, true)) if (accept(Token::Comment, true))
return comment; return comment;
return string{}; return string{};
} }
@ -256,7 +269,7 @@ string TestFileParser::parseComment()
string TestFileParser::parseNumber() string TestFileParser::parseNumber()
{ {
string literal = m_scanner.currentLiteral(); string literal = m_scanner.currentLiteral();
expect(SoltToken::Number); expect(Token::Number);
return literal; return literal;
} }
@ -281,18 +294,21 @@ void TestFileParser::Scanner::readStream(istream& _stream)
void TestFileParser::Scanner::scanNextToken() void TestFileParser::Scanner::scanNextToken()
{ {
auto detectToken = [](std::string const& _literal = "") -> TokenDesc { // Make code coverage happy.
if (_literal == "ether") return TokenDesc{SoltToken::Ether, _literal}; assert(formatToken(Token::NUM_TOKENS) == "");
if (_literal == "FAILURE") return TokenDesc{SoltToken::Failure, _literal};
return TokenDesc{SoltToken::Identifier, _literal}; 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(); advance();
return make_pair(_token, !_literal.empty() ? _literal : formatToken(_token)); return make_pair(_token, !_literal.empty() ? _literal : formatToken(_token));
}; };
TokenDesc token = make_pair(SoltToken::Unknown, ""); TokenDesc token = make_pair(Token::Unknown, "");
do do
{ {
switch(current()) switch(current())
@ -300,50 +316,50 @@ void TestFileParser::Scanner::scanNextToken()
case '/': case '/':
advance(); advance();
if (current() == '/') if (current() == '/')
token = selectToken(SoltToken::Newline); token = selectToken(Token::Newline);
else
token = selectToken(Token::Invalid);
break; break;
case '-': case '-':
if (peek() == '>') if (peek() == '>')
{ {
advance(); advance();
token = selectToken(SoltToken::Arrow); token = selectToken(Token::Arrow);
} }
else else
token = selectToken(SoltToken::Sub); token = selectToken(Token::Sub);
break; break;
case ':': case ':':
token = selectToken(SoltToken::Colon); token = selectToken(Token::Colon);
break; break;
case '#': case '#':
token = selectToken(SoltToken::Comment, scanComment()); token = selectToken(Token::Comment, scanComment());
break; break;
case ',': case ',':
token = selectToken(SoltToken::Comma); token = selectToken(Token::Comma);
break; break;
case '(': case '(':
token = selectToken(SoltToken::LParen); token = selectToken(Token::LParen);
break; break;
case ')': case ')':
token = selectToken(SoltToken::RParen); token = selectToken(Token::RParen);
break; break;
default: default:
if (isIdentifierStart(current())) if (isIdentifierStart(current()))
{ {
TokenDesc detectedToken = detectToken(scanIdentifierOrKeyword()); TokenDesc detectedToken = detectKeyword(scanIdentifierOrKeyword());
token = selectToken(detectedToken.first, detectedToken.second); token = selectToken(detectedToken.first, detectedToken.second);
} }
else if (isdigit(current())) else if (isdigit(current()))
token = selectToken(SoltToken::Number, scanNumber()); token = selectToken(Token::Number, scanNumber());
else if (isspace(current())) else if (isspace(current()))
token = selectToken(SoltToken::Whitespace); token = selectToken(Token::Whitespace);
else if (isEndOfLine()) else if (isEndOfLine())
token = selectToken(SoltToken::EOS); token = selectToken(Token::EOS);
else
token = selectToken(SoltToken::Invalid);
break; break;
} }
} }
while (token.first == SoltToken::Whitespace); while (token.first == Token::Whitespace);
m_currentToken = token; 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) \ #define SOLT_TOKEN_LIST(T, K) \
T(Unknown, "unknown", 0) \ T(Unknown, "unknown", 0) \
@ -62,12 +62,30 @@ namespace test
/* special keywords */ \ /* special keywords */ \
K(Failure, "FAILURE", 0) \ K(Failure, "FAILURE", 0) \
enum class SoltToken : unsigned int { namespace soltest
#define T(name, string, precedence) name, {
SOLT_TOKEN_LIST(T, T) enum class Token : unsigned int {
NUM_TOKENS #define T(name, string, precedence) name,
#undef T 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 * 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 * 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 struct FormatInfo
{ {
@ -133,7 +151,7 @@ using ParameterList = std::vector<Parameter>;
*/ */
struct FunctionCallExpectations 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 /// attached to the function call object. It is checked against the actual result of
/// a function call when used in test framework. /// a function call when used in test framework.
ParameterList result; ParameterList result;
@ -237,10 +255,8 @@ public:
/// of its arguments or expected results. /// of its arguments or expected results.
std::vector<FunctionCall> parseFunctionCalls(); std::vector<FunctionCall> parseFunctionCalls();
/// Prints a friendly string representation of \param _token.
static std::string formatToken(SoltToken _token);
private: private:
using Token = soltest::Token;
/** /**
* Token scanner that is used internally to abstract away character traversal. * Token scanner that is used internally to abstract away character traversal.
*/ */
@ -257,7 +273,7 @@ private:
/// Reads character stream and creates token. /// Reads character stream and creates token.
void scanNextToken(); void scanNextToken();
SoltToken currentToken() { return m_currentToken.first; } soltest::Token currentToken() { return m_currentToken.first; }
std::string currentLiteral() { return m_currentToken.second; } std::string currentLiteral() { return m_currentToken.second; }
std::string scanComment(); std::string scanComment();
@ -265,7 +281,7 @@ private:
std::string scanNumber(); std::string scanNumber();
private: private:
using TokenDesc = std::pair<SoltToken, std::string>; using TokenDesc = std::pair<Token, std::string>;
/// Advances current position in the input stream. /// Advances current position in the input stream.
void advance() { ++m_char; } void advance() { ++m_char; }
@ -284,8 +300,8 @@ private:
TokenDesc m_currentToken; TokenDesc m_currentToken;
}; };
bool accept(SoltToken _token, bool const _expect = false); bool accept(soltest::Token _token, bool const _expect = false);
bool expect(SoltToken _token, bool const _advance = true); bool expect(soltest::Token _token, bool const _advance = true);
/// Parses a function call signature in the form of f(uint256, ...). /// Parses a function call signature in the form of f(uint256, ...).
std::string parseFunctionSignature(); std::string parseFunctionSignature();
@ -319,7 +335,12 @@ private:
/// if data type is not supported. /// if data type is not supported.
std::pair<bytes, ABIType> parseABITypeLiteral(); 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(); std::string parseComment();
/// Parses the current number literal. /// 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) BOOST_AUTO_TEST_CASE(call_arguments_mismatch)
{ {
char const* source = R"( char const* source = R"(
@ -333,18 +388,58 @@ BOOST_AUTO_TEST_CASE(call_signature)
char const* source = R"( char const* source = R"(
// f(uint256, uint8, string) -> FAILURE // f(uint256, uint8, string) -> FAILURE
// f(invalid, xyz, foo) -> FAILURE // f(invalid, xyz, foo) -> FAILURE
)"; )";
auto const calls = parse(source); auto const calls = parse(source);
BOOST_REQUIRE_EQUAL(calls.size(), 2); BOOST_REQUIRE_EQUAL(calls.size(), 2);
testFunctionCall(calls.at(0), Mode::SingleLine, "f(uint256,uint8,string)", true); testFunctionCall(calls.at(0), Mode::SingleLine, "f(uint256,uint8,string)", true);
testFunctionCall(calls.at(1), Mode::SingleLine, "f(invalid,xyz,foo)", 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) BOOST_AUTO_TEST_CASE(call_signature_invalid)
{ {
char const* source = R"( char const* source = R"(
// f(uint8,) -> FAILURE // 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); 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_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) BOOST_AUTO_TEST_CASE(call_ether_value_invalid)
{ {
char const* source = R"( char const* source = R"(
@ -378,6 +481,14 @@ BOOST_AUTO_TEST_CASE(call_ether_value_invalid)
BOOST_REQUIRE_THROW(parse(source), langutil::Error); 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) BOOST_AUTO_TEST_CASE(call_ether_type_invalid)
{ {
char const* source = R"( char const* source = R"(
@ -405,6 +516,14 @@ BOOST_AUTO_TEST_CASE(call_arguments_newline_colon)
BOOST_REQUIRE_THROW(parse(source), langutil::Error); 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() BOOST_AUTO_TEST_SUITE_END()
} }