mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #2224 from ethereum/julia-switch
Implement switch statement in the assembly parser/printer
This commit is contained in:
		
						commit
						ec676ba9f2
					
				| @ -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; | ||||
|  | ||||
| @ -47,6 +47,7 @@ struct Identifier; | ||||
| struct StackAssignment; | ||||
| struct FunctionDefinition; | ||||
| struct FunctionCall; | ||||
| struct Switch; | ||||
| 
 | ||||
| struct Scope; | ||||
| 
 | ||||
| @ -78,6 +79,7 @@ public: | ||||
| 	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: | ||||
|  | ||||
| @ -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 | ||||
| { | ||||
|  | ||||
| @ -266,6 +266,10 @@ public: | ||||
| 		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."); | ||||
|  | ||||
| @ -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; }; | ||||
| @ -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> | ||||
| { | ||||
|  | ||||
| @ -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) | ||||
| @ -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); | ||||
|  | ||||
| @ -62,6 +62,7 @@ protected: | ||||
| 
 | ||||
| 	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(); | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
| @ -40,6 +40,7 @@ struct Assignment; | ||||
| struct VariableDeclaration; | ||||
| struct FunctionDefinition; | ||||
| struct FunctionCall; | ||||
| struct Switch; | ||||
| struct Block; | ||||
| 
 | ||||
| class AsmPrinter: public boost::static_visitor<std::string> | ||||
| @ -57,6 +58,7 @@ public: | ||||
| 	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: | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -46,6 +46,7 @@ struct Identifier; | ||||
| struct StackAssignment; | ||||
| struct FunctionDefinition; | ||||
| struct FunctionCall; | ||||
| struct Switch; | ||||
| 
 | ||||
| struct Scope; | ||||
| 
 | ||||
| @ -69,6 +70,7 @@ public: | ||||
| 	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: | ||||
|  | ||||
| @ -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 } }")); | ||||
| @ -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}"); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user