Skip to content

Commit

Permalink
Merge pull request ethereum#2224 from ethereum/julia-switch
Browse files Browse the repository at this point in the history
Implement switch statement in the assembly parser/printer
  • Loading branch information
chriseth authored May 26, 2017
2 parents 7126aad + 05fcf19 commit ec676ba
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 2 deletions.
42 changes: 42 additions & 0 deletions libsolidity/inlineasm/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,48 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
return success;
}

bool AsmAnalyzer::operator()(Switch const& _switch)
{
bool success = true;

int const initialStackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, *_switch.expression))
success = false;
expectDeposit(1, initialStackHeight, locationOf(*_switch.expression));

set<tuple<LiteralKind, string>> cases;
for (auto const& _case: _switch.cases)
{
if (_case.value)
{
int const initialStackHeight = m_stackHeight;
if (!(*this)(*_case.value))
success = false;
expectDeposit(1, initialStackHeight, _case.value->location);
m_stackHeight--;

/// Note: the parser ensures there is only one default case
auto val = make_tuple(_case.value->kind, _case.value->value);
if (!cases.insert(val).second)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Duplicate case defined",
_case.location
));
success = false;
}
}

if (!(*this)(_case.body))
success = false;
}

m_stackHeight--;

return success;
}

bool AsmAnalyzer::operator()(Block const& _block)
{
bool success = true;
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/inlineasm/AsmAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct Identifier;
struct StackAssignment;
struct FunctionDefinition;
struct FunctionCall;
struct Switch;

struct Scope;

Expand Down Expand Up @@ -78,6 +79,7 @@ class AsmAnalyzer: public boost::static_visitor<bool>
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const& _functionCall);
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::Block const& _block);

private:
Expand Down
3 changes: 2 additions & 1 deletion libsolidity/inlineasm/AsmAnalysisInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ struct Identifier;
struct StackAssignment;
struct FunctionDefinition;
struct FunctionCall;
struct Switch;

struct Scope;

using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>;
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>;

struct AsmAnalysisInfo
{
Expand Down
4 changes: 4 additions & 0 deletions libsolidity/inlineasm/AsmCodeGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ class CodeTransform: public boost::static_visitor<>
CodeTransform(m_state, m_assembly, _block, m_identifierAccess, m_initialStackHeight);
checkStackHeight(&_block);
}
void operator()(assembly::Switch const&)
{
solAssert(false, "Switch not removed during desugaring phase.");
}
void operator()(assembly::FunctionDefinition const&)
{
solAssert(false, "Function definition not removed during desugaring phase.");
Expand Down
7 changes: 6 additions & 1 deletion libsolidity/inlineasm/AsmData.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ struct VariableDeclaration;
struct FunctionalInstruction;
struct FunctionDefinition;
struct FunctionCall;
struct Switch;
struct Block;

using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>;
using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, Block>;

/// Direct EVM instruction (except PUSHi and JUMPDEST)
struct Instruction { SourceLocation location; solidity::Instruction instruction; };
Expand All @@ -77,6 +78,10 @@ struct VariableDeclaration { SourceLocation location; TypedNameList variables; s
struct Block { SourceLocation location; std::vector<Statement> statements; };
/// Function definition ("function f(a, b) -> (d, e) { ... }")
struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; };
/// Switch case or default case
struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; };
/// Switch statement
struct Switch { SourceLocation location; std::shared_ptr<Statement> expression; std::vector<Case> cases; };

struct LocationExtractor: boost::static_visitor<SourceLocation>
{
Expand Down
40 changes: 40 additions & 0 deletions libsolidity/inlineasm/AsmParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ assembly::Statement Parser::parseStatement()
return parseFunctionDefinition();
case Token::LBrace:
return parseBlock();
case Token::Switch:
{
assembly::Switch _switch = createWithLocation<assembly::Switch>();
m_scanner->next();
_switch.expression = make_shared<Statement>(parseExpression());
if (_switch.expression->type() == typeid(assembly::Instruction))
fatalParserError("Instructions are not supported as expressions for switch.");
while (m_scanner->currentToken() == Token::Case)
_switch.cases.emplace_back(parseCase());
if (m_scanner->currentToken() == Token::Default)
_switch.cases.emplace_back(parseCase());
if (m_scanner->currentToken() == Token::Default)
fatalParserError("Only one default case allowed.");
else if (m_scanner->currentToken() == Token::Case)
fatalParserError("Case not allowed after default case.");
if (_switch.cases.size() == 0)
fatalParserError("Switch statement without any cases.");
_switch.location.end = _switch.cases.back().body.location.end;
return _switch;
}
case Token::Assign:
{
if (m_julia)
Expand Down Expand Up @@ -134,6 +154,26 @@ assembly::Statement Parser::parseStatement()
return statement;
}

assembly::Case Parser::parseCase()
{
assembly::Case _case = createWithLocation<assembly::Case>();
if (m_scanner->currentToken() == Token::Default)
m_scanner->next();
else if (m_scanner->currentToken() == Token::Case)
{
m_scanner->next();
assembly::Statement statement = parseElementaryOperation();
if (statement.type() != typeid(assembly::Literal))
fatalParserError("Literal expected.");
_case.value = make_shared<Literal>(std::move(boost::get<assembly::Literal>(statement)));
}
else
fatalParserError("Case or default case expected.");
_case.body = parseBlock();
_case.location.end = _case.body.location.end;
return _case;
}

assembly::Statement Parser::parseExpression()
{
Statement operation = parseElementaryOperation(true);
Expand Down
1 change: 1 addition & 0 deletions libsolidity/inlineasm/AsmParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class Parser: public ParserBase

Block parseBlock();
Statement parseStatement();
Case parseCase();
/// Parses a functional expression that has to push exactly one stack element
Statement parseExpression();
std::map<std::string, dev::solidity::Instruction> const& instructions();
Expand Down
14 changes: 14 additions & 0 deletions libsolidity/inlineasm/AsmPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@ string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall)
")";
}

string AsmPrinter::operator()(Switch const& _switch)
{
string out = "switch " + boost::apply_visitor(*this, *_switch.expression);
for (auto const& _case: _switch.cases)
{
if (!_case.value)
out += "\ndefault ";
else
out += "\ncase " + (*this)(*_case.value) + " ";
out += (*this)(_case.body);
}
return out;
}

string AsmPrinter::operator()(Block const& _block)
{
if (_block.statements.empty())
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/inlineasm/AsmPrinter.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct Assignment;
struct VariableDeclaration;
struct FunctionDefinition;
struct FunctionCall;
struct Switch;
struct Block;

class AsmPrinter: public boost::static_visitor<std::string>
Expand All @@ -57,6 +58,7 @@ class AsmPrinter: public boost::static_visitor<std::string>
std::string operator()(assembly::VariableDeclaration const& _variableDeclaration);
std::string operator()(assembly::FunctionDefinition const& _functionDefinition);
std::string operator()(assembly::FunctionCall const& _functionCall);
std::string operator()(assembly::Switch const& _switch);
std::string operator()(assembly::Block const& _block);

private:
Expand Down
9 changes: 9 additions & 0 deletions libsolidity/inlineasm/AsmScopeFiller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
return success;
}

bool ScopeFiller::operator()(Switch const& _switch)
{
bool success = true;
for (auto const& _case: _switch.cases)
if (!(*this)(_case.body))
success = false;
return success;
}

bool ScopeFiller::operator()(Block const& _block)
{
bool success = true;
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/inlineasm/AsmScopeFiller.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ struct Identifier;
struct StackAssignment;
struct FunctionDefinition;
struct FunctionCall;
struct Switch;

struct Scope;

Expand All @@ -69,6 +70,7 @@ class ScopeFiller: public boost::static_visitor<bool>
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const&) { return true; }
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::Block const& _block);

private:
Expand Down
52 changes: 52 additions & 0 deletions test/libsolidity/InlineAssembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,53 @@ BOOST_AUTO_TEST_CASE(variable_use_before_decl)
CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared.");
}

BOOST_AUTO_TEST_CASE(switch_statement)
{
BOOST_CHECK(successParse("{ switch 42 default {} }"));
BOOST_CHECK(successParse("{ switch 42 case 1 {} }"));
BOOST_CHECK(successParse("{ switch 42 case 1 {} case 2 {} }"));
BOOST_CHECK(successParse("{ switch 42 case 1 {} default {} }"));
BOOST_CHECK(successParse("{ switch 42 case 1 {} case 2 {} default {} }"));
BOOST_CHECK(successParse("{ switch mul(1, 2) case 1 {} case 2 {} default {} }"));
BOOST_CHECK(successParse("{ function f() -> x {} switch f() case 1 {} case 2 {} default {} }"));
}

BOOST_AUTO_TEST_CASE(switch_no_cases)
{
CHECK_PARSE_ERROR("{ switch 42 }", ParserError, "Switch statement without any cases.");
}

BOOST_AUTO_TEST_CASE(switch_duplicate_case)
{
CHECK_PARSE_ERROR("{ switch 42 case 1 {} case 1 {} default {} }", DeclarationError, "Duplicate case defined");
}

BOOST_AUTO_TEST_CASE(switch_invalid_expression)
{
CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Expected elementary inline assembly operation.");
CHECK_PARSE_ERROR("{ 1 2 switch mul default {} }", ParserError, "Instructions are not supported as expressions for switch.");
}

BOOST_AUTO_TEST_CASE(switch_default_before_case)
{
CHECK_PARSE_ERROR("{ switch 42 default {} case 1 {} }", ParserError, "Case not allowed after default case.");
}

BOOST_AUTO_TEST_CASE(switch_duplicate_default_case)
{
CHECK_PARSE_ERROR("{ switch 42 default {} default {} }", ParserError, "Only one default case allowed.");
}

BOOST_AUTO_TEST_CASE(switch_invalid_case)
{
CHECK_PARSE_ERROR("{ switch 42 case mul(1, 2) {} case 2 {} default {} }", ParserError, "Literal expected.");
}

BOOST_AUTO_TEST_CASE(switch_invalid_body)
{
CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected token LBrace got 'Identifier'");
}

BOOST_AUTO_TEST_CASE(blocks)
{
BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }"));
Expand Down Expand Up @@ -336,6 +383,11 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
parsePrintCompare(parsed);
}

BOOST_AUTO_TEST_CASE(print_switch)
{
parsePrintCompare("{\n switch 42\n case 1 {\n }\n case 2 {\n }\n default {\n }\n}");
}

BOOST_AUTO_TEST_CASE(function_definitions_multiple_args)
{
parsePrintCompare("{\n function f(a, d)\n {\n mstore(a, d)\n }\n function g(a, d) -> x, y\n {\n }\n}");
Expand Down

0 comments on commit ec676ba

Please sign in to comment.