diff --git a/CMakeLists.txt b/CMakeLists.txt index 3620e1a3b..171e7cd48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ add_subdirectory(libdevcore) add_subdirectory(libevmface) add_subdirectory(liblll) add_subdirectory(libserpent) +add_subdirectory(libsolidity) if(NOT APPLE) if(PYTHON_LS) add_subdirectory(libpyserpent) @@ -116,6 +117,7 @@ if(NOT APPLE) endif() add_subdirectory(lllc) +add_subdirectory(solc) add_subdirectory(sc) if (NOT LANGUAGES) add_subdirectory(secp256k1) diff --git a/exp/main.cpp b/exp/main.cpp index c1c6139a2..3af192380 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -105,14 +105,17 @@ int main(int argc, char** argv) /// Only interested in odd packets auto w = wh->installWatch(BuildTopicMask()("odd")); + KeyPair us = KeyPair::create(); for (int i = 0; ; ++i) { - wh->sendRaw(RLPStream().append(i * i).out(), BuildTopic(i)(i % 2 ? "odd" : "even")); + wh->post(us.sec(), RLPStream().append(i * i).out(), BuildTopic(i)(i % 2 ? "odd" : "even")); for (auto i: wh->checkWatch(w)) { - auto p = wh->envelope(i).open().payload(); - cnote << "New message:" << RLP(p).toInt(); + Message msg = wh->envelope(i).open(); + + cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); } + this_thread::sleep_for(chrono::seconds(1)); } return 0; } diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp new file mode 100644 index 000000000..15da9e0d8 --- /dev/null +++ b/libsolidity/AST.cpp @@ -0,0 +1,483 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Solidity abstract syntax tree. + */ + +#include + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +void ContractDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + listAccept(m_definedStructs, _visitor); + listAccept(m_stateVariables, _visitor); + listAccept(m_definedFunctions, _visitor); + } + _visitor.endVisit(*this); +} + +void StructDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + listAccept(m_members, _visitor); + _visitor.endVisit(*this); +} + +void ParameterList::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + listAccept(m_parameters, _visitor); + _visitor.endVisit(*this); +} + +void FunctionDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_parameters->accept(_visitor); + if (m_returnParameters) + m_returnParameters->accept(_visitor); + m_body->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void VariableDeclaration::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + if (m_typeName) + m_typeName->accept(_visitor); + _visitor.endVisit(*this); +} + +void TypeName::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void ElementaryTypeName::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void UserDefinedTypeName::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void Mapping::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_keyType->accept(_visitor); + m_valueType->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void Statement::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void Block::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + listAccept(m_statements, _visitor); + _visitor.endVisit(*this); +} + +void IfStatement::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_condition->accept(_visitor); + m_trueBody->accept(_visitor); + if (m_falseBody) + m_falseBody->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void BreakableStatement::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void WhileStatement::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_condition->accept(_visitor); + m_body->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void Continue::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void Break::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void Return::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + if (m_expression) + m_expression->accept(_visitor); + _visitor.endVisit(*this); +} + +void VariableDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_variable->accept(_visitor); + if (m_value) + m_value->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void Assignment::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_leftHandSide->accept(_visitor); + m_rightHandSide->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void UnaryOperation::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + m_subExpression->accept(_visitor); + _visitor.endVisit(*this); +} + +void BinaryOperation::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_left->accept(_visitor); + m_right->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void FunctionCall::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_expression->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + +void MemberAccess::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + m_expression->accept(_visitor); + _visitor.endVisit(*this); +} + +void IndexAccess::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_base->accept(_visitor); + m_index->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void Identifier::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void ElementaryTypeNameExpression::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void Literal::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void Statement::expectType(Expression& _expression, const Type& _expectedType) +{ + _expression.checkTypeRequirements(); + if (!_expression.getType()->isImplicitlyConvertibleTo(_expectedType)) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Type not implicitly convertible " + "to expected type.")); + //@todo provide more information to the exception +} + +void Block::checkTypeRequirements() +{ + for (std::shared_ptr const& statement: m_statements) + statement->checkTypeRequirements(); +} + +void IfStatement::checkTypeRequirements() +{ + expectType(*m_condition, BoolType()); + m_trueBody->checkTypeRequirements(); + if (m_falseBody) + m_falseBody->checkTypeRequirements(); +} + +void WhileStatement::checkTypeRequirements() +{ + expectType(*m_condition, BoolType()); + m_body->checkTypeRequirements(); +} + +void Continue::checkTypeRequirements() +{ +} + +void Break::checkTypeRequirements() +{ +} + +void Return::checkTypeRequirements() +{ + BOOST_ASSERT(m_returnParameters != nullptr); + if (m_returnParameters->getParameters().size() != 1) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Different number of arguments in " + "return statement than in returns " + "declaration.")); + // this could later be changed such that the paramaters type is an anonymous struct type, + // but for now, we only allow one return parameter + expectType(*m_expression, *m_returnParameters->getParameters().front()->getType()); +} + +void VariableDefinition::checkTypeRequirements() +{ + // Variables can be declared without type (with "var"), in which case the first assignment + // setsthe type. + // Note that assignments before the first declaration are legal because of the special scoping + // rules inherited from JavaScript. + if (m_value) + { + if (m_variable->getType()) + expectType(*m_value, *m_variable->getType()); + else + { + // no type declared and no previous assignment, infer the type + m_value->checkTypeRequirements(); + m_variable->setType(m_value->getType()); + } + } +} + +void Assignment::checkTypeRequirements() +{ + //@todo lefthandside actually has to be assignable + // add a feature to the type system to check that + m_leftHandSide->checkTypeRequirements(); + expectType(*m_rightHandSide, *m_leftHandSide->getType()); + m_type = m_leftHandSide->getType(); + if (m_assigmentOperator != Token::ASSIGN) + { + // complex assignment + if (!m_type->acceptsBinaryOperator(Token::AssignmentToBinaryOp(m_assigmentOperator))) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Operator not compatible with type.")); + } +} + +void UnaryOperation::checkTypeRequirements() +{ + // INC, DEC, NOT, BIT_NOT, DELETE + m_subExpression->checkTypeRequirements(); + m_type = m_subExpression->getType(); + if (m_type->acceptsUnaryOperator(m_operator)) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Unary operator not compatible with type.")); +} + +void BinaryOperation::checkTypeRequirements() +{ + m_right->checkTypeRequirements(); + m_left->checkTypeRequirements(); + if (m_right->getType()->isImplicitlyConvertibleTo(*m_left->getType())) + m_commonType = m_left->getType(); + else if (m_left->getType()->isImplicitlyConvertibleTo(*m_right->getType())) + m_commonType = m_right->getType(); + else + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("No common type found in binary operation.")); + if (Token::isCompareOp(m_operator)) + m_type = std::make_shared(); + else + { + BOOST_ASSERT(Token::isBinaryOp(m_operator)); + m_type = m_commonType; + if (!m_commonType->acceptsBinaryOperator(m_operator)) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Operator not compatible with type.")); + } +} + +void FunctionCall::checkTypeRequirements() +{ + m_expression->checkTypeRequirements(); + for (ASTPointer const& argument: m_arguments) + argument->checkTypeRequirements(); + Type const& expressionType = *m_expression->getType(); + Type::Category const category = expressionType.getCategory(); + if (category == Type::Category::TYPE) + { + TypeType const* type = dynamic_cast(&expressionType); + BOOST_ASSERT(type != nullptr); + //@todo for structs, we have to check the number of arguments to be equal to the + // number of non-mapping members + if (m_arguments.size() != 1) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("More than one argument for " + "explicit type conersion.")); + if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type->getActualType())) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Explicit type conversion not " + "allowed.")); + m_type = type->getActualType(); + } + else if (category == Type::Category::FUNCTION) + { + //@todo would be nice to create a struct type from the arguments + // and then ask if that is implicitly convertible to the struct represented by the + // function parameters + FunctionType const* function = dynamic_cast(&expressionType); + BOOST_ASSERT(function != nullptr); + FunctionDefinition const& fun = function->getFunction(); + std::vector> const& parameters = fun.getParameters(); + if (parameters.size() != m_arguments.size()) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Wrong argument count for " + "function call.")); + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameters[i]->getType())) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Invalid type for argument in " + "function call.")); + // @todo actually the return type should be an anonymous struct, + // but we change it to the type of the first return value until we have structs + if (fun.getReturnParameterList()->getParameters().empty()) + m_type = std::make_shared(); + else + m_type = fun.getReturnParameterList()->getParameters().front()->getType(); + } + else + { + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Type does not support invocation.")); + } +} + +void MemberAccess::checkTypeRequirements() +{ + BOOST_ASSERT(false); // not yet implemented + // m_type = ; +} + +void IndexAccess::checkTypeRequirements() +{ + BOOST_ASSERT(false); // not yet implemented + // m_type = ; +} + +void Identifier::checkTypeRequirements() +{ + BOOST_ASSERT(m_referencedDeclaration != nullptr); + //@todo these dynamic casts here are not really nice... + // is i useful to have an AST visitor here? + // or can this already be done in NameAndTypeResolver? + // the only problem we get there is that in + // var x; + // x = 2; + // var y = x; + // the type of x is not yet determined. + VariableDeclaration* variable = dynamic_cast(m_referencedDeclaration); + if (variable != nullptr) + { + if (!variable->getType()) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Variable referenced before type " + "could be determined.")); + m_type = variable->getType(); + return; + } + //@todo can we unify these with TypeName::toType()? + StructDefinition* structDef = dynamic_cast(m_referencedDeclaration); + if (structDef != nullptr) + { + // note that we do not have a struct type here + m_type = std::make_shared(std::make_shared(*structDef)); + return; + } + FunctionDefinition* functionDef = dynamic_cast(m_referencedDeclaration); + if (functionDef != nullptr) + { + // a function reference is not a TypeType, because calling a TypeType converts to the type. + // Calling a function (e.g. function(12), otherContract.function(34)) does not do a type + // conversion. + m_type = std::make_shared(*functionDef); + return; + } + ContractDefinition* contractDef = dynamic_cast(m_referencedDeclaration); + if (contractDef != nullptr) + { + m_type = std::make_shared(std::make_shared(*contractDef)); + return; + } + BOOST_ASSERT(false); // declaration reference of unknown/forbidden type +} + +void ElementaryTypeNameExpression::checkTypeRequirements() +{ + m_type = std::make_shared(Type::fromElementaryTypeName(m_typeToken)); +} + +void Literal::checkTypeRequirements() +{ + m_type = Type::forLiteral(*this); +} + +} +} diff --git a/libsolidity/AST.h b/libsolidity/AST.h new file mode 100644 index 000000000..d5e1e0662 --- /dev/null +++ b/libsolidity/AST.h @@ -0,0 +1,533 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Solidity abstract syntax tree. + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class ASTVisitor; + +class ASTNode: private boost::noncopyable +{ +public: + explicit ASTNode(Location const& _location): m_location(_location) {} + + virtual ~ASTNode() {} + + virtual void accept(ASTVisitor& _visitor) = 0; + template + static void listAccept(std::vector>& _list, ASTVisitor& _visitor) + { + for (ASTPointer& element: _list) + element->accept(_visitor); + } + + Location const& getLocation() const { return m_location; } + +private: + Location m_location; +}; + +class Declaration: public ASTNode +{ +public: + Declaration(Location const& _location, ASTPointer const& _name): + ASTNode(_location), m_name(_name) {} + + const ASTString& getName() const { return *m_name; } + +private: + ASTPointer m_name; +}; + +class ContractDefinition: public Declaration +{ +public: + ContractDefinition(Location const& _location, + ASTPointer const& _name, + std::vector> const& _definedStructs, + std::vector> const& _stateVariables, + std::vector> const& _definedFunctions): + Declaration(_location, _name), + m_definedStructs(_definedStructs), + m_stateVariables(_stateVariables), + m_definedFunctions(_definedFunctions) + {} + + virtual void accept(ASTVisitor& _visitor) override; + + std::vector> const& getDefinedStructs() { return m_definedStructs; } + std::vector> const& getStateVariables() { return m_stateVariables; } + std::vector> const& getDefinedFunctions() { return m_definedFunctions; } + +private: + std::vector> m_definedStructs; + std::vector> m_stateVariables; + std::vector> m_definedFunctions; +}; + +class StructDefinition: public Declaration +{ +public: + StructDefinition(Location const& _location, + ASTPointer const& _name, + std::vector> const& _members): + Declaration(_location, _name), m_members(_members) {} + virtual void accept(ASTVisitor& _visitor) override; + +private: + std::vector> m_members; +}; + +/// Used as function parameter list and return list +/// None of the parameters is allowed to contain mappings (not even recursively +/// inside structs) +class ParameterList: public ASTNode +{ +public: + ParameterList(Location const& _location, + std::vector> const& _parameters): + ASTNode(_location), m_parameters(_parameters) {} + virtual void accept(ASTVisitor& _visitor) override; + + std::vector> const& getParameters() { return m_parameters; } + +private: + std::vector> m_parameters; +}; + +class FunctionDefinition: public Declaration +{ +public: + FunctionDefinition(Location const& _location, ASTPointer const& _name, bool _isPublic, + ASTPointer const& _parameters, + bool _isDeclaredConst, + ASTPointer const& _returnParameters, + ASTPointer const& _body): + Declaration(_location, _name), m_isPublic(_isPublic), m_parameters(_parameters), + m_isDeclaredConst(_isDeclaredConst), m_returnParameters(_returnParameters), + m_body(_body) {} + virtual void accept(ASTVisitor& _visitor) override; + + bool isPublic() const { return m_isPublic; } + bool isDeclaredConst() const { return m_isDeclaredConst; } + std::vector> const& getParameters() const { return m_parameters->getParameters(); } + ParameterList& getParameterList() { return *m_parameters; } + ASTPointer const& getReturnParameterList() const { return m_returnParameters; } + Block& getBody() { return *m_body; } + +private: + bool m_isPublic; + ASTPointer m_parameters; + bool m_isDeclaredConst; + ASTPointer m_returnParameters; + ASTPointer m_body; +}; + +class VariableDeclaration: public Declaration +{ +public: + VariableDeclaration(Location const& _location, ASTPointer const& _type, + ASTPointer const& _name): + Declaration(_location, _name), m_typeName(_type) {} + virtual void accept(ASTVisitor& _visitor) override; + + bool isTypeGivenExplicitly() const { return m_typeName.get() != nullptr; } + TypeName* getTypeName() const { return m_typeName.get(); } + + //! Returns the declared or inferred type. Can be an empty pointer if no type was explicitly + //! declared and there is no assignment to the variable that fixes the type. + std::shared_ptr const& getType() const { return m_type; } + void setType(std::shared_ptr const& _type) { m_type = _type; } + +private: + ASTPointer m_typeName; ///< can be empty ("var") + + std::shared_ptr m_type; +}; + +/// types +/// @{ + +class TypeName: public ASTNode +{ +public: + explicit TypeName(Location const& _location): ASTNode(_location) {} + virtual void accept(ASTVisitor& _visitor) override; + + virtual std::shared_ptr toType() = 0; +}; + +/// any pre-defined type that is not a mapping +class ElementaryTypeName: public TypeName +{ +public: + explicit ElementaryTypeName(Location const& _location, Token::Value _type): + TypeName(_location), m_type(_type) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual std::shared_ptr toType() override { return Type::fromElementaryTypeName(m_type); } + + Token::Value getType() const { return m_type; } + +private: + Token::Value m_type; +}; + +class UserDefinedTypeName: public TypeName +{ +public: + UserDefinedTypeName(Location const& _location, ASTPointer const& _name): + TypeName(_location), m_name(_name) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual std::shared_ptr toType() override { return Type::fromUserDefinedTypeName(*this); } + + const ASTString& getName() const { return *m_name; } + void setReferencedStruct(StructDefinition& _referencedStruct) { m_referencedStruct = &_referencedStruct; } + StructDefinition const* getReferencedStruct() const { return m_referencedStruct; } + +private: + ASTPointer m_name; + + StructDefinition* m_referencedStruct; +}; + +class Mapping: public TypeName +{ +public: + Mapping(Location const& _location, ASTPointer const& _keyType, + ASTPointer const& _valueType): + TypeName(_location), m_keyType(_keyType), m_valueType(_valueType) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual std::shared_ptr toType() override { return Type::fromMapping(*this); } + +private: + ASTPointer m_keyType; + ASTPointer m_valueType; +}; + +/// @} + +/// Statements +/// @{ + +class Statement: public ASTNode +{ +public: + explicit Statement(Location const& _location): ASTNode(_location) {} + virtual void accept(ASTVisitor& _visitor) override; + + //! Check all type requirements, throws exception if some requirement is not met. + //! For expressions, this also returns the inferred type of the expression. For other + //! statements, returns the empty pointer. + virtual void checkTypeRequirements() = 0; + +protected: + //! Check that the inferred type for _expression is _expectedType or at least implicitly + //! convertible to _expectedType. If not, throw exception. + void expectType(Expression& _expression, Type const& _expectedType); +}; + +class Block: public Statement +{ +public: + Block(Location const& _location, std::vector> const& _statements): + Statement(_location), m_statements(_statements) {} + virtual void accept(ASTVisitor& _visitor) override; + + virtual void checkTypeRequirements() override; + +private: + std::vector> m_statements; +}; + +class IfStatement: public Statement +{ +public: + IfStatement(Location const& _location, ASTPointer const& _condition, + ASTPointer const& _trueBody, ASTPointer const& _falseBody): + Statement(_location), + m_condition(_condition), m_trueBody(_trueBody), m_falseBody(_falseBody) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + +private: + ASTPointer m_condition; + ASTPointer m_trueBody; + ASTPointer m_falseBody; //< "else" part, optional +}; + +class BreakableStatement: public Statement +{ +public: + BreakableStatement(Location const& _location): Statement(_location) {} + virtual void accept(ASTVisitor& _visitor) override; +}; + +class WhileStatement: public BreakableStatement +{ +public: + WhileStatement(Location const& _location, ASTPointer const& _condition, + ASTPointer const& _body): + BreakableStatement(_location), m_condition(_condition), m_body(_body) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + +private: + ASTPointer m_condition; + ASTPointer m_body; +}; + +class Continue: public Statement +{ +public: + Continue(Location const& _location): Statement(_location) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; +}; + +class Break: public Statement +{ +public: + Break(Location const& _location): Statement(_location) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; +}; + +class Return: public Statement +{ +public: + Return(Location const& _location, ASTPointer _expression): + Statement(_location), m_expression(_expression) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + + void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; } + +private: + ASTPointer m_expression; //< value to return, optional + + ParameterList* m_returnParameters; //< extracted from the function declaration +}; + +class VariableDefinition: public Statement +{ +public: + VariableDefinition(Location const& _location, ASTPointer _variable, + ASTPointer _value): + Statement(_location), m_variable(_variable), m_value(_value) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + +private: + ASTPointer m_variable; + ASTPointer m_value; ///< can be missing +}; + +class Expression: public Statement +{ +public: + Expression(Location const& _location): Statement(_location) {} + std::shared_ptr const& getType() const { return m_type; } + +protected: + //! Inferred type of the expression, only filled after a call to checkTypeRequirements(). + std::shared_ptr m_type; +}; + +/// @} + +/// Expressions +/// @{ + +class Assignment: public Expression +{ +public: + Assignment(Location const& _location, ASTPointer const& _leftHandSide, + Token::Value _assignmentOperator, ASTPointer const& _rightHandSide): + Expression(_location), m_leftHandSide(_leftHandSide), + m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + + Token::Value getAssignmentOperator() const { return m_assigmentOperator; } + +private: + ASTPointer m_leftHandSide; + Token::Value m_assigmentOperator; + ASTPointer m_rightHandSide; +}; + +class UnaryOperation: public Expression +{ +public: + UnaryOperation(Location const& _location, Token::Value _operator, + ASTPointer const& _subExpression, bool _isPrefix): + Expression(_location), m_operator(_operator), + m_subExpression(_subExpression), m_isPrefix(_isPrefix) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + + Token::Value getOperator() const { return m_operator; } + bool isPrefixOperation() const { return m_isPrefix; } + +private: + Token::Value m_operator; + ASTPointer m_subExpression; + bool m_isPrefix; +}; + +class BinaryOperation: public Expression +{ +public: + BinaryOperation(Location const& _location, ASTPointer const& _left, + Token::Value _operator, ASTPointer const& _right): + Expression(_location), m_left(_left), m_operator(_operator), m_right(_right) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + + Token::Value getOperator() const { return m_operator; } + +private: + ASTPointer m_left; + Token::Value m_operator; + ASTPointer m_right; + + std::shared_ptr m_commonType; +}; + +/// Can be ordinary function call, type cast or struct construction. +class FunctionCall: public Expression +{ +public: + FunctionCall(Location const& _location, ASTPointer const& _expression, + std::vector> const& _arguments): + Expression(_location), m_expression(_expression), m_arguments(_arguments) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + +private: + ASTPointer m_expression; + std::vector> m_arguments; +}; + +class MemberAccess: public Expression +{ +public: + MemberAccess(Location const& _location, ASTPointer _expression, + ASTPointer const& _memberName): + Expression(_location), m_expression(_expression), m_memberName(_memberName) {} + virtual void accept(ASTVisitor& _visitor) override; + const ASTString& getMemberName() const { return *m_memberName; } + virtual void checkTypeRequirements() override; + +private: + ASTPointer m_expression; + ASTPointer m_memberName; +}; + +class IndexAccess: public Expression +{ +public: + IndexAccess(Location const& _location, ASTPointer const& _base, + ASTPointer const& _index): + Expression(_location), m_base(_base), m_index(_index) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + +private: + ASTPointer m_base; + ASTPointer m_index; +}; + +class PrimaryExpression: public Expression +{ +public: + PrimaryExpression(Location const& _location): Expression(_location) {} +}; + +class Identifier: public PrimaryExpression +{ +public: + Identifier(Location const& _location, ASTPointer const& _name): + PrimaryExpression(_location), m_name(_name) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + + ASTString const& getName() const { return *m_name; } + void setReferencedDeclaration(Declaration& _referencedDeclaration) { m_referencedDeclaration = &_referencedDeclaration; } + Declaration* getReferencedDeclaration() { return m_referencedDeclaration; } + +private: + ASTPointer m_name; + + //! Declaration the name refers to. + Declaration* m_referencedDeclaration; +}; + +class ElementaryTypeNameExpression: public PrimaryExpression +{ +public: + ElementaryTypeNameExpression(Location const& _location, Token::Value _typeToken): + PrimaryExpression(_location), m_typeToken(_typeToken) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + + Token::Value getTypeToken() const { return m_typeToken; } + +private: + Token::Value m_typeToken; +}; + +class Literal: public PrimaryExpression +{ +public: + Literal(Location const& _location, Token::Value _token, ASTPointer const& _value): + PrimaryExpression(_location), m_token(_token), m_value(_value) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; + + Token::Value getToken() const { return m_token; } + ASTString const& getValue() const { return *m_value; } + +private: + Token::Value m_token; + ASTPointer m_value; +}; + +/// @} + +} +} diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h new file mode 100644 index 000000000..c9a780f5f --- /dev/null +++ b/libsolidity/ASTForward.h @@ -0,0 +1,78 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Forward-declarations of AST classes. + */ + +#pragma once + +#include +#include +#include + +// Forward-declare all AST node types + +namespace dev +{ +namespace solidity +{ + +class ASTNode; +class Declaration; +class ContractDefinition; +class StructDefinition; +class ParameterList; +class FunctionDefinition; +class VariableDeclaration; +class TypeName; +class ElementaryTypeName; +class UserDefinedTypeName; +class Mapping; +class Statement; +class Block; +class IfStatement; +class BreakableStatement; +class WhileStatement; +class Continue; +class Break; +class Return; +class VariableDefinition; +class Expression; +class Assignment; +class UnaryOperation; +class BinaryOperation; +class FunctionCall; +class MemberAccess; +class IndexAccess; +class PrimaryExpression; +class Identifier; +class ElementaryTypeNameExpression; +class Literal; + +// Used as pointers to AST nodes, to be replaced by more clever pointers, e.g. pointers which do +// not do reference counting but point to a special memory area that is completely released +// explicitly. +template +using ASTPointer = std::shared_ptr; + +using ASTString = std::string; + + +} +} diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp new file mode 100644 index 000000000..c512bd3f9 --- /dev/null +++ b/libsolidity/ASTPrinter.cpp @@ -0,0 +1,424 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging. + */ + +#include +#include + +namespace dev +{ +namespace solidity +{ + +ASTPrinter::ASTPrinter(ASTPointer const& _ast, std::string const& _source): + m_indentation(0), m_source(_source), m_ast(_ast) +{ +} + +void ASTPrinter::print(std::ostream& _stream) +{ + m_ostream = &_stream; + m_ast->accept(*this); + m_ostream = nullptr; +} + + +bool ASTPrinter::visit(ContractDefinition& _node) +{ + writeLine("ContractDefinition \"" + _node.getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(StructDefinition& _node) +{ + writeLine("StructDefinition \"" + _node.getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(ParameterList& _node) +{ + writeLine("ParameterList"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(FunctionDefinition& _node) +{ + writeLine("FunctionDefinition \"" + _node.getName() + "\"" + + (_node.isPublic() ? " - public" : "") + + (_node.isDeclaredConst() ? " - const" : "")); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(VariableDeclaration& _node) +{ + writeLine("VariableDeclaration \"" + _node.getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(TypeName& _node) +{ + writeLine("TypeName"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(ElementaryTypeName& _node) +{ + writeLine(std::string("ElementaryTypeName ") + Token::toString(_node.getType())); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(UserDefinedTypeName& _node) +{ + writeLine("UserDefinedTypeName \"" + _node.getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Mapping& _node) +{ + writeLine("Mapping"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Statement& _node) +{ + writeLine("Statement"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Block& _node) +{ + writeLine("Block"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(IfStatement& _node) +{ + writeLine("IfStatement"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(BreakableStatement& _node) +{ + writeLine("BreakableStatement"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(WhileStatement& _node) +{ + writeLine("WhileStatement"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Continue& _node) +{ + writeLine("Continue"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Break& _node) +{ + writeLine("Break"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Return& _node) +{ + writeLine("Return"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(VariableDefinition& _node) +{ + writeLine("VariableDefinition"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Expression& _node) +{ + writeLine("Expression"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Assignment& _node) +{ + writeLine(std::string("Assignment using operator ") + Token::toString(_node.getAssignmentOperator())); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(UnaryOperation& _node) +{ + writeLine(std::string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") + + ") " + Token::toString(_node.getOperator())); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(BinaryOperation& _node) +{ + writeLine(std::string("BinaryOperation using operator ") + Token::toString(_node.getOperator())); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(FunctionCall& _node) +{ + writeLine("FunctionCall"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(MemberAccess& _node) +{ + writeLine("MemberAccess to member " + _node.getMemberName()); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(IndexAccess& _node) +{ + writeLine("IndexAccess"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(PrimaryExpression& _node) +{ + writeLine("PrimaryExpression"); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Identifier& _node) +{ + writeLine(std::string("Identifier ") + _node.getName()); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(ElementaryTypeNameExpression& _node) +{ + writeLine(std::string("ElementaryTypeNameExpression ") + Token::toString(_node.getTypeToken())); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(Literal& _node) +{ + char const* tokenString = Token::toString(_node.getToken()); + if (tokenString == nullptr) + tokenString = "[no token]"; + writeLine(std::string("Literal, token: ") + tokenString + " value: " + _node.getValue()); + printSourcePart(_node); + return goDeeper(); +} + +void ASTPrinter::endVisit(ASTNode&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(ContractDefinition&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(StructDefinition&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(ParameterList&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(FunctionDefinition&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(VariableDeclaration&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(TypeName&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(ElementaryTypeName&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(UserDefinedTypeName&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Mapping&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Statement&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Block&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(IfStatement&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(BreakableStatement&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(WhileStatement&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Continue&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Break&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Return&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(VariableDefinition&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Expression&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Assignment&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(UnaryOperation&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(BinaryOperation&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(FunctionCall&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(MemberAccess&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(IndexAccess&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(PrimaryExpression&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Identifier&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(ElementaryTypeNameExpression&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(Literal&) +{ + m_indentation--; +} + +void ASTPrinter::printSourcePart(ASTNode const& _node) +{ + if (!m_source.empty()) + { + Location const& location(_node.getLocation()); + *m_ostream << getIndentation() << " Source: |" + << m_source.substr(location.start, location.end - location.start) << "|\n"; + } +} + +std::string ASTPrinter::getIndentation() const +{ + return std::string(m_indentation * 2, ' '); +} + +void ASTPrinter::writeLine(std::string const& _line) +{ + *m_ostream << getIndentation() << _line << '\n'; +} + +} +} diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h new file mode 100644 index 000000000..e8d125a54 --- /dev/null +++ b/libsolidity/ASTPrinter.h @@ -0,0 +1,116 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Pretty-printer for the abstract syntax tree (the "pretty" is arguable), used for debugging. + */ + +#pragma once + +#include +#include + +namespace dev +{ +namespace solidity +{ + +class ASTPrinter: public ASTVisitor +{ +public: + /// Create a printer for the given abstract syntax tree. If the source is specified, + /// the corresponding parts of the source are printed with each node. + ASTPrinter(ASTPointer const& _ast, const std::string& _source = std::string()); + /// Output the string representation of the AST to _stream. + void print(std::ostream& _stream); + + bool visit(ContractDefinition& _node) override; + bool visit(StructDefinition& _node) override; + bool visit(ParameterList& _node) override; + bool visit(FunctionDefinition& _node) override; + bool visit(VariableDeclaration& _node) override; + bool visit(TypeName& _node) override; + bool visit(ElementaryTypeName& _node) override; + bool visit(UserDefinedTypeName& _node) override; + bool visit(Mapping& _node) override; + bool visit(Statement& _node) override; + bool visit(Block& _node) override; + bool visit(IfStatement& _node) override; + bool visit(BreakableStatement& _node) override; + bool visit(WhileStatement& _node) override; + bool visit(Continue& _node) override; + bool visit(Break& _node) override; + bool visit(Return& _node) override; + bool visit(VariableDefinition& _node) override; + bool visit(Expression& _node) override; + bool visit(Assignment& _node) override; + bool visit(UnaryOperation& _node) override; + bool visit(BinaryOperation& _node) override; + bool visit(FunctionCall& _node) override; + bool visit(MemberAccess& _node) override; + bool visit(IndexAccess& _node) override; + bool visit(PrimaryExpression& _node) override; + bool visit(Identifier& _node) override; + bool visit(ElementaryTypeNameExpression& _node) override; + bool visit(Literal& _node) override; + + void endVisit(ASTNode& _node) override; + void endVisit(ContractDefinition&) override; + void endVisit(StructDefinition&) override; + void endVisit(ParameterList&) override; + void endVisit(FunctionDefinition&) override; + void endVisit(VariableDeclaration&) override; + void endVisit(TypeName&) override; + void endVisit(ElementaryTypeName&) override; + void endVisit(UserDefinedTypeName&) override; + void endVisit(Mapping&) override; + void endVisit(Statement&) override; + void endVisit(Block&) override; + void endVisit(IfStatement&) override; + void endVisit(BreakableStatement&) override; + void endVisit(WhileStatement&) override; + void endVisit(Continue&) override; + void endVisit(Break&) override; + void endVisit(Return&) override; + void endVisit(VariableDefinition&) override; + void endVisit(Expression&) override; + void endVisit(Assignment&) override; + void endVisit(UnaryOperation&) override; + void endVisit(BinaryOperation&) override; + void endVisit(FunctionCall&) override; + void endVisit(MemberAccess&) override; + void endVisit(IndexAccess&) override; + void endVisit(PrimaryExpression&) override; + void endVisit(Identifier&) override; + void endVisit(ElementaryTypeNameExpression&) override; + void endVisit(Literal&) override; + +private: + void printSourcePart(ASTNode const& _node); + std::string getIndentation() const; + void writeLine(std::string const& _line); + bool goDeeper() { m_indentation++; return true; } + + int m_indentation; + std::string m_source; + ASTPointer m_ast; + std::ostream* m_ostream; +}; + +} +} diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h new file mode 100644 index 000000000..72f287683 --- /dev/null +++ b/libsolidity/ASTVisitor.h @@ -0,0 +1,104 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * AST visitor base class. + */ + +#pragma once + +#include +#include + +namespace dev +{ +namespace solidity +{ + +class ASTVisitor +{ +public: + /// These functions are called after a call to ASTNode::accept, + /// first visit, then (if visit returns true) recursively for all + /// child nodes in document order (exception for contracts) and then + /// endVisit. + virtual bool visit(ASTNode&) { return true; } + virtual bool visit(ContractDefinition&) { return true; } + virtual bool visit(StructDefinition&) { return true; } + virtual bool visit(ParameterList&) { return true; } + virtual bool visit(FunctionDefinition&) { return true; } + virtual bool visit(VariableDeclaration&) { return true; } + virtual bool visit(TypeName&) { return true; } + virtual bool visit(ElementaryTypeName&) { return true; } + virtual bool visit(UserDefinedTypeName&) { return true; } + virtual bool visit(Mapping&) { return true; } + virtual bool visit(Statement&) { return true; } + virtual bool visit(Block&) { return true; } + virtual bool visit(IfStatement&) { return true; } + virtual bool visit(BreakableStatement&) { return true; } + virtual bool visit(WhileStatement&) { return true; } + virtual bool visit(Continue&) { return true; } + virtual bool visit(Break&) { return true; } + virtual bool visit(Return&) { return true; } + virtual bool visit(VariableDefinition&) { return true; } + virtual bool visit(Expression&) { return true; } + virtual bool visit(Assignment&) { return true; } + virtual bool visit(UnaryOperation&) { return true; } + virtual bool visit(BinaryOperation&) { return true; } + virtual bool visit(FunctionCall&) { return true; } + virtual bool visit(MemberAccess&) { return true; } + virtual bool visit(IndexAccess&) { return true; } + virtual bool visit(PrimaryExpression&) { return true; } + virtual bool visit(Identifier&) { return true; } + virtual bool visit(ElementaryTypeNameExpression&) { return true; } + virtual bool visit(Literal&) { return true; } + + virtual void endVisit(ASTNode&) { } + virtual void endVisit(ContractDefinition&) { } + virtual void endVisit(StructDefinition&) { } + virtual void endVisit(ParameterList&) { } + virtual void endVisit(FunctionDefinition&) { } + virtual void endVisit(VariableDeclaration&) { } + virtual void endVisit(TypeName&) { } + virtual void endVisit(ElementaryTypeName&) { } + virtual void endVisit(UserDefinedTypeName&) { } + virtual void endVisit(Mapping&) { } + virtual void endVisit(Statement&) { } + virtual void endVisit(Block&) { } + virtual void endVisit(IfStatement&) { } + virtual void endVisit(BreakableStatement&) { } + virtual void endVisit(WhileStatement&) { } + virtual void endVisit(Continue&) { } + virtual void endVisit(Break&) { } + virtual void endVisit(Return&) { } + virtual void endVisit(VariableDefinition&) { } + virtual void endVisit(Expression&) { } + virtual void endVisit(Assignment&) { } + virtual void endVisit(UnaryOperation&) { } + virtual void endVisit(BinaryOperation&) { } + virtual void endVisit(FunctionCall&) { } + virtual void endVisit(MemberAccess&) { } + virtual void endVisit(IndexAccess&) { } + virtual void endVisit(PrimaryExpression&) { } + virtual void endVisit(Identifier&) { } + virtual void endVisit(ElementaryTypeNameExpression&) { } + virtual void endVisit(Literal&) { } +}; + +} +} diff --git a/libsolidity/BaseTypes.h b/libsolidity/BaseTypes.h new file mode 100644 index 000000000..fdf3f7b53 --- /dev/null +++ b/libsolidity/BaseTypes.h @@ -0,0 +1,45 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Some elementary types for the parser. + */ + +#pragma once + + +namespace dev +{ +namespace solidity +{ + +/// Representation of an interval of source positions. +/// The interval includes start and excludes end. +struct Location +{ + Location(int _start, int _end): start(_start), end(_end) { } + Location(): start(-1), end(-1) { } + + bool IsValid() const { return start >= 0 && end >= start; } + + int start; + int end; +}; + +} +} diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt new file mode 100644 index 000000000..59aa78364 --- /dev/null +++ b/libsolidity/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_policy(SET CMP0015 NEW) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") + +aux_source_directory(. SRC_LIST) + +set(EXECUTABLE solidity) + +if(ETH_STATIC) + add_library(${EXECUTABLE} STATIC ${SRC_LIST}) +else() + add_library(${EXECUTABLE} SHARED ${SRC_LIST}) +endif() + +file(GLOB HEADERS "*.h") + +include_directories(..) + +target_link_libraries(${EXECUTABLE} evmface) +target_link_libraries(${EXECUTABLE} devcore) + + +if("${TARGET_PLATFORM}" STREQUAL "w64") + target_link_libraries(${EXECUTABLE} boost_system-mt-s) + target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTABLE} iphlpapi) + target_link_libraries(${EXECUTABLE} ws2_32) + target_link_libraries(${EXECUTABLE} mswsock) + target_link_libraries(${EXECUTABLE} shlwapi) +elseif (APPLE) + # Latest mavericks boost libraries only come with -mt + target_link_libraries(${EXECUTABLE} boost_system-mt) + target_link_libraries(${EXECUTABLE} boost_thread-mt) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +elseif (UNIX) + target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +else () + target_link_libraries(${EXECUTABLE} boost_system) + target_link_libraries(${EXECUTABLE} boost_thread) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +endif () + +install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) +install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) + diff --git a/libsolidity/Exceptions.h b/libsolidity/Exceptions.h new file mode 100644 index 000000000..c600ebf12 --- /dev/null +++ b/libsolidity/Exceptions.h @@ -0,0 +1,37 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Solidity exception hierarchy. + */ + +#pragma once + +#include + +namespace dev +{ +namespace solidity +{ + +struct ParserError: virtual Exception {}; +struct TypeError: virtual Exception {}; +struct DeclarationError: virtual Exception {}; + +} +} diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp new file mode 100644 index 000000000..707b6ce14 --- /dev/null +++ b/libsolidity/NameAndTypeResolver.cpp @@ -0,0 +1,198 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Parser part that determines the declarations corresponding to names and the types of expressions. + */ + +#include + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + + +void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) +{ + reset(); + DeclarationRegistrationHelper registrar(m_scopes, _contract); + m_currentScope = &m_scopes[&_contract]; + //@todo structs + for (ASTPointer const& variable: _contract.getStateVariables()) + ReferencesResolver resolver(*variable, *this, nullptr); + for (ASTPointer const& function: _contract.getDefinedFunctions()) + { + m_currentScope = &m_scopes[function.get()]; + ReferencesResolver referencesResolver(*function, *this, + function->getReturnParameterList().get()); + } + // First, the parameter types of all functions need to be resolved before we can check + // the types, since it is possible to call functions that are only defined later + // in the source. + for (ASTPointer const& function: _contract.getDefinedFunctions()) + { + m_currentScope = &m_scopes[function.get()]; + function->getBody().checkTypeRequirements(); + } +} + +void NameAndTypeResolver::reset() +{ + m_scopes.clear(); + m_currentScope = nullptr; +} + +Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive) +{ + return m_currentScope->resolveName(_name, _recursive); +} + + +DeclarationRegistrationHelper::DeclarationRegistrationHelper(std::map& _scopes, + ASTNode& _astRoot): + m_scopes(_scopes), m_currentScope(&m_scopes[nullptr]) +{ + _astRoot.accept(*this); +} + +bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract) +{ + registerDeclaration(_contract, true); + return true; +} + +void DeclarationRegistrationHelper::endVisit(ContractDefinition&) +{ + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(StructDefinition& _struct) +{ + registerDeclaration(_struct, true); + return true; +} + +void DeclarationRegistrationHelper::endVisit(StructDefinition&) +{ + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) +{ + registerDeclaration(_function, true); + return true; +} + +void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) +{ + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) +{ + registerDeclaration(_declaration, false); + return true; +} + +void DeclarationRegistrationHelper::endVisit(VariableDeclaration&) +{ +} + +void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _node) +{ + std::map::iterator iter; + bool newlyAdded; + std::tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope)); + BOOST_ASSERT(newlyAdded); + m_currentScope = &iter->second; +} + +void DeclarationRegistrationHelper::closeCurrentScope() +{ + BOOST_ASSERT(m_currentScope != nullptr); + m_currentScope = m_currentScope->getOuterScope(); +} + +void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) +{ + BOOST_ASSERT(m_currentScope != nullptr); + if (!m_currentScope->registerDeclaration(_declaration)) + BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_comment("Identifier already declared.")); + if (_opensScope) + enterNewSubScope(_declaration); +} + +ReferencesResolver::ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver, + ParameterList* _returnParameters): + m_resolver(_resolver), m_returnParameters(_returnParameters) +{ + _root.accept(*this); +} + +void ReferencesResolver::endVisit(VariableDeclaration& _variable) +{ + // endVisit because the internal type needs resolving if it is a user defined type + // or mapping + if (_variable.getTypeName() != nullptr) + _variable.setType(_variable.getTypeName()->toType()); + // otherwise we have a "var"-declaration whose type is resolved by the first assignment +} + +bool ReferencesResolver::visit(Return& _return) +{ + BOOST_ASSERT(m_returnParameters != nullptr); + _return.setFunctionReturnParameters(*m_returnParameters); + return true; +} + +bool ReferencesResolver::visit(Mapping&) +{ + // @todo + return true; +} + +bool ReferencesResolver::visit(UserDefinedTypeName& _typeName) +{ + Declaration* declaration = m_resolver.getNameFromCurrentScope(_typeName.getName()); + if (declaration == nullptr) + BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_comment("Undeclared identifier.")); + StructDefinition* referencedStruct = dynamic_cast(declaration); + //@todo later, contracts are also valid types + if (referencedStruct == nullptr) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Identifier does not name a type name.")); + _typeName.setReferencedStruct(*referencedStruct); + return false; +} + +bool ReferencesResolver::visit(Identifier& _identifier) +{ + Declaration* declaration = m_resolver.getNameFromCurrentScope(_identifier.getName()); + if (declaration == nullptr) + BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_comment("Undeclared identifier.")); + _identifier.setReferencedDeclaration(*declaration); + return false; +} + + +} +} diff --git a/libsolidity/NameAndTypeResolver.h b/libsolidity/NameAndTypeResolver.h new file mode 100644 index 000000000..7abcbb0c5 --- /dev/null +++ b/libsolidity/NameAndTypeResolver.h @@ -0,0 +1,100 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Parser part that determines the declarations corresponding to names and the types of expressions. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace dev +{ +namespace solidity +{ + +//! Resolves name references, resolves all types and checks that all operations are valid for the +//! inferred types. An exception is throw on the first error. +class NameAndTypeResolver: private boost::noncopyable +{ +public: + NameAndTypeResolver() {} + + void resolveNamesAndTypes(ContractDefinition& _contract); + Declaration* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true); + +private: + void reset(); + + //! Maps nodes declaring a scope to scopes, i.e. ContractDefinition, FunctionDeclaration and + //! StructDefinition (@todo not yet implemented), where nullptr denotes the global scope. + std::map m_scopes; + + Scope* m_currentScope; +}; + +//! Traverses the given AST upon construction and fills _scopes with all declarations inside the +//! AST. +class DeclarationRegistrationHelper: private ASTVisitor +{ +public: + DeclarationRegistrationHelper(std::map& _scopes, ASTNode& _astRoot); + +private: + bool visit(ContractDefinition& _contract); + void endVisit(ContractDefinition& _contract); + bool visit(StructDefinition& _struct); + void endVisit(StructDefinition& _struct); + bool visit(FunctionDefinition& _function); + void endVisit(FunctionDefinition& _function); + bool visit(VariableDeclaration& _declaration); + void endVisit(VariableDeclaration& _declaration); + + void enterNewSubScope(ASTNode& _node); + void closeCurrentScope(); + void registerDeclaration(Declaration& _declaration, bool _opensScope); + + std::map& m_scopes; + Scope* m_currentScope; +}; + +//! Resolves references to declarations (of variables and types) and also establishes the link +//! between a return statement and the return parameter list. +class ReferencesResolver: private ASTVisitor +{ +public: + ReferencesResolver(ASTNode& _root, NameAndTypeResolver& _resolver, ParameterList* _returnParameters); + +private: + virtual void endVisit(VariableDeclaration& _variable) override; + virtual bool visit(Identifier& _identifier) override; + virtual bool visit(UserDefinedTypeName& _typeName) override; + virtual bool visit(Mapping&) override; + virtual bool visit(Return& _return) override; + + NameAndTypeResolver& m_resolver; + ParameterList* m_returnParameters; +}; + +} +} diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp new file mode 100644 index 000000000..408aa7bd6 --- /dev/null +++ b/libsolidity/Parser.cpp @@ -0,0 +1,553 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Solidity parser. + */ + +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +ASTPointer Parser::parse(std::shared_ptr const& _scanner) +{ + m_scanner = _scanner; + return parseContractDefinition(); +} + + +/// AST node factory that also tracks the begin and end position of an AST node +/// while it is being parsed +class Parser::ASTNodeFactory +{ +public: + ASTNodeFactory(Parser const& _parser): m_parser(_parser), m_location(_parser.getPosition(), -1) {} + + void markEndPosition() { m_location.end = m_parser.getEndPosition(); } + void setLocationEmpty() { m_location.end = m_location.start; } + /// Set the end position to the one of the given node. + void setEndPositionFromNode(ASTPointer const& _node) { m_location.end = _node->getLocation().end; } + + template + ASTPointer createNode(Args&& ... _args) + { + if (m_location.end < 0) + markEndPosition(); + return std::make_shared(m_location, std::forward(_args)...); + } + +private: + Parser const& m_parser; + Location m_location; +}; + +int Parser::getPosition() const +{ + return m_scanner->getCurrentLocation().start; +} + +int Parser::getEndPosition() const +{ + return m_scanner->getCurrentLocation().end; +} + +ASTPointer Parser::parseContractDefinition() +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::CONTRACT); + ASTPointer name = expectIdentifierToken(); + expectToken(Token::LBRACE); + std::vector> structs; + std::vector> stateVariables; + std::vector> functions; + bool visibilityIsPublic = true; + while (true) + { + Token::Value currentToken = m_scanner->getCurrentToken(); + if (currentToken == Token::RBRACE) + break; + else if (currentToken == Token::PUBLIC || currentToken == Token::PRIVATE) + { + visibilityIsPublic = (m_scanner->getCurrentToken() == Token::PUBLIC); + m_scanner->next(); + expectToken(Token::COLON); + } + else if (currentToken == Token::FUNCTION) + functions.push_back(parseFunctionDefinition(visibilityIsPublic)); + else if (currentToken == Token::STRUCT) + structs.push_back(parseStructDefinition()); + else if (currentToken == Token::IDENTIFIER || currentToken == Token::MAPPING || + Token::isElementaryTypeName(currentToken)) + { + bool const allowVar = false; + stateVariables.push_back(parseVariableDeclaration(allowVar)); + expectToken(Token::SEMICOLON); + } + else + throwExpectationError("Function, variable or struct declaration expected."); + } + nodeFactory.markEndPosition(); + expectToken(Token::RBRACE); + expectToken(Token::EOS); + return nodeFactory.createNode(name, structs, stateVariables, functions); +} + +ASTPointer Parser::parseFunctionDefinition(bool _isPublic) +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::FUNCTION); + ASTPointer name(expectIdentifierToken()); + ASTPointer parameters(parseParameterList()); + bool isDeclaredConst = false; + if (m_scanner->getCurrentToken() == Token::CONST) + { + isDeclaredConst = true; + m_scanner->next(); + } + ASTPointer returnParameters; + if (m_scanner->getCurrentToken() == Token::RETURNS) + { + bool const permitEmptyParameterList = false; + m_scanner->next(); + returnParameters = parseParameterList(permitEmptyParameterList); + } + else + { + // create an empty parameter list at a zero-length location + ASTNodeFactory nodeFactory(*this); + nodeFactory.setLocationEmpty(); + returnParameters = nodeFactory.createNode(std::vector>()); + } + ASTPointer block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + return nodeFactory.createNode(name, _isPublic, parameters, + isDeclaredConst, returnParameters, block); +} + +ASTPointer Parser::parseStructDefinition() +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::STRUCT); + ASTPointer name = expectIdentifierToken(); + std::vector> members; + expectToken(Token::LBRACE); + while (m_scanner->getCurrentToken() != Token::RBRACE) + { + bool const allowVar = false; + members.push_back(parseVariableDeclaration(allowVar)); + expectToken(Token::SEMICOLON); + } + nodeFactory.markEndPosition(); + expectToken(Token::RBRACE); + return nodeFactory.createNode(name, members); +} + +ASTPointer Parser::parseVariableDeclaration(bool _allowVar) +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer type = parseTypeName(_allowVar); + nodeFactory.markEndPosition(); + return nodeFactory.createNode(type, expectIdentifierToken()); +} + +ASTPointer Parser::parseTypeName(bool _allowVar) +{ + ASTPointer type; + Token::Value token = m_scanner->getCurrentToken(); + if (Token::isElementaryTypeName(token)) + { + type = ASTNodeFactory(*this).createNode(token); + m_scanner->next(); + } + else if (token == Token::VAR) + { + if (!_allowVar) + throwExpectationError("Expected explicit type name."); + m_scanner->next(); + } + else if (token == Token::MAPPING) + { + type = parseMapping(); + } + else if (token == Token::IDENTIFIER) + { + ASTNodeFactory nodeFactory(*this); + nodeFactory.markEndPosition(); + type = nodeFactory.createNode(expectIdentifierToken()); + } + else + throwExpectationError("Expected type name"); + return type; +} + +ASTPointer Parser::parseMapping() +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::MAPPING); + expectToken(Token::LPAREN); + if (!Token::isElementaryTypeName(m_scanner->getCurrentToken())) + throwExpectationError("Expected elementary type name for mapping key type"); + ASTPointer keyType; + keyType = ASTNodeFactory(*this).createNode(m_scanner->getCurrentToken()); + m_scanner->next(); + expectToken(Token::ARROW); + bool const allowVar = false; + ASTPointer valueType = parseTypeName(allowVar); + nodeFactory.markEndPosition(); + expectToken(Token::RPAREN); + return nodeFactory.createNode(keyType, valueType); +} + +ASTPointer Parser::parseParameterList(bool _allowEmpty) +{ + ASTNodeFactory nodeFactory(*this); + std::vector> parameters; + expectToken(Token::LPAREN); + if (!_allowEmpty || m_scanner->getCurrentToken() != Token::RPAREN) + { + bool const allowVar = false; + parameters.push_back(parseVariableDeclaration(allowVar)); + while (m_scanner->getCurrentToken() != Token::RPAREN) + { + expectToken(Token::COMMA); + parameters.push_back(parseVariableDeclaration(allowVar)); + } + } + nodeFactory.markEndPosition(); + m_scanner->next(); + return nodeFactory.createNode(parameters); +} + +ASTPointer Parser::parseBlock() +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::LBRACE); + std::vector> statements; + while (m_scanner->getCurrentToken() != Token::RBRACE) + statements.push_back(parseStatement()); + nodeFactory.markEndPosition(); + expectToken(Token::RBRACE); + return nodeFactory.createNode(statements); +} + +ASTPointer Parser::parseStatement() +{ + ASTPointer statement; + switch (m_scanner->getCurrentToken()) + { + case Token::IF: + return parseIfStatement(); + case Token::WHILE: + return parseWhileStatement(); + case Token::LBRACE: + return parseBlock(); + // starting from here, all statements must be terminated by a semicolon + case Token::CONTINUE: + statement = ASTNodeFactory(*this).createNode(); + break; + case Token::BREAK: + statement = ASTNodeFactory(*this).createNode(); + break; + case Token::RETURN: + { + ASTNodeFactory nodeFactory(*this); + ASTPointer expression; + if (m_scanner->next() != Token::SEMICOLON) + { + expression = parseExpression(); + nodeFactory.setEndPositionFromNode(expression); + } + statement = nodeFactory.createNode(expression); + } + break; + default: + // distinguish between variable definition (and potentially assignment) and expressions + // (which include assignments to other expressions and pre-declared variables) + // We have a variable definition if we ge a keyword that specifies a type name, or + // in the case of a user-defined type, we have two identifiers following each other. + if (m_scanner->getCurrentToken() == Token::MAPPING || + m_scanner->getCurrentToken() == Token::VAR || + Token::isElementaryTypeName(m_scanner->getCurrentToken()) || + (m_scanner->getCurrentToken() == Token::IDENTIFIER && + m_scanner->peekNextToken() == Token::IDENTIFIER)) + statement = parseVariableDefinition(); + else // "ordinary" expression + statement = parseExpression(); + } + expectToken(Token::SEMICOLON); + return statement; +} + +ASTPointer Parser::parseIfStatement() +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::IF); + expectToken(Token::LPAREN); + ASTPointer condition = parseExpression(); + expectToken(Token::RPAREN); + ASTPointer trueBody = parseStatement(); + ASTPointer falseBody; + if (m_scanner->getCurrentToken() == Token::ELSE) + { + m_scanner->next(); + falseBody = parseStatement(); + nodeFactory.setEndPositionFromNode(falseBody); + } + else + nodeFactory.setEndPositionFromNode(trueBody); + return nodeFactory.createNode(condition, trueBody, falseBody); +} + +ASTPointer Parser::parseWhileStatement() +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::WHILE); + expectToken(Token::LPAREN); + ASTPointer condition = parseExpression(); + expectToken(Token::RPAREN); + ASTPointer body = parseStatement(); + nodeFactory.setEndPositionFromNode(body); + return nodeFactory.createNode(condition, body); +} + +ASTPointer Parser::parseVariableDefinition() +{ + ASTNodeFactory nodeFactory(*this); + bool const allowVar = true; + ASTPointer variable = parseVariableDeclaration(allowVar); + ASTPointer value; + if (m_scanner->getCurrentToken() == Token::ASSIGN) + { + m_scanner->next(); + value = parseExpression(); + nodeFactory.setEndPositionFromNode(value); + } + else + nodeFactory.setEndPositionFromNode(variable); + return nodeFactory.createNode(variable, value); +} + +ASTPointer Parser::parseExpression() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer expression = parseBinaryExpression(); + if (!Token::isAssignmentOp(m_scanner->getCurrentToken())) + return expression; + Token::Value assignmentOperator = expectAssignmentOperator(); + ASTPointer rightHandSide = parseExpression(); + nodeFactory.setEndPositionFromNode(rightHandSide); + return nodeFactory.createNode(expression, assignmentOperator, rightHandSide); +} + +ASTPointer Parser::parseBinaryExpression(int _minPrecedence) +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer expression = parseUnaryExpression(); + int precedence = Token::precedence(m_scanner->getCurrentToken()); + for (; precedence >= _minPrecedence; --precedence) + { + while (Token::precedence(m_scanner->getCurrentToken()) == precedence) + { + Token::Value op = m_scanner->getCurrentToken(); + m_scanner->next(); + ASTPointer right = parseBinaryExpression(precedence + 1); + nodeFactory.setEndPositionFromNode(right); + expression = nodeFactory.createNode(expression, op, right); + } + } + return expression; +} + +ASTPointer Parser::parseUnaryExpression() +{ + ASTNodeFactory nodeFactory(*this); + Token::Value token = m_scanner->getCurrentToken(); + if (Token::isUnaryOp(token) || Token::isCountOp(token)) + { + // prefix expression + m_scanner->next(); + ASTPointer subExpression = parseUnaryExpression(); + nodeFactory.setEndPositionFromNode(subExpression); + return nodeFactory.createNode(token, subExpression, true); + } + else + { + // potential postfix expression + ASTPointer subExpression = parseLeftHandSideExpression(); + token = m_scanner->getCurrentToken(); + if (!Token::isCountOp(token)) + return subExpression; + nodeFactory.markEndPosition(); + m_scanner->next(); + return nodeFactory.createNode(token, subExpression, false); + } +} + +ASTPointer Parser::parseLeftHandSideExpression() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer expression = parsePrimaryExpression(); + while (true) + { + switch (m_scanner->getCurrentToken()) + { + case Token::LBRACK: + { + m_scanner->next(); + ASTPointer index = parseExpression(); + nodeFactory.markEndPosition(); + expectToken(Token::RBRACK); + expression = nodeFactory.createNode(expression, index); + } + break; + case Token::PERIOD: + { + m_scanner->next(); + nodeFactory.markEndPosition(); + expression = nodeFactory.createNode(expression, expectIdentifierToken()); + } + break; + case Token::LPAREN: + { + m_scanner->next(); + std::vector> arguments = parseFunctionCallArguments(); + nodeFactory.markEndPosition(); + expectToken(Token::RPAREN); + expression = nodeFactory.createNode(expression, arguments); + } + break; + default: + return expression; + } + } +} + +ASTPointer Parser::parsePrimaryExpression() +{ + ASTNodeFactory nodeFactory(*this); + Token::Value token = m_scanner->getCurrentToken(); + ASTPointer expression; + switch (token) + { + case Token::TRUE_LITERAL: + case Token::FALSE_LITERAL: + expression = nodeFactory.createNode(token, ASTPointer()); + m_scanner->next(); + break; + case Token::NUMBER: + case Token::STRING_LITERAL: + nodeFactory.markEndPosition(); + expression = nodeFactory.createNode(token, getLiteralAndAdvance()); + break; + case Token::IDENTIFIER: + nodeFactory.markEndPosition(); + expression = nodeFactory.createNode(getLiteralAndAdvance()); + break; + case Token::LPAREN: + { + m_scanner->next(); + ASTPointer expression = parseExpression(); + expectToken(Token::RPAREN); + return expression; + } + default: + if (Token::isElementaryTypeName(token)) + { + // used for casts + expression = nodeFactory.createNode(token); + m_scanner->next(); + } + else + { + throwExpectationError("Expected primary expression."); + return ASTPointer(); // this is not reached + } + break; + } + return expression; +} + +std::vector> Parser::parseFunctionCallArguments() +{ + std::vector> arguments; + if (m_scanner->getCurrentToken() != Token::RPAREN) + { + arguments.push_back(parseExpression()); + while (m_scanner->getCurrentToken() != Token::RPAREN) + { + expectToken(Token::COMMA); + arguments.push_back(parseExpression()); + } + } + return arguments; +} + +void Parser::expectToken(Token::Value _value) +{ + if (m_scanner->getCurrentToken() != _value) + throwExpectationError(std::string("Expected token ") + std::string(Token::getName(_value))); + m_scanner->next(); +} + +Token::Value Parser::expectAssignmentOperator() +{ + Token::Value op = m_scanner->getCurrentToken(); + if (!Token::isAssignmentOp(op)) + throwExpectationError(std::string("Expected assignment operator")); + m_scanner->next(); + return op; +} + +ASTPointer Parser::expectIdentifierToken() +{ + if (m_scanner->getCurrentToken() != Token::IDENTIFIER) + throwExpectationError("Expected identifier"); + return getLiteralAndAdvance(); +} + +ASTPointer Parser::getLiteralAndAdvance() +{ + ASTPointer identifier = std::make_shared(m_scanner->getCurrentLiteral()); + m_scanner->next(); + return identifier; +} + +void Parser::throwExpectationError(std::string const& _description) +{ + //@todo put some of this stuff into ParserError + int line, column; + std::tie(line, column) = m_scanner->translatePositionToLineColumn(getPosition()); + std::stringstream buf; + buf << "Solidity parser error: " << _description + << " at line " << (line + 1) + << ", column " << (column + 1) << "\n" + << m_scanner->getLineAtPosition(getPosition()) << "\n" + << std::string(column, ' ') << "^"; + BOOST_THROW_EXCEPTION(ParserError() << errinfo_comment(buf.str())); +} + + +} +} diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h new file mode 100644 index 000000000..7cc415136 --- /dev/null +++ b/libsolidity/Parser.h @@ -0,0 +1,83 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Solidity parser. + */ + +#pragma once + +#include "libsolidity/AST.h" + +namespace dev +{ +namespace solidity +{ + +class Scanner; + +class Parser +{ +public: + ASTPointer parse(std::shared_ptr const& _scanner); + +private: + class ASTNodeFactory; + + /// Start position of the current token + int getPosition() const; + /// End position of the current token + int getEndPosition() const; + + /// Parsing functions for the AST nodes + /// @{ + ASTPointer parseContractDefinition(); + ASTPointer parseFunctionDefinition(bool _isPublic); + ASTPointer parseStructDefinition(); + ASTPointer parseVariableDeclaration(bool _allowVar); + ASTPointer parseTypeName(bool _allowVar); + ASTPointer parseMapping(); + ASTPointer parseParameterList(bool _allowEmpty = true); + ASTPointer parseBlock(); + ASTPointer parseStatement(); + ASTPointer parseIfStatement(); + ASTPointer parseWhileStatement(); + ASTPointer parseVariableDefinition(); + ASTPointer parseExpression(); + ASTPointer parseBinaryExpression(int _minPrecedence = 4); + ASTPointer parseUnaryExpression(); + ASTPointer parseLeftHandSideExpression(); + ASTPointer parsePrimaryExpression(); + std::vector> parseFunctionCallArguments(); + /// @} + + /// Helper functions + /// @{ + /// If current token value is not _value, throw exception otherwise advance token. + void expectToken(Token::Value _value); + Token::Value expectAssignmentOperator(); + ASTPointer expectIdentifierToken(); + ASTPointer getLiteralAndAdvance(); + void throwExpectationError(std::string const& _description); + /// @} + + std::shared_ptr m_scanner; +}; + +} +} diff --git a/libsolidity/Scanner.cpp b/libsolidity/Scanner.cpp new file mode 100644 index 000000000..35da248a4 --- /dev/null +++ b/libsolidity/Scanner.cpp @@ -0,0 +1,702 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . + + This file is derived from the file "scanner.cc", which was part of the + V8 project. The original copyright header follows: + + Copyright 2006-2012, the V8 project authors. All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/** + * @author Christian + * @date 2014 + * Solidity scanner. + */ + +#include +#include + +#include + +namespace dev +{ +namespace solidity +{ + +namespace +{ +bool IsDecimalDigit(char c) +{ + return '0' <= c && c <= '9'; +} +bool IsHexDigit(char c) +{ + return IsDecimalDigit(c) + || ('a' <= c && c <= 'f') + || ('A' <= c && c <= 'F'); +} +bool IsLineTerminator(char c) +{ + return c == '\n'; +} +bool IsWhiteSpace(char c) +{ + return c == ' ' || c == '\n' || c == '\t'; +} +bool IsIdentifierStart(char c) +{ + return c == '_' || c == '$' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); +} +bool IsIdentifierPart(char c) +{ + return IsIdentifierStart(c) || IsDecimalDigit(c); +} + +int HexValue(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else return -1; +} +} // end anonymous namespace + +Scanner::Scanner(CharStream const& _source) +{ + reset(_source); +} + +void Scanner::reset(CharStream const& _source) +{ + m_source = _source; + m_char = m_source.get(); + skipWhitespace(); + scanToken(); + next(); +} + + +bool Scanner::scanHexNumber(char& o_scannedNumber, int _expectedLength) +{ + BOOST_ASSERT(_expectedLength <= 4); // prevent overflow + char x = 0; + for (int i = 0; i < _expectedLength; i++) + { + int d = HexValue(m_char); + if (d < 0) + { + rollback(i); + return false; + } + x = x * 16 + d; + advance(); + } + o_scannedNumber = x; + return true; +} + + +// Ensure that tokens can be stored in a byte. +BOOST_STATIC_ASSERT(Token::NUM_TOKENS <= 0x100); + +Token::Value Scanner::next() +{ + m_current_token = m_next_token; + scanToken(); + return m_current_token.token; +} + +Token::Value Scanner::selectToken(char _next, Token::Value _then, Token::Value _else) +{ + advance(); + if (m_char == _next) + return selectToken(_then); + else + return _else; +} + + +bool Scanner::skipWhitespace() +{ + int const start_position = getSourcePos(); + while (IsWhiteSpace(m_char)) + advance(); + // Return whether or not we skipped any characters. + return getSourcePos() != start_position; +} + + +Token::Value Scanner::skipSingleLineComment() +{ + // The line terminator at the end of the line is not considered + // to be part of the single-line comment; it is recognized + // separately by the lexical grammar and becomes part of the + // stream of input elements for the syntactic grammar + while (advance() && !IsLineTerminator(m_char)) { }; + return Token::WHITESPACE; +} + +Token::Value Scanner::skipMultiLineComment() +{ + BOOST_ASSERT(m_char == '*'); + advance(); + while (!isSourcePastEndOfInput()) + { + char ch = m_char; + advance(); + + // If we have reached the end of the multi-line comment, we + // consume the '/' and insert a whitespace. This way all + // multi-line comments are treated as whitespace. + if (ch == '*' && m_char == '/') + { + m_char = ' '; + return Token::WHITESPACE; + } + } + // Unterminated multi-line comment. + return Token::ILLEGAL; +} + +void Scanner::scanToken() +{ + m_next_token.literal.clear(); + Token::Value token; + do + { + // Remember the position of the next token + m_next_token.location.start = getSourcePos(); + switch (m_char) + { + case '\n': // fall-through + case ' ': + case '\t': + token = selectToken(Token::WHITESPACE); + break; + case '"': + case '\'': + token = scanString(); + break; + case '<': + // < <= << <<= + advance(); + if (m_char == '=') + token = selectToken(Token::LTE); + else if (m_char == '<') + token = selectToken('=', Token::ASSIGN_SHL, Token::SHL); + else + token = Token::LT; + break; + case '>': + // > >= >> >>= >>> >>>= + advance(); + if (m_char == '=') + token = selectToken(Token::GTE); + else if (m_char == '>') + { + // >> >>= >>> >>>= + advance(); + if (m_char == '=') + token = selectToken(Token::ASSIGN_SAR); + else if (m_char == '>') + token = selectToken('=', Token::ASSIGN_SHR, Token::SHR); + else + token = Token::SAR; + } + else + token = Token::GT; + break; + case '=': + // = == => + advance(); + if (m_char == '=') + token = selectToken(Token::EQ); + else if (m_char == '>') + token = selectToken(Token::ARROW); + else + token = Token::ASSIGN; + break; + case '!': + // ! != + advance(); + if (m_char == '=') + token = selectToken(Token::NE); + else + token = Token::NOT; + break; + case '+': + // + ++ += + advance(); + if (m_char == '+') + token = selectToken(Token::INC); + else if (m_char == '=') + token = selectToken(Token::ASSIGN_ADD); + else + token = Token::ADD; + break; + case '-': + // - -- -= + advance(); + if (m_char == '-') + { + advance(); + token = Token::DEC; + } + else if (m_char == '=') + token = selectToken(Token::ASSIGN_SUB); + else + token = Token::SUB; + break; + case '*': + // * *= + token = selectToken('=', Token::ASSIGN_MUL, Token::MUL); + break; + case '%': + // % %= + token = selectToken('=', Token::ASSIGN_MOD, Token::MOD); + break; + case '/': + // / // /* /= + advance(); + if (m_char == '/') + token = skipSingleLineComment(); + else if (m_char == '*') + token = skipMultiLineComment(); + else if (m_char == '=') + token = selectToken(Token::ASSIGN_DIV); + else + token = Token::DIV; + break; + case '&': + // & && &= + advance(); + if (m_char == '&') + token = selectToken(Token::AND); + else if (m_char == '=') + token = selectToken(Token::ASSIGN_BIT_AND); + else + token = Token::BIT_AND; + break; + case '|': + // | || |= + advance(); + if (m_char == '|') + token = selectToken(Token::OR); + else if (m_char == '=') + token = selectToken(Token::ASSIGN_BIT_OR); + else + token = Token::BIT_OR; + break; + case '^': + // ^ ^= + token = selectToken('=', Token::ASSIGN_BIT_XOR, Token::BIT_XOR); + break; + case '.': + // . Number + advance(); + if (IsDecimalDigit(m_char)) + token = scanNumber(true); + else + token = Token::PERIOD; + break; + case ':': + token = selectToken(Token::COLON); + break; + case ';': + token = selectToken(Token::SEMICOLON); + break; + case ',': + token = selectToken(Token::COMMA); + break; + case '(': + token = selectToken(Token::LPAREN); + break; + case ')': + token = selectToken(Token::RPAREN); + break; + case '[': + token = selectToken(Token::LBRACK); + break; + case ']': + token = selectToken(Token::RBRACK); + break; + case '{': + token = selectToken(Token::LBRACE); + break; + case '}': + token = selectToken(Token::RBRACE); + break; + case '?': + token = selectToken(Token::CONDITIONAL); + break; + case '~': + token = selectToken(Token::BIT_NOT); + break; + default: + if (IsIdentifierStart(m_char)) + token = scanIdentifierOrKeyword(); + else if (IsDecimalDigit(m_char)) + token = scanNumber(false); + else if (skipWhitespace()) + token = Token::WHITESPACE; + else if (isSourcePastEndOfInput()) + token = Token::EOS; + else + token = selectToken(Token::ILLEGAL); + break; + } + // Continue scanning for tokens as long as we're just skipping + // whitespace. + } + while (token == Token::WHITESPACE); + m_next_token.location.end = getSourcePos(); + m_next_token.token = token; +} + +bool Scanner::scanEscape() +{ + char c = m_char; + advance(); + // Skip escaped newlines. + if (IsLineTerminator(c)) + return true; + switch (c) + { + case '\'': // fall through + case '"': // fall through + case '\\': + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'u': + if (!scanHexNumber(c, 4)) + return false; + break; + case 'v': + c = '\v'; + break; + case 'x': + if (!scanHexNumber(c, 2)) + return false; + break; + } + + addLiteralChar(c); + return true; +} + +Token::Value Scanner::scanString() +{ + char const quote = m_char; + advance(); // consume quote + LiteralScope literal(this); + while (m_char != quote && !isSourcePastEndOfInput() && !IsLineTerminator(m_char)) + { + char c = m_char; + advance(); + if (c == '\\') + { + if (isSourcePastEndOfInput() || !scanEscape()) + return Token::ILLEGAL; + } + else + addLiteralChar(c); + } + if (m_char != quote) return Token::ILLEGAL; + literal.Complete(); + advance(); // consume quote + return Token::STRING_LITERAL; +} + + +void Scanner::scanDecimalDigits() +{ + while (IsDecimalDigit(m_char)) + addLiteralCharAndAdvance(); +} + + +Token::Value Scanner::scanNumber(bool _periodSeen) +{ + BOOST_ASSERT(IsDecimalDigit(m_char)); // the first digit of the number or the fraction + enum { DECIMAL, HEX, OCTAL, IMPLICIT_OCTAL, BINARY } kind = DECIMAL; + LiteralScope literal(this); + if (_periodSeen) + { + // we have already seen a decimal point of the float + addLiteralChar('.'); + scanDecimalDigits(); // we know we have at least one digit + } + else + { + // if the first character is '0' we must check for octals and hex + if (m_char == '0') + { + addLiteralCharAndAdvance(); + // either 0, 0exxx, 0Exxx, 0.xxx, a hex number, a binary number or + // an octal number. + if (m_char == 'x' || m_char == 'X') + { + // hex number + kind = HEX; + addLiteralCharAndAdvance(); + if (!IsHexDigit(m_char)) + return Token::ILLEGAL; // we must have at least one hex digit after 'x'/'X' + while (IsHexDigit(m_char)) + addLiteralCharAndAdvance(); + } + } + // Parse decimal digits and allow trailing fractional part. + if (kind == DECIMAL) + { + scanDecimalDigits(); // optional + if (m_char == '.') + { + addLiteralCharAndAdvance(); + scanDecimalDigits(); // optional + } + } + } + // scan exponent, if any + if (m_char == 'e' || m_char == 'E') + { + BOOST_ASSERT(kind != HEX); // 'e'/'E' must be scanned as part of the hex number + if (kind != DECIMAL) return Token::ILLEGAL; + // scan exponent + addLiteralCharAndAdvance(); + if (m_char == '+' || m_char == '-') + addLiteralCharAndAdvance(); + if (!IsDecimalDigit(m_char)) + return Token::ILLEGAL; // we must have at least one decimal digit after 'e'/'E' + scanDecimalDigits(); + } + // The source character immediately following a numeric literal must + // not be an identifier start or a decimal digit; see ECMA-262 + // section 7.8.3, page 17 (note that we read only one decimal digit + // if the value is 0). + if (IsDecimalDigit(m_char) || IsIdentifierStart(m_char)) + return Token::ILLEGAL; + literal.Complete(); + return Token::NUMBER; +} + + +// ---------------------------------------------------------------------------- +// Keyword Matcher + +#define KEYWORDS(KEYWORD_GROUP, KEYWORD) \ + KEYWORD_GROUP('a') \ + KEYWORD("address", Token::ADDRESS) \ + KEYWORD_GROUP('b') \ + KEYWORD("break", Token::BREAK) \ + KEYWORD("bool", Token::BOOL) \ + KEYWORD_GROUP('c') \ + KEYWORD("case", Token::CASE) \ + KEYWORD("const", Token::CONST) \ + KEYWORD("continue", Token::CONTINUE) \ + KEYWORD("contract", Token::CONTRACT) \ + KEYWORD_GROUP('d') \ + KEYWORD("default", Token::DEFAULT) \ + KEYWORD("delete", Token::DELETE) \ + KEYWORD("do", Token::DO) \ + KEYWORD_GROUP('e') \ + KEYWORD("else", Token::ELSE) \ + KEYWORD("extends", Token::EXTENDS) \ + KEYWORD_GROUP('f') \ + KEYWORD("false", Token::FALSE_LITERAL) \ + KEYWORD("for", Token::FOR) \ + KEYWORD("function", Token::FUNCTION) \ + KEYWORD_GROUP('h') \ + KEYWORD("hash", Token::HASH) \ + KEYWORD("hash32", Token::HASH32) \ + KEYWORD("hash64", Token::HASH64) \ + KEYWORD("hash128", Token::HASH128) \ + KEYWORD("hash256", Token::HASH256) \ + KEYWORD_GROUP('i') \ + KEYWORD("if", Token::IF) \ + KEYWORD("in", Token::IN) \ + KEYWORD("int", Token::INT) \ + KEYWORD("int32", Token::INT32) \ + KEYWORD("int64", Token::INT64) \ + KEYWORD("int128", Token::INT128) \ + KEYWORD("int256", Token::INT256) \ + KEYWORD_GROUP('l') \ + KEYWORD_GROUP('m') \ + KEYWORD("mapping", Token::MAPPING) \ + KEYWORD_GROUP('n') \ + KEYWORD("new", Token::NEW) \ + KEYWORD("null", Token::NULL_LITERAL) \ + KEYWORD_GROUP('p') \ + KEYWORD("private", Token::PRIVATE) \ + KEYWORD("public", Token::PUBLIC) \ + KEYWORD_GROUP('r') \ + KEYWORD("real", Token::REAL) \ + KEYWORD("return", Token::RETURN) \ + KEYWORD("returns", Token::RETURNS) \ + KEYWORD_GROUP('s') \ + KEYWORD("string", Token::STRING_TYPE) \ + KEYWORD("struct", Token::STRUCT) \ + KEYWORD("switch", Token::SWITCH) \ + KEYWORD_GROUP('t') \ + KEYWORD("text", Token::TEXT) \ + KEYWORD("this", Token::THIS) \ + KEYWORD("true", Token::TRUE_LITERAL) \ + KEYWORD_GROUP('u') \ + KEYWORD("uint", Token::UINT) \ + KEYWORD("uint32", Token::UINT32) \ + KEYWORD("uint64", Token::UINT64) \ + KEYWORD("uint128", Token::UINT128) \ + KEYWORD("uint256", Token::UINT256) \ + KEYWORD("ureal", Token::UREAL) \ + KEYWORD_GROUP('v') \ + KEYWORD("var", Token::VAR) \ + KEYWORD_GROUP('w') \ + KEYWORD("while", Token::WHILE) \ + + +static Token::Value KeywordOrIdentifierToken(std::string const& input) +{ + BOOST_ASSERT(!input.empty()); + int const kMinLength = 2; + int const kMaxLength = 10; + if (input.size() < kMinLength || input.size() > kMaxLength) + return Token::IDENTIFIER; + switch (input[0]) + { + default: +#define KEYWORD_GROUP_CASE(ch) \ + break; \ +case ch: +#define KEYWORD(keyword, token) \ + { \ + /* 'keyword' is a char array, so sizeof(keyword) is */ \ + /* strlen(keyword) plus 1 for the NUL char. */ \ + int const keyword_length = sizeof(keyword) - 1; \ + BOOST_STATIC_ASSERT(keyword_length >= kMinLength); \ + BOOST_STATIC_ASSERT(keyword_length <= kMaxLength); \ + if (input == keyword) \ + return token; \ + } + KEYWORDS(KEYWORD_GROUP_CASE, KEYWORD) + } + return Token::IDENTIFIER; +} + +Token::Value Scanner::scanIdentifierOrKeyword() +{ + BOOST_ASSERT(IsIdentifierStart(m_char)); + LiteralScope literal(this); + addLiteralCharAndAdvance(); + // Scan the rest of the identifier characters. + while (IsIdentifierPart(m_char)) + addLiteralCharAndAdvance(); + literal.Complete(); + return KeywordOrIdentifierToken(m_next_token.literal); +} + +char CharStream::advanceAndGet() +{ + if (isPastEndOfInput()) + return 0; + ++m_pos; + if (isPastEndOfInput()) + return 0; + return get(); +} + +char CharStream::rollback(size_t _amount) +{ + BOOST_ASSERT(m_pos >= _amount); + m_pos -= _amount; + return get(); +} + +std::string CharStream::getLineAtPosition(int _position) const +{ + // if _position points to \n, it returns the line before the \n + using size_type = std::string::size_type; + size_type searchStart = std::min(m_source.size(), _position); + if (searchStart > 0) + searchStart--; + size_type lineStart = m_source.rfind('\n', searchStart); + if (lineStart == std::string::npos) + lineStart = 0; + else + lineStart++; + return m_source.substr(lineStart, + std::min(m_source.find('\n', lineStart), + m_source.size()) - lineStart); +} + +std::tuple CharStream::translatePositionToLineColumn(int _position) const +{ + using size_type = std::string::size_type; + size_type searchPosition = std::min(m_source.size(), _position); + int lineNumber = std::count(m_source.begin(), m_source.begin() + searchPosition, '\n'); + size_type lineStart; + if (searchPosition == 0) + lineStart = 0; + else + { + lineStart = m_source.rfind('\n', searchPosition - 1); + lineStart = lineStart == std::string::npos ? 0 : lineStart + 1; + } + return std::tuple(lineNumber, searchPosition - lineStart); +} + + +} +} diff --git a/libsolidity/Scanner.h b/libsolidity/Scanner.h new file mode 100644 index 000000000..adae10dca --- /dev/null +++ b/libsolidity/Scanner.h @@ -0,0 +1,204 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . + + This file is derived from the file "scanner.h", which was part of the + V8 project. The original copyright header follows: + + Copyright 2006-2012, the V8 project authors. All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/** + * @author Christian + * @date 2014 + * Solidity scanner. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + + +class AstRawString; +class AstValueFactory; +class ParserRecorder; + +class CharStream +{ +public: + CharStream(): m_pos(0) {} + explicit CharStream(std::string const& _source): m_source(_source), m_pos(0) {} + int getPos() const { return m_pos; } + bool isPastEndOfInput() const { return m_pos >= m_source.size(); } + char get() const { return m_source[m_pos]; } + char advanceAndGet(); + char rollback(size_t _amount); + + /// Functions that help pretty-printing parse errors + /// Do only use in error cases, they are quite expensive. + /// @{ + std::string getLineAtPosition(int _position) const; + std::tuple translatePositionToLineColumn(int _position) const; + /// @} + +private: + std::string m_source; + size_t m_pos; +}; + + +class Scanner +{ +public: + // Scoped helper for literal recording. Automatically drops the literal + // if aborting the scanning before it's complete. + class LiteralScope + { + public: + explicit LiteralScope(Scanner* self): scanner_(self), complete_(false) { scanner_->startNewLiteral(); } + ~LiteralScope() { if (!complete_) scanner_->dropLiteral(); } + void Complete() { complete_ = true; } + + private: + Scanner* scanner_; + bool complete_; + }; + + explicit Scanner(CharStream const& _source); + + /// Resets the scanner as if newly constructed with _input as input. + void reset(CharStream const& _source); + + /// Returns the next token and advances input. + Token::Value next(); + + /// Information about the current token + /// @{ + + /// Returns the current token + Token::Value getCurrentToken() { return m_current_token.token; } + Location getCurrentLocation() const { return m_current_token.location; } + const std::string& getCurrentLiteral() const { return m_current_token.literal; } + /// @} + + /// Information about the next token + /// @{ + /// Returns the next token without advancing input. + Token::Value peekNextToken() const { return m_next_token.token; } + Location peekLocation() const { return m_next_token.location; } + const std::string& peekLiteral() const { return m_next_token.literal; } + /// @} + + /// Functions that help pretty-printing parse errors. + /// Do only use in error cases, they are quite expensive. + /// @{ + std::string getLineAtPosition(int _position) const { return m_source.getLineAtPosition(_position); } + std::tuple translatePositionToLineColumn(int _position) const { return m_source.translatePositionToLineColumn(_position); } + /// @} + +private: + // Used for the current and look-ahead token. + struct TokenDesc + { + Token::Value token; + Location location; + std::string literal; + }; + + /// Literal buffer support + /// @{ + inline void startNewLiteral() { m_next_token.literal.clear(); } + inline void addLiteralChar(char c) { m_next_token.literal.push_back(c); } + inline void dropLiteral() { m_next_token.literal.clear(); } + inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); } + /// @} + + bool advance() { m_char = m_source.advanceAndGet(); return !m_source.isPastEndOfInput(); } + void rollback(int _amount) { m_char = m_source.rollback(_amount); } + + inline Token::Value selectToken(Token::Value _tok) { advance(); return _tok; } + /// If the next character is _next, advance and return _then, otherwise return _else. + inline Token::Value selectToken(char _next, Token::Value _then, Token::Value _else); + + bool scanHexNumber(char& o_scannedNumber, int _expectedLength); + + // Scans a single JavaScript token. + void scanToken(); + + bool skipWhitespace(); + Token::Value skipSingleLineComment(); + Token::Value skipMultiLineComment(); + + void scanDecimalDigits(); + Token::Value scanNumber(bool _periodSeen); + Token::Value scanIdentifierOrKeyword(); + + Token::Value scanString(); + + /// Scans an escape-sequence which is part of a string and adds the + /// decoded character to the current literal. Returns true if a pattern + /// is scanned. + bool scanEscape(); + + /// Return the current source position. + int getSourcePos() { return m_source.getPos(); } + bool isSourcePastEndOfInput() { return m_source.isPastEndOfInput(); } + + TokenDesc m_current_token; // desc for current token (as returned by Next()) + TokenDesc m_next_token; // desc for next token (one token look-ahead) + + CharStream m_source; + + /// one character look-ahead, equals 0 at end of input + char m_char; +}; + +} +} diff --git a/libsolidity/Scope.cpp b/libsolidity/Scope.cpp new file mode 100644 index 000000000..28a54dd27 --- /dev/null +++ b/libsolidity/Scope.cpp @@ -0,0 +1,50 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Scope - object that holds declaration of names. + */ + +#include +#include + +namespace dev +{ +namespace solidity +{ + +bool Scope::registerDeclaration(Declaration& _declaration) +{ + if (m_declarations.find(_declaration.getName()) != m_declarations.end()) + return false; + m_declarations[_declaration.getName()] = &_declaration; + return true; +} + +Declaration* Scope::resolveName(ASTString const& _name, bool _recursive) const +{ + auto result = m_declarations.find(_name); + if (result != m_declarations.end()) + return result->second; + if (_recursive && m_outerScope != nullptr) + return m_outerScope->resolveName(_name, true); + return nullptr; +} + +} +} diff --git a/libsolidity/Scope.h b/libsolidity/Scope.h new file mode 100644 index 000000000..2e36e5281 --- /dev/null +++ b/libsolidity/Scope.h @@ -0,0 +1,51 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Scope - object that holds declaration of names. + */ + +#pragma once + +#include +#include + +#include + +namespace dev +{ +namespace solidity +{ + +class Scope +{ +public: + explicit Scope(Scope* _outerScope = nullptr): m_outerScope(_outerScope) {} + /// Registers the declaration in the scope unless its name is already declared. Returns true iff + /// it was not yet declared. + bool registerDeclaration(Declaration& _declaration); + Declaration* resolveName(ASTString const& _name, bool _recursive = false) const; + Scope* getOuterScope() const { return m_outerScope; } + +private: + Scope* m_outerScope; + std::map m_declarations; +}; + +} +} diff --git a/libsolidity/Token.cpp b/libsolidity/Token.cpp new file mode 100644 index 000000000..093bd9c1d --- /dev/null +++ b/libsolidity/Token.cpp @@ -0,0 +1,84 @@ +// Copyright 2006-2012, the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Modifications as part of cpp-ethereum under the following license: +// +// cpp-ethereum 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. +// +// cpp-ethereum 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 cpp-ethereum. If not, see . + +#include + +namespace dev +{ +namespace solidity +{ + +#define T(name, string, precedence) #name, +char const* const Token::m_name[NUM_TOKENS] = +{ + TOKEN_LIST(T, T) +}; +#undef T + + +#define T(name, string, precedence) string, +char const* const Token::m_string[NUM_TOKENS] = +{ + TOKEN_LIST(T, T) +}; +#undef T + + +#define T(name, string, precedence) precedence, +int8_t const Token::m_precedence[NUM_TOKENS] = +{ + TOKEN_LIST(T, T) +}; +#undef T + + +#define KT(a, b, c) 'T', +#define KK(a, b, c) 'K', +char const Token::m_tokenType[] = +{ + TOKEN_LIST(KT, KK) +}; +#undef KT +#undef KK + +} +} diff --git a/libsolidity/Token.h b/libsolidity/Token.h new file mode 100644 index 000000000..2db6e05de --- /dev/null +++ b/libsolidity/Token.h @@ -0,0 +1,334 @@ +// Copyright 2006-2012, the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Modifications as part of cpp-ethereum under the following license: +// +// cpp-ethereum 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. +// +// cpp-ethereum 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 cpp-ethereum. If not, see . + +#pragma once + +#include + +#include +#include + +namespace dev +{ +namespace solidity +{ + +// TOKEN_LIST takes a list of 3 macros M, all of which satisfy the +// same signature M(name, string, precedence), where name is the +// symbolic token name, string is the corresponding syntactic symbol +// (or NULL, for literals), and precedence is the precedence (or 0). +// The parameters are invoked for token categories as follows: +// +// T: Non-keyword tokens +// K: Keyword tokens + +// IGNORE_TOKEN is a convenience macro that can be supplied as +// an argument (at any position) for a TOKEN_LIST call. It does +// nothing with tokens belonging to the respective category. + +#define IGNORE_TOKEN(name, string, precedence) + +#define TOKEN_LIST(T, K) \ + /* End of source indicator. */ \ + T(EOS, "EOS", 0) \ + \ + /* Punctuators (ECMA-262, section 7.7, page 15). */ \ + T(LPAREN, "(", 0) \ + T(RPAREN, ")", 0) \ + T(LBRACK, "[", 0) \ + T(RBRACK, "]", 0) \ + T(LBRACE, "{", 0) \ + T(RBRACE, "}", 0) \ + T(COLON, ":", 0) \ + T(SEMICOLON, ";", 0) \ + T(PERIOD, ".", 0) \ + T(CONDITIONAL, "?", 3) \ + T(INC, "++", 0) \ + T(DEC, "--", 0) \ + T(ARROW, "=>", 0) \ + \ + /* Assignment operators. */ \ + /* IsAssignmentOp() relies on this block of enum values being */ \ + /* contiguous and sorted in the same order!*/ \ + T(ASSIGN, "=", 2) \ + /* The following have to be in exactly the same order as the simple binary operators*/ \ + T(ASSIGN_BIT_OR, "|=", 2) \ + T(ASSIGN_BIT_XOR, "^=", 2) \ + T(ASSIGN_BIT_AND, "&=", 2) \ + T(ASSIGN_SHL, "<<=", 2) \ + T(ASSIGN_SAR, ">>=", 2) \ + T(ASSIGN_SHR, ">>>=", 2) \ + T(ASSIGN_ADD, "+=", 2) \ + T(ASSIGN_SUB, "-=", 2) \ + T(ASSIGN_MUL, "*=", 2) \ + T(ASSIGN_DIV, "/=", 2) \ + T(ASSIGN_MOD, "%=", 2) \ + \ + /* Binary operators sorted by precedence. */ \ + /* IsBinaryOp() relies on this block of enum values */ \ + /* being contiguous and sorted in the same order! */ \ + T(COMMA, ",", 1) \ + T(OR, "||", 4) \ + T(AND, "&&", 5) \ + T(BIT_OR, "|", 6) \ + T(BIT_XOR, "^", 7) \ + T(BIT_AND, "&", 8) \ + T(SHL, "<<", 11) \ + T(SAR, ">>", 11) \ + T(SHR, ">>>", 11) \ + T(ADD, "+", 12) \ + T(SUB, "-", 12) \ + T(MUL, "*", 13) \ + T(DIV, "/", 13) \ + T(MOD, "%", 13) \ + \ + /* Compare operators sorted by precedence. */ \ + /* IsCompareOp() relies on this block of enum values */ \ + /* being contiguous and sorted in the same order! */ \ + T(EQ, "==", 9) \ + T(NE, "!=", 9) \ + T(LT, "<", 10) \ + T(GT, ">", 10) \ + T(LTE, "<=", 10) \ + T(GTE, ">=", 10) \ + K(IN, "in", 10) \ + \ + /* Unary operators. */ \ + /* IsUnaryOp() relies on this block of enum values */ \ + /* being contiguous and sorted in the same order! */ \ + T(NOT, "!", 0) \ + T(BIT_NOT, "~", 0) \ + K(DELETE, "delete", 0) \ + \ + /* Keywords */ \ + K(BREAK, "break", 0) \ + K(CASE, "case", 0) \ + K(CONST, "const", 0) \ + K(CONTINUE, "continue", 0) \ + K(CONTRACT, "contract", 0) \ + K(DEFAULT, "default", 0) \ + K(DO, "do", 0) \ + K(ELSE, "else", 0) \ + K(EXTENDS, "extends", 0) \ + K(FOR, "for", 0) \ + K(FUNCTION, "function", 0) \ + K(IF, "if", 0) \ + K(IMPORT, "import", 0) \ + K(MAPPING, "mapping", 0) \ + K(NEW, "new", 0) \ + K(PUBLIC, "public", 0) \ + K(PRIVATE, "private", 0) \ + K(RETURN, "return", 0) \ + K(RETURNS, "returns", 0) \ + K(STRUCT, "struct", 0) \ + K(SWITCH, "switch", 0) \ + K(THIS, "this", 0) \ + K(VAR, "var", 0) \ + K(WHILE, "while", 0) \ + \ + \ + /* type keywords, keep them in this order, keep int as first keyword + * the implementation in Types.cpp has to be synced to this here + * TODO more to be added */ \ + K(INT, "int", 0) \ + K(INT32, "int32", 0) \ + K(INT64, "int64", 0) \ + K(INT128, "int128", 0) \ + K(INT256, "int256", 0) \ + K(UINT, "uint", 0) \ + K(UINT32, "uint32", 0) \ + K(UINT64, "uint64", 0) \ + K(UINT128, "uint128", 0) \ + K(UINT256, "uint256", 0) \ + K(HASH, "hash", 0) \ + K(HASH32, "hash32", 0) \ + K(HASH64, "hash64", 0) \ + K(HASH128, "hash128", 0) \ + K(HASH256, "hash256", 0) \ + K(ADDRESS, "address", 0) \ + K(BOOL, "bool", 0) \ + K(STRING_TYPE, "string", 0) \ + K(TEXT, "text", 0) \ + K(REAL, "real", 0) \ + K(UREAL, "ureal", 0) \ + T(TYPES_END, NULL, 0) /* used as type enum end marker */ \ + \ + /* Literals */ \ + K(NULL_LITERAL, "null", 0) \ + K(TRUE_LITERAL, "true", 0) \ + K(FALSE_LITERAL, "false", 0) \ + T(NUMBER, NULL, 0) \ + T(STRING_LITERAL, NULL, 0) \ + \ + /* Identifiers (not keywords or future reserved words). */ \ + T(IDENTIFIER, NULL, 0) \ + \ + /* Illegal token - not able to scan. */ \ + T(ILLEGAL, "ILLEGAL", 0) \ + \ + /* Scanner-internal use only. */ \ + T(WHITESPACE, NULL, 0) + + +class Token +{ +public: + // All token values. +#define T(name, string, precedence) name, + enum Value + { + TOKEN_LIST(T, T) + NUM_TOKENS + }; +#undef T + + // Returns a string corresponding to the C++ token name + // (e.g. "LT" for the token LT). + static char const* getName(Value tok) + { + BOOST_ASSERT(tok < NUM_TOKENS); // tok is unsigned + return m_name[tok]; + } + + // Predicates + static bool isKeyword(Value tok) { return m_tokenType[tok] == 'K'; } + static bool isIdentifier(Value tok) { return tok == IDENTIFIER; } + static bool isElementaryTypeName(Value tok) { return INT <= tok && tok < TYPES_END; } + static bool isAssignmentOp(Value tok) { return ASSIGN <= tok && tok <= ASSIGN_MOD; } + static bool isBinaryOp(Value op) { return COMMA <= op && op <= MOD; } + static bool isTruncatingBinaryOp(Value op) { return BIT_OR <= op && op <= SHR; } + static bool isCompareOp(Value op) { return EQ <= op && op <= IN; } + static bool isOrderedRelationalCompareOp(Value op) + { + return op == LT || op == LTE || op == GT || op == GTE; + } + static bool isEqualityOp(Value op) { return op == EQ; } + static bool isInequalityOp(Value op) { return op == NE; } + static bool isArithmeticCompareOp(Value op) + { + return isOrderedRelationalCompareOp(op) || + isEqualityOp(op) || isInequalityOp(op); + } + + static Value negateCompareOp(Value op) + { + BOOST_ASSERT(isArithmeticCompareOp(op)); + switch (op) + { + case EQ: + return NE; + case NE: + return EQ; + case LT: + return GTE; + case GT: + return LTE; + case LTE: + return GT; + case GTE: + return LT; + default: + BOOST_ASSERT(false); // should not get here + return op; + } + } + + static Value reverseCompareOp(Value op) + { + BOOST_ASSERT(isArithmeticCompareOp(op)); + switch (op) + { + case EQ: + return EQ; + case NE: + return NE; + case LT: + return GT; + case GT: + return LT; + case LTE: + return GTE; + case GTE: + return LTE; + default: + BOOST_ASSERT(false); // should not get here + return op; + } + } + + static Value AssignmentToBinaryOp(Value op) + { + BOOST_ASSERT(isAssignmentOp(op) && op != ASSIGN); + return Token::Value(op + (BIT_OR - ASSIGN_BIT_OR)); + } + + static bool isBitOp(Value op) { return (BIT_OR <= op && op <= SHR) || op == BIT_NOT; } + static bool isUnaryOp(Value op) { return (NOT <= op && op <= DELETE) || op == ADD || op == SUB; } + static bool isCountOp(Value op) { return op == INC || op == DEC; } + static bool isShiftOp(Value op) { return (SHL <= op) && (op <= SHR); } + + // Returns a string corresponding to the JS token string + // (.e., "<" for the token LT) or NULL if the token doesn't + // have a (unique) string (e.g. an IDENTIFIER). + static char const* toString(Value tok) + { + BOOST_ASSERT(tok < NUM_TOKENS); // tok is unsigned. + return m_string[tok]; + } + + // Returns the precedence > 0 for binary and compare + // operators; returns 0 otherwise. + static int precedence(Value tok) + { + BOOST_ASSERT(tok < NUM_TOKENS); // tok is unsigned. + return m_precedence[tok]; + } + +private: + static char const* const m_name[NUM_TOKENS]; + static char const* const m_string[NUM_TOKENS]; + static int8_t const m_precedence[NUM_TOKENS]; + static char const m_tokenType[NUM_TOKENS]; +}; + +} +} diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp new file mode 100644 index 000000000..7634951a1 --- /dev/null +++ b/libsolidity/Types.cpp @@ -0,0 +1,164 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Solidity data types + */ + +#include +#include + +namespace dev +{ +namespace solidity +{ + +std::shared_ptr Type::fromElementaryTypeName(Token::Value _typeToken) +{ + if (Token::INT <= _typeToken && _typeToken <= Token::HASH256) + { + int offset = _typeToken - Token::INT; + int bits = offset % 5; + if (bits == 0) + bits = 256; + else + bits = (1 << (bits - 1)) * 32; + int modifier = offset / 5; + return std::make_shared(bits, + modifier == 0 ? IntegerType::Modifier::SIGNED : + modifier == 1 ? IntegerType::Modifier::UNSIGNED : + IntegerType::Modifier::HASH); + } + else if (_typeToken == Token::ADDRESS) + return std::make_shared(0, IntegerType::Modifier::ADDRESS); + else if (_typeToken == Token::BOOL) + return std::make_shared(); + else + BOOST_ASSERT(false); // @todo add other tyes +} + +std::shared_ptr Type::fromUserDefinedTypeName(UserDefinedTypeName const& _typeName) +{ + return std::make_shared(*_typeName.getReferencedStruct()); +} + +std::shared_ptr Type::fromMapping(Mapping const&) +{ + BOOST_ASSERT(false); //@todo not yet implemented + return std::shared_ptr(); +} + +std::shared_ptr Type::forLiteral(Literal const& _literal) +{ + switch (_literal.getToken()) + { + case Token::TRUE_LITERAL: + case Token::FALSE_LITERAL: + return std::make_shared(); + case Token::NUMBER: + return IntegerType::smallestTypeForLiteral(_literal.getValue()); + case Token::STRING_LITERAL: + return std::shared_ptr(); // @todo + default: + return std::shared_ptr(); + } +} + +std::shared_ptr IntegerType::smallestTypeForLiteral(std::string const&) +{ + //@todo + return std::make_shared(256, Modifier::UNSIGNED); +} + +IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): + m_bits(_bits), m_modifier(_modifier) +{ + BOOST_ASSERT(_bits > 0 && _bits <= 256 && _bits % 8 == 0); + if (isAddress()) + _bits = 160; +} + +bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (_convertTo.getCategory() != Category::INTEGER) + return false; + IntegerType const& convertTo = dynamic_cast(_convertTo); + if (convertTo.m_bits < m_bits) + return false; + if (isAddress()) + return convertTo.isAddress(); + else if (isHash()) + return convertTo.isHash(); + else if (isSigned()) + return convertTo.isSigned(); + else + return !convertTo.isSigned() || convertTo.m_bits > m_bits; +} + +bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + return _convertTo.getCategory() == Category::INTEGER; +} + +bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const +{ + if (isAddress()) + return Token::isCompareOp(_operator); + else if (isHash()) + return Token::isCompareOp(_operator) || Token::isBitOp(_operator); + else + return true; +} + +bool IntegerType::acceptsUnaryOperator(Token::Value _operator) const +{ + return _operator == Token::DELETE || (!isAddress() && _operator == Token::BIT_NOT); +} + +bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + // conversion to integer is fine, but not to address + // this is an example of explicit conversions being not transitive (though implicit should be) + if (_convertTo.getCategory() == Category::INTEGER) + { + IntegerType const& convertTo = dynamic_cast(_convertTo); + if (!convertTo.isAddress()) + return true; + } + return isImplicitlyConvertibleTo(_convertTo); +} + +bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (_convertTo.getCategory() != Category::CONTRACT) + return false; + ContractType const& convertTo = dynamic_cast(_convertTo); + return &m_contract == &convertTo.m_contract; +} + +bool StructType::isImplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (_convertTo.getCategory() != Category::STRUCT) + return false; + StructType const& convertTo = dynamic_cast(_convertTo); + return &m_struct == &convertTo.m_struct; +} + + +} +} diff --git a/libsolidity/Types.h b/libsolidity/Types.h new file mode 100644 index 000000000..e0c09bdcf --- /dev/null +++ b/libsolidity/Types.h @@ -0,0 +1,180 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Solidity data types + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +// @todo realMxN, string, mapping + +class Type: private boost::noncopyable +{ +public: + enum class Category + { + INTEGER, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE + }; + + //! factory functions that convert an AST TypeName to a Type. + static std::shared_ptr fromElementaryTypeName(Token::Value _typeToken); + static std::shared_ptr fromUserDefinedTypeName(UserDefinedTypeName const& _typeName); + static std::shared_ptr fromMapping(Mapping const& _typeName); + + static std::shared_ptr forLiteral(Literal const& _literal); + + virtual Category getCategory() const = 0; + virtual bool isImplicitlyConvertibleTo(Type const&) const { return false; } + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const + { + return isImplicitlyConvertibleTo(_convertTo); + } + virtual bool acceptsBinaryOperator(Token::Value) const { return false; } + virtual bool acceptsUnaryOperator(Token::Value) const { return false; } +}; + +class IntegerType: public Type +{ +public: + enum class Modifier + { + UNSIGNED, SIGNED, HASH, ADDRESS + }; + virtual Category getCategory() const { return Category::INTEGER; } + + static std::shared_ptr smallestTypeForLiteral(std::string const& _literal); + + explicit IntegerType(int _bits, Modifier _modifier = Modifier::UNSIGNED); + + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool acceptsBinaryOperator(Token::Value _operator) const override; + virtual bool acceptsUnaryOperator(Token::Value _operator) const override; + + int getNumBits() const { return m_bits; } + bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; } + bool isAddress() const { return m_modifier == Modifier::ADDRESS; } + int isSigned() const { return m_modifier == Modifier::SIGNED; } + +private: + int m_bits; + Modifier m_modifier; +}; + +class BoolType: public Type +{ +public: + virtual Category getCategory() const { return Category::BOOL; } + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override + { + return _convertTo.getCategory() == Category::BOOL; + } + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool acceptsBinaryOperator(Token::Value _operator) const override + { + return _operator == Token::AND || _operator == Token::OR; + } + virtual bool acceptsUnaryOperator(Token::Value _operator) const override + { + return _operator == Token::NOT || _operator == Token::DELETE; + } +}; + +class ContractType: public Type +{ +public: + virtual Category getCategory() const { return Category::CONTRACT; } + ContractType(ContractDefinition const& _contract): m_contract(_contract) {} + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const; + +private: + ContractDefinition const& m_contract; +}; + +class StructType: public Type +{ +public: + virtual Category getCategory() const { return Category::STRUCT; } + StructType(StructDefinition const& _struct): m_struct(_struct) {} + virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const; + virtual bool acceptsUnaryOperator(Token::Value _operator) const override + { + return _operator == Token::DELETE; + } + +private: + StructDefinition const& m_struct; +}; + +class FunctionType: public Type +{ +public: + virtual Category getCategory() const { return Category::FUNCTION; } + FunctionType(FunctionDefinition const& _function): m_function(_function) {} + + FunctionDefinition const& getFunction() const { return m_function; } + +private: + FunctionDefinition const& m_function; +}; + +class MappingType: public Type +{ +public: + virtual Category getCategory() const { return Category::MAPPING; } + MappingType() {} +private: + //@todo +}; + +//@todo should be changed into "empty anonymous struct" +class VoidType: public Type +{ +public: + virtual Category getCategory() const { return Category::VOID; } + VoidType() {} +}; + +class TypeType: public Type +{ +public: + virtual Category getCategory() const { return Category::TYPE; } + TypeType(std::shared_ptr const& _actualType): m_actualType(_actualType) {} + + std::shared_ptr const& getActualType() const { return m_actualType; } + +private: + std::shared_ptr m_actualType; +}; + + +} +} diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt new file mode 100644 index 000000000..c0ab06074 --- /dev/null +++ b/libsolidity/grammar.txt @@ -0,0 +1,35 @@ +ContractDefinition = 'contract' Identifier '{' ContractPart* '}' +ContractPart = VariableDeclaration ';' | StructDefinition | + FunctionDefinition | 'public:' | 'private:' + +StructDefinition = 'struct' Identifier '{' + ( VariableDeclaration (';' VariableDeclaration)* )? '} + +FunctionDefinition = 'function' Identifier ParameterList 'const'? + ( 'returns' ParameterList )? Block +ParameterList = '(' ( VariableDeclaration (',' VariableDeclaration)* )? ')' +// semantic restriction: mappings and structs (recursively) containing mappings +// are not allowed in argument lists +VariableDeclaration = TypeName Identifier +TypeName = ElementaryTypeName | Identifier | Mapping +Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' + +Block = '{' Statement* '}' +Statement = IfStatement | WhileStatement | Block | + ( Continue | Break | Return | VariableDefinition | Expression ) ';' + +IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )? +WhileStatement = 'while' '(' Expression ')' Statement +Continue = 'continue' ';' +Break = 'break' ';' +Return = 'return' Expression? ';' +VariableDefinition = VariableDeclaration ( = Expression )? ';' + +Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | IndexAccess | + MemberAccess | PrimaryExpression +// The expression syntax is actually much more complicated +Assignment = Expression (AssignmentOp Expression) +FunctionCall = Expression '(' ( Expression ( ',' Expression )* ) ')' +MemberAccess = Expression '.' Identifier +IndexAccess = Expression '[' Expresison ']' +PrimaryExpression = Identifier | NumberLiteral | StringLiteral | ElementaryTypeName | '(' Expression ')' diff --git a/libwhisper/Interface.h b/libwhisper/Interface.h index 3b2ec7267..e131a3ada 100644 --- a/libwhisper/Interface.h +++ b/libwhisper/Interface.h @@ -76,10 +76,10 @@ public: virtual Envelope envelope(h256 _m) const = 0; - void sendRaw(bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_topic, _ttl, _workToProve)); } - void sendRaw(Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_to, _topic, _ttl, _workToProve)); } - void sendRaw(Secret _from, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_from, _topic, _ttl, _workToProve)); } - void sendRaw(Secret _from, Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_from, _to, _topic, _ttl, _workToProve)); } + void post(bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_topic, _ttl, _workToProve)); } + void post(Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_to, _topic, _ttl, _workToProve)); } + void post(Secret _from, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_from, _topic, _ttl, _workToProve)); } + void post(Secret _from, Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_from, _to, _topic, _ttl, _workToProve)); } }; struct WatshhChannel: public dev::LogChannel { static const char* name() { return "shh"; } static const int verbosity = 1; }; diff --git a/libwhisper/Message.cpp b/libwhisper/Message.cpp index 5d8466a2c..80b493076 100644 --- a/libwhisper/Message.cpp +++ b/libwhisper/Message.cpp @@ -32,7 +32,8 @@ Message::Message(Envelope const& _e, Secret const& _s) { bytes b; if (_s) - decrypt(_s, &(_e.data()), b); + if (!decrypt(_s, &(_e.data()), b)) + return; populate(_s ? b : _e.data()); m_to = KeyPair(_s).pub(); } @@ -49,9 +50,10 @@ void Message::populate(bytes const& _data) byte flags = _data[0]; if (!!(flags & ContainsSignature) && _data.size() > sizeof(Signature) + 1) // has a signature { - bytesConstRef payload = bytesConstRef(&_data).cropped(sizeof(Signature) + 1); + bytesConstRef payload = bytesConstRef(&_data).cropped(1, _data.size() - sizeof(Signature) - 1); h256 h = sha3(payload); - m_from = recover(*(Signature const*)&(_data[1]), h); + Signature const& sig = *(Signature const*)&(_data[1 + payload.size()]); + m_from = recover(sig, h); m_payload = payload.toBytes(); } else @@ -71,6 +73,8 @@ Envelope Message::seal(Secret _from, Topic const& _topic, unsigned _ttl, unsigne input.resize(1 + m_payload.size() + sizeof(Signature)); input[0] |= ContainsSignature; *(Signature*)&(input[1 + m_payload.size()]) = sign(_from, sha3(m_payload)); + // If this fails, the something is wrong with the sign-recover round-trip. + assert(recover(*(Signature*)&(input[1 + m_payload.size()]), sha3(m_payload)) == KeyPair(_from).pub()); } if (m_to) diff --git a/libwhisper/Message.h b/libwhisper/Message.h index 17ddb3f7f..fa5cc3662 100644 --- a/libwhisper/Message.h +++ b/libwhisper/Message.h @@ -84,7 +84,7 @@ private: enum /*Message Flags*/ { - ContainsSignature = 0 + ContainsSignature = 1 }; /// An (unencrypted) message, constructed from the combination of an Envelope, and, potentially, diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt new file mode 100644 index 000000000..9224c109d --- /dev/null +++ b/solc/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_policy(SET CMP0015 NEW) + +aux_source_directory(. SRC_LIST) + +include_directories(..) + +set(EXECUTABLE solc) + +add_executable(${EXECUTABLE} ${SRC_LIST}) + +target_link_libraries(${EXECUTABLE} solidity) +target_link_libraries(${EXECUTABLE} devcore) + +if ("${TARGET_PLATFORM}" STREQUAL "w64") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") + target_link_libraries(${EXECUTABLE} gcc) + target_link_libraries(${EXECUTABLE} gdi32) + target_link_libraries(${EXECUTABLE} ws2_32) + target_link_libraries(${EXECUTABLE} mswsock) + target_link_libraries(${EXECUTABLE} shlwapi) + target_link_libraries(${EXECUTABLE} iphlpapi) + target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) + set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS) +elseif (UNIX) +else () + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +endif () + +install( TARGETS ${EXECUTABLE} DESTINATION bin ) + diff --git a/solc/main.cpp b/solc/main.cpp new file mode 100644 index 000000000..e155b0fe6 --- /dev/null +++ b/solc/main.cpp @@ -0,0 +1,84 @@ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +ASTPointer parseAST(std::string const& _source) +{ + ASTPointer scanner = std::make_shared(CharStream(_source)); + Parser parser; + return parser.parse(scanner); +} + +} +} // end namespaces + +void help() +{ + std::cout + << "Usage solc [OPTIONS] " << std::endl + << "Options:" << std::endl + << " -h,--help Show this help message and exit." << std::endl + << " -V,--version Show the version and exit." << std::endl; + exit(0); +} + +void version() +{ + std::cout + << "solc, the solidity complier commandline interface " << dev::Version << std::endl + << " by Christian , (c) 2014." << std::endl + << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << std::endl; + exit(0); +} + +int main(int argc, char** argv) +{ + std::string infile; + for (int i = 1; i < argc; ++i) + { + std::string arg = argv[i]; + if (arg == "-h" || arg == "--help") + help(); + else if (arg == "-V" || arg == "--version") + version(); + else + infile = argv[i]; + } + std::string src; + if (infile.empty()) + { + std::string s; + while (!std::cin.eof()) + { + getline(std::cin, s); + src.append(s); + } + } + else + { + src = dev::asString(dev::contents(infile)); + } + std::cout << "Parsing..." << std::endl; + // @todo catch exception + dev::solidity::ASTPointer ast = dev::solidity::parseAST(src); + std::cout << "Syntax tree for the contract:" << std::endl; + dev::solidity::ASTPrinter printer(ast, src); + printer.print(std::cout); + std::cout << "Resolving identifiers..." << std::endl; + dev::solidity::NameAndTypeResolver resolver; + resolver.resolveNamesAndTypes(*ast.get()); + return 0; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a764d8b6b..6aa99b618 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,6 +15,7 @@ target_link_libraries(testeth ethereum) target_link_libraries(testeth ethcore) target_link_libraries(testeth secp256k1) target_link_libraries(testeth gmp) +target_link_libraries(testeth solidity) target_link_libraries(testeth ${CRYPTOPP_LS}) target_link_libraries(createRandomTest ethereum) diff --git a/test/solidityNameAndTypeResolution.cpp b/test/solidityNameAndTypeResolution.cpp new file mode 100644 index 000000000..833ae6d4b --- /dev/null +++ b/test/solidityNameAndTypeResolution.cpp @@ -0,0 +1,178 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Unit tests for the name and type resolution of the solidity parser. + */ + +#include + +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ +void parseTextAndResolveNames(const std::string& _source) +{ + Parser parser; + ASTPointer contract = parser.parse( + std::make_shared(CharStream(_source))); + NameAndTypeResolver resolver; + resolver.resolveNamesAndTypes(*contract); +} +} + +BOOST_AUTO_TEST_SUITE(SolidityNameAndTypeResolution) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + char const* text = "contract test {\n" + " uint256 stateVariable1;\n" + " function fun(uint256 arg1) { var x; uint256 y; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(double_stateVariable_declaration) +{ + char const* text = "contract test {\n" + " uint256 variable;\n" + " uint128 variable;\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), DeclarationError); +} + +BOOST_AUTO_TEST_CASE(double_function_declaration) +{ + char const* text = "contract test {\n" + " function fun() { var x; }\n" + " function fun() { var x; }\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), DeclarationError); +} + +BOOST_AUTO_TEST_CASE(double_variable_declaration) +{ + char const* text = "contract test {\n" + " function f() { uint256 x; if (true) { uint256 x; } }\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), DeclarationError); +} + +BOOST_AUTO_TEST_CASE(name_shadowing) +{ + char const* text = "contract test {\n" + " uint256 variable;\n" + " function f() { uint32 variable ; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(name_references) +{ + char const* text = "contract test {\n" + " uint256 variable;\n" + " function f(uint256 arg) returns (uint out) { f(variable); test; out; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(undeclared_name) +{ + char const* text = "contract test {\n" + " uint256 variable;\n" + " function f(uint256 arg) { f(notfound); }" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), DeclarationError); +} + +BOOST_AUTO_TEST_CASE(reference_to_later_declaration) +{ + char const* text = "contract test {\n" + " function g() { f(); }" + " function f() { }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(type_inference_smoke_test) +{ + char const* text = "contract test {\n" + " function f(uint256 arg1, uint32 arg2) returns (bool ret) { var x = arg1 + arg2 == 8; ret = x; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(type_checking_return) +{ + char const* text = "contract test {\n" + " function f() returns (bool r) { return 1 >= 2; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(type_checking_return_wrong_number) +{ + char const* text = "contract test {\n" + " function f() returns (bool r1, bool r2) { return 1 >= 2; }" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(type_checking_return_wrong_type) +{ + char const* text = "contract test {\n" + " function f() returns (uint256 r) { return 1 >= 2; }" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(type_checking_function_call) +{ + char const* text = "contract test {\n" + " function f() returns (bool r) { return g(12, true) == 3; }\n" + " function g(uint256 a, bool b) returns (uint256 r) { }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(type_inference_explicit_conversion) +{ + char const* text = "contract test {\n" + " function f() returns (int256 r) { var x = int256(uint32(2)); return x; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces + diff --git a/test/solidityParser.cpp b/test/solidityParser.cpp new file mode 100644 index 000000000..025cd74d1 --- /dev/null +++ b/test/solidityParser.cpp @@ -0,0 +1,221 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Unit tests for the solidity parser. + */ + +#include + +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +namespace +{ +ASTPointer parseText(const std::string& _source) +{ + Parser parser; + return parser.parse(std::make_shared(CharStream(_source))); +} +} + +BOOST_AUTO_TEST_SUITE(SolidityParser) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + char const* text = "contract test {\n" + " uint256 stateVariable1;\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(missing_variable_name_in_declaration) +{ + char const* text = "contract test {\n" + " uint256 ;\n" + "}\n"; + BOOST_CHECK_THROW(parseText(text), ParserError); +} + +BOOST_AUTO_TEST_CASE(empty_function) +{ + char const* text = "contract test {\n" + " uint256 stateVar;\n" + " function functionName(hash160 arg1, address addr) const\n" + " returns (int id)\n" + " { }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(no_function_params) +{ + char const* text = "contract test {\n" + " uint256 stateVar;\n" + " function functionName() {}\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(single_function_param) +{ + char const* text = "contract test {\n" + " uint256 stateVar;\n" + " function functionName(hash hashin) returns (hash hashout) {}\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(struct_definition) +{ + char const* text = "contract test {\n" + " uint256 stateVar;\n" + " struct MyStructName {\n" + " address addr;\n" + " uint256 count;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(mapping) +{ + char const* text = "contract test {\n" + " mapping(address => string) names;\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(mapping_in_struct) +{ + char const* text = "contract test {\n" + " struct test_struct {\n" + " address addr;\n" + " uint256 count;\n" + " mapping(hash => test_struct) self_reference;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(mapping_to_mapping_in_struct) +{ + char const* text = "contract test {\n" + " struct test_struct {\n" + " address addr;\n" + " mapping (uint64 => mapping (hash => uint)) complex_mapping;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(variable_definition) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " var b;\n" + " uint256 c;\n" + " mapping(address=>hash) d;\n" + " customtype varname;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(variable_definition_with_initialization) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " var b = 2;\n" + " uint256 c = 0x87;\n" + " mapping(address=>hash) d;\n" + " string name = \"Solidity\";" + " customtype varname;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(operator_expression) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " uint256 x = (1 + 4) || false && (1 - 12) + -9;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(complex_expression) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " uint256 x = (1 + 4).member(++67)[a/=9] || true;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(while_loop) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " uint256 x = (1 + 4).member(++67) || true;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(if_statement) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) {\n" + " if (a >= 8) return 2; else { var b = 7; }\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(else_if_statement) +{ + char const* text = "contract test {\n" + " function fun(uint256 a) returns (address b) {\n" + " if (a < 0) b = 0x67; else if (a == 0) b = 0x12; else b = 0x78;\n" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + + + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces + diff --git a/test/solidityScanner.cpp b/test/solidityScanner.cpp new file mode 100644 index 000000000..d2a960cfb --- /dev/null +++ b/test/solidityScanner.cpp @@ -0,0 +1,143 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Unit tests for the solidity scanner. + */ + +#include +#include + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(SolidityScanner) + +BOOST_AUTO_TEST_CASE(test_empty) +{ + Scanner scanner(CharStream("")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS); +} + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + Scanner scanner(CharStream("function break;765 \t \"string1\",'string2'\nidentifier1")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::FUNCTION); + BOOST_CHECK_EQUAL(scanner.next(), Token::BREAK); + BOOST_CHECK_EQUAL(scanner.next(), Token::SEMICOLON); + BOOST_CHECK_EQUAL(scanner.next(), Token::NUMBER); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "765"); + BOOST_CHECK_EQUAL(scanner.next(), Token::STRING_LITERAL); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "string1"); + BOOST_CHECK_EQUAL(scanner.next(), Token::COMMA); + BOOST_CHECK_EQUAL(scanner.next(), Token::STRING_LITERAL); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "string2"); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "identifier1"); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); +} + +BOOST_AUTO_TEST_CASE(string_escapes) +{ + Scanner scanner(CharStream(" { \"a\\x61\"")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LBRACE); + BOOST_CHECK_EQUAL(scanner.next(), Token::STRING_LITERAL); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "aa"); +} + +BOOST_AUTO_TEST_CASE(string_escapes_with_zero) +{ + Scanner scanner(CharStream(" { \"a\\x61\\x00abc\"")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LBRACE); + BOOST_CHECK_EQUAL(scanner.next(), Token::STRING_LITERAL); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), std::string("aa\0abc", 6)); +} + +BOOST_AUTO_TEST_CASE(string_escape_illegal) +{ + Scanner scanner(CharStream(" bla \"\\x6rf\" (illegalescape)")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::ILLEGAL); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), ""); + // TODO recovery from illegal tokens should be improved + BOOST_CHECK_EQUAL(scanner.next(), Token::ILLEGAL); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::ILLEGAL); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); +} + +BOOST_AUTO_TEST_CASE(hex_numbers) +{ + Scanner scanner(CharStream("var x = 0x765432536763762734623472346;")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::VAR); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::ASSIGN); + BOOST_CHECK_EQUAL(scanner.next(), Token::NUMBER); + BOOST_CHECK_EQUAL(scanner.getCurrentLiteral(), "0x765432536763762734623472346"); + BOOST_CHECK_EQUAL(scanner.next(), Token::SEMICOLON); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); +} + +BOOST_AUTO_TEST_CASE(locations) +{ + Scanner scanner(CharStream("function_identifier has ; -0x743/*comment*/\n ident //comment")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 0); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 19); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 20); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 23); + BOOST_CHECK_EQUAL(scanner.next(), Token::SEMICOLON); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 24); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 25); + BOOST_CHECK_EQUAL(scanner.next(), Token::SUB); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 26); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 27); + BOOST_CHECK_EQUAL(scanner.next(), Token::NUMBER); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 27); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 32); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().start, 45); + BOOST_CHECK_EQUAL(scanner.getCurrentLocation().end, 50); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); +} + +BOOST_AUTO_TEST_CASE(ambiguities) +{ + // test scanning of some operators which need look-ahead + Scanner scanner(CharStream("<=""<""+ +=a++ =>""<<")); + BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::LTE); + BOOST_CHECK_EQUAL(scanner.next(), Token::LT); + BOOST_CHECK_EQUAL(scanner.next(), Token::ADD); + BOOST_CHECK_EQUAL(scanner.next(), Token::ASSIGN_ADD); + BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER); + BOOST_CHECK_EQUAL(scanner.next(), Token::INC); + BOOST_CHECK_EQUAL(scanner.next(), Token::ARROW); + BOOST_CHECK_EQUAL(scanner.next(), Token::SHL); +} + + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces