diff options
-rw-r--r-- | AST.cpp | 117 | ||||
-rw-r--r-- | AST.h | 151 | ||||
-rw-r--r-- | ASTForward.h | 1 | ||||
-rw-r--r-- | ASTPrinter.cpp | 42 | ||||
-rw-r--r-- | ASTPrinter.h | 3 | ||||
-rw-r--r-- | ASTVisitor.h | 12 | ||||
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | Compiler.cpp | 208 | ||||
-rw-r--r-- | Compiler.h | 59 | ||||
-rw-r--r-- | CompilerUtilities.cpp | 60 | ||||
-rw-r--r-- | CompilerUtilities.h | 83 | ||||
-rw-r--r-- | Exceptions.h | 1 | ||||
-rw-r--r-- | ExpressionCompiler.cpp | 408 | ||||
-rw-r--r-- | ExpressionCompiler.h | 79 | ||||
-rw-r--r-- | NameAndTypeResolver.cpp | 49 | ||||
-rw-r--r-- | NameAndTypeResolver.h | 17 | ||||
-rw-r--r-- | Parser.cpp | 19 | ||||
-rw-r--r-- | Parser.h | 14 | ||||
-rw-r--r-- | Scanner.cpp | 45 | ||||
-rw-r--r-- | Scanner.h | 36 | ||||
-rw-r--r-- | Scope.cpp | 4 | ||||
-rw-r--r-- | Scope.h | 8 | ||||
-rw-r--r-- | SourceReferenceFormatter.cpp | 36 | ||||
-rw-r--r-- | Token.h | 20 | ||||
-rw-r--r-- | Types.cpp | 90 | ||||
-rw-r--r-- | Types.h | 50 |
26 files changed, 1399 insertions, 217 deletions
@@ -26,6 +26,8 @@ #include <libsolidity/ASTVisitor.h> #include <libsolidity/Exceptions.h> +using namespace std; + namespace dev { namespace solidity @@ -165,6 +167,14 @@ void Return::accept(ASTVisitor& _visitor) _visitor.endVisit(*this); } +void ExpressionStatement::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)) @@ -248,28 +258,20 @@ void Literal::accept(ASTVisitor& _visitor) _visitor.endVisit(*this); } -TypeError ASTNode::createTypeError(std::string const& _description) +TypeError ASTNode::createTypeError(string const& _description) { return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description); } -void Statement::expectType(Expression& _expression, const Type& _expectedType) -{ - _expression.checkTypeRequirements(); - if (!_expression.getType()->isImplicitlyConvertibleTo(_expectedType)) - BOOST_THROW_EXCEPTION(_expression.createTypeError("Type not implicitly convertible to expected type.")); - //@todo provide more information to the exception -} - void Block::checkTypeRequirements() { - for (std::shared_ptr<Statement> const& statement: m_statements) + for (shared_ptr<Statement> const& statement: m_statements) statement->checkTypeRequirements(); } void IfStatement::checkTypeRequirements() { - expectType(*m_condition, BoolType()); + m_condition->expectType(BoolType()); m_trueBody->checkTypeRequirements(); if (m_falseBody) m_falseBody->checkTypeRequirements(); @@ -277,7 +279,7 @@ void IfStatement::checkTypeRequirements() void WhileStatement::checkTypeRequirements() { - expectType(*m_condition, BoolType()); + m_condition->expectType(BoolType()); m_body->checkTypeRequirements(); } @@ -291,13 +293,15 @@ void Break::checkTypeRequirements() void Return::checkTypeRequirements() { - BOOST_ASSERT(m_returnParameters); + assert(m_returnParameters); + if (!m_expression) + return; if (m_returnParameters->getParameters().size() != 1) BOOST_THROW_EXCEPTION(createTypeError("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()); + m_expression->expectType(*m_returnParameters->getParameters().front()->getType()); } void VariableDefinition::checkTypeRequirements() @@ -309,7 +313,7 @@ void VariableDefinition::checkTypeRequirements() if (m_value) { if (m_variable->getType()) - expectType(*m_value, *m_variable->getType()); + m_value->expectType(*m_variable->getType()); else { // no type declared and no previous assignment, infer the type @@ -324,22 +328,42 @@ 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()); + if (!m_leftHandSide->isLvalue()) + BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue.")); + m_rightHandSide->expectType(*m_leftHandSide->getType()); m_type = m_leftHandSide->getType(); if (m_assigmentOperator != Token::ASSIGN) { - // complex assignment + // compound assignment if (!m_type->acceptsBinaryOperator(Token::AssignmentToBinaryOp(m_assigmentOperator))) BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type.")); } } +void ExpressionStatement::checkTypeRequirements() +{ + m_expression->checkTypeRequirements(); +} + +void Expression::expectType(const Type& _expectedType) +{ + checkTypeRequirements(); + if (!getType()->isImplicitlyConvertibleTo(_expectedType)) + BOOST_THROW_EXCEPTION(createTypeError("Type not implicitly convertible to expected type.")); + //@todo provide more information to the exception +} + void UnaryOperation::checkTypeRequirements() { - // INC, DEC, NOT, BIT_NOT, DELETE + // INC, DEC, ADD, SUB, NOT, BIT_NOT, DELETE m_subExpression->checkTypeRequirements(); + if (m_operator == Token::Value::INC || m_operator == Token::Value::DEC || m_operator == Token::Value::DELETE) + { + if (!m_subExpression->isLvalue()) + BOOST_THROW_EXCEPTION(createTypeError("Expression has to be an lvalue.")); + } m_type = m_subExpression->getType(); - if (m_type->acceptsUnaryOperator(m_operator)) + if (!m_type->acceptsUnaryOperator(m_operator)) BOOST_THROW_EXCEPTION(createTypeError("Unary operator not compatible with type.")); } @@ -354,10 +378,10 @@ void BinaryOperation::checkTypeRequirements() else BOOST_THROW_EXCEPTION(createTypeError("No common type found in binary operation.")); if (Token::isCompareOp(m_operator)) - m_type = std::make_shared<BoolType>(); + m_type = make_shared<BoolType>(); else { - BOOST_ASSERT(Token::isBinaryOp(m_operator)); + assert(Token::isBinaryOp(m_operator)); m_type = m_commonType; if (!m_commonType->acceptsBinaryOperator(m_operator)) BOOST_THROW_EXCEPTION(createTypeError("Operator not compatible with type.")); @@ -369,12 +393,12 @@ void FunctionCall::checkTypeRequirements() m_expression->checkTypeRequirements(); for (ASTPointer<Expression> const& argument: m_arguments) argument->checkTypeRequirements(); - Type const& expressionType = *m_expression->getType(); - Type::Category const category = expressionType.getCategory(); - if (category == Type::Category::TYPE) + + Type const* expressionType = m_expression->getType().get(); + if (isTypeConversion()) { - TypeType const* type = dynamic_cast<TypeType const*>(&expressionType); - BOOST_ASSERT(type); + TypeType const* type = dynamic_cast<TypeType const*>(expressionType); + assert(type); //@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) @@ -384,15 +408,15 @@ void FunctionCall::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed.")); m_type = type->getActualType(); } - else if (category == Type::Category::FUNCTION) + else { //@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<FunctionType const*>(&expressionType); - BOOST_ASSERT(function); + FunctionType const* function = dynamic_cast<FunctionType const*>(expressionType); + assert(function); FunctionDefinition const& fun = function->getFunction(); - std::vector<ASTPointer<VariableDeclaration>> const& parameters = fun.getParameters(); + vector<ASTPointer<VariableDeclaration>> const& parameters = fun.getParameters(); if (parameters.size() != m_arguments.size()) BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call.")); for (size_t i = 0; i < m_arguments.size(); ++i) @@ -400,30 +424,33 @@ void FunctionCall::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("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<VoidType>(); + if (fun.getReturnParameters().empty()) + m_type = make_shared<VoidType>(); else - m_type = fun.getReturnParameterList()->getParameters().front()->getType(); + m_type = fun.getReturnParameters().front()->getType(); } - else - BOOST_THROW_EXCEPTION(createTypeError("Type does not support invocation.")); +} + +bool FunctionCall::isTypeConversion() const +{ + return m_expression->getType()->getCategory() == Type::Category::TYPE; } void MemberAccess::checkTypeRequirements() { - BOOST_ASSERT(false); // not yet implemented + assert(false); // not yet implemented // m_type = ; } void IndexAccess::checkTypeRequirements() { - BOOST_ASSERT(false); // not yet implemented + assert(false); // not yet implemented // m_type = ; } void Identifier::checkTypeRequirements() { - BOOST_ASSERT(m_referencedDeclaration); + assert(m_referencedDeclaration); //@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? @@ -436,9 +463,9 @@ void Identifier::checkTypeRequirements() if (variable) { if (!variable->getType()) - BOOST_THROW_EXCEPTION(createTypeError("Variable referenced before type " - "could be determined.")); + BOOST_THROW_EXCEPTION(createTypeError("Variable referenced before type could be determined.")); m_type = variable->getType(); + m_isLvalue = true; return; } //@todo can we unify these with TypeName::toType()? @@ -446,7 +473,7 @@ void Identifier::checkTypeRequirements() if (structDef) { // note that we do not have a struct type here - m_type = std::make_shared<TypeType>(std::make_shared<StructType>(*structDef)); + m_type = make_shared<TypeType>(make_shared<StructType>(*structDef)); return; } FunctionDefinition* functionDef = dynamic_cast<FunctionDefinition*>(m_referencedDeclaration); @@ -455,21 +482,21 @@ void Identifier::checkTypeRequirements() // 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<FunctionType>(*functionDef); + m_type = make_shared<FunctionType>(*functionDef); return; } ContractDefinition* contractDef = dynamic_cast<ContractDefinition*>(m_referencedDeclaration); if (contractDef) { - m_type = std::make_shared<TypeType>(std::make_shared<ContractType>(*contractDef)); + m_type = make_shared<TypeType>(make_shared<ContractType>(*contractDef)); return; } - BOOST_ASSERT(false); // declaration reference of unknown/forbidden type + assert(false); // declaration reference of unknown/forbidden type } void ElementaryTypeNameExpression::checkTypeRequirements() { - m_type = std::make_shared<TypeType>(Type::fromElementaryTypeName(m_typeToken)); + m_type = make_shared<TypeType>(Type::fromElementaryTypeName(m_typeToken)); } void Literal::checkTypeRequirements() @@ -40,6 +40,10 @@ namespace solidity class ASTVisitor; + +/// The root (abstract) class of the AST inheritance tree. +/// It is possible to traverse all direct and indirect children of an AST node by calling +/// accept, providing an ASTVisitor. class ASTNode: private boost::noncopyable { public: @@ -55,28 +59,41 @@ public: element->accept(_visitor); } + /// Returns the source code location of this node. Location const& getLocation() const { return m_location; } /// Creates a @ref TypeError exception and decorates it with the location of the node and /// the given description TypeError createTypeError(std::string const& _description); + ///@{ + ///@name equality operators + /// Equality relies on the fact that nodes cannot be copied. + bool operator==(ASTNode const& _other) const { return this == &_other; } + bool operator!=(ASTNode const& _other) const { return !operator==(_other); } + ///@} + private: Location m_location; }; +/// Abstract AST class for a declaration (contract, function, struct, variable). class Declaration: public ASTNode { public: Declaration(Location const& _location, ASTPointer<ASTString> const& _name): ASTNode(_location), m_name(_name) {} + /// Returns the declared name. const ASTString& getName() const { return *m_name; } private: ASTPointer<ASTString> m_name; }; +/// Definition of a contract. This is the only AST nodes where child nodes are not visited in +/// document order. It first visits all struct declarations, then all variable declarations and +/// finally all function declarations. class ContractDefinition: public Declaration { public: @@ -116,9 +133,9 @@ private: std::vector<ASTPointer<VariableDeclaration>> m_members; }; -/// Used as function parameter list and return list +/// Parameter list, used as function parameter list and return list. /// None of the parameters is allowed to contain mappings (not even recursively -/// inside structs) +/// inside structs), but (@todo) this is not yet enforced. class ParameterList: public ASTNode { public: @@ -127,7 +144,7 @@ public: ASTNode(_location), m_parameters(_parameters) {} virtual void accept(ASTVisitor& _visitor) override; - std::vector<ASTPointer<VariableDeclaration>> const& getParameters() { return m_parameters; } + std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters; } private: std::vector<ASTPointer<VariableDeclaration>> m_parameters; @@ -150,17 +167,24 @@ public: bool isDeclaredConst() const { return m_isDeclaredConst; } std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); } ParameterList& getParameterList() { return *m_parameters; } + std::vector<ASTPointer<VariableDeclaration>> const& getReturnParameters() const { return m_returnParameters->getParameters(); } ASTPointer<ParameterList> const& getReturnParameterList() const { return m_returnParameters; } Block& getBody() { return *m_body; } + void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } + std::vector<VariableDeclaration const*>const& getLocalVariables() const { return m_localVariables; } private: bool m_isPublic; ASTPointer<ParameterList> m_parameters; bool m_isDeclaredConst; ASTPointer<ParameterList> m_returnParameters; ASTPointer<Block> m_body; + + std::vector<VariableDeclaration const*> m_localVariables; }; +/// Declaration of a variable. This can be used in various places, e.g. in function parameter +/// lists, struct definitions and even function bodys. class VariableDeclaration: public Declaration { public: @@ -180,22 +204,26 @@ public: private: ASTPointer<TypeName> m_typeName; ///< can be empty ("var") - std::shared_ptr<Type const> m_type; + std::shared_ptr<Type const> m_type; ///< derived type, initially empty }; -/// types +/// Types /// @{ +/// Abstract base class of a type name, can be any built-in or user-defined type. class TypeName: public ASTNode { public: explicit TypeName(Location const& _location): ASTNode(_location) {} virtual void accept(ASTVisitor& _visitor) override; + /// Retrieve the element of the type hierarchy this node refers to. Can return an empty shared + /// pointer until the types have been resolved using the @ref NameAndTypeResolver. virtual std::shared_ptr<Type> toType() = 0; }; -/// any pre-defined type that is not a mapping +/// Any pre-defined type name represented by a single keyword, i.e. it excludes mappings, +/// contracts, functions, etc. class ElementaryTypeName: public TypeName { public: @@ -204,12 +232,14 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual std::shared_ptr<Type> toType() override { return Type::fromElementaryTypeName(m_type); } - Token::Value getType() const { return m_type; } + Token::Value getTypeName() const { return m_type; } private: Token::Value m_type; }; +/// Name referring to a user-defined type (i.e. a struct). +/// @todo some changes are necessary if this is also used to refer to contract types later class UserDefinedTypeName: public TypeName { public: @@ -228,6 +258,7 @@ private: StructDefinition* m_referencedStruct; }; +/// A mapping type. Its source form is "mapping('keyType' => 'valueType')" class Mapping: public TypeName { public: @@ -247,6 +278,8 @@ private: /// Statements /// @{ + +/// Abstract base class for statements. class Statement: public ASTNode { public: @@ -254,16 +287,12 @@ public: 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. + //! This includes checking that operators are applicable to their arguments but also that + //! the number of function call arguments matches the number of formal parameters and so forth. 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); }; +/// Brace-enclosed block containing zero or more statements. class Block: public Statement { public: @@ -277,6 +306,8 @@ private: std::vector<ASTPointer<Statement>> m_statements; }; +/// If-statement with an optional "else" part. Note that "else if" is modeled by having a new +/// if-statement as the false (else) body. class IfStatement: public Statement { public: @@ -287,12 +318,17 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getCondition() const { return *m_condition; } + Statement& getTrueStatement() const { return *m_trueBody; } + Statement* getFalseStatement() const { return m_falseBody.get(); } private: ASTPointer<Expression> m_condition; ASTPointer<Statement> m_trueBody; ASTPointer<Statement> m_falseBody; //< "else" part, optional }; +/// Statement in which a break statement is legal. +/// @todo actually check this requirement. class BreakableStatement: public Statement { public: @@ -309,6 +345,8 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getCondition() const { return *m_condition; } + Statement& getBody() const { return *m_body; } private: ASTPointer<Expression> m_condition; ASTPointer<Statement> m_body; @@ -339,13 +377,19 @@ public: virtual void checkTypeRequirements() override; void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; } + ParameterList const& getFunctionReturnParameters() const { assert(m_returnParameters); return *m_returnParameters; } + Expression* getExpression() const { return m_expression.get(); } private: ASTPointer<Expression> m_expression; //< value to return, optional - ParameterList* m_returnParameters; //< extracted from the function declaration + /// Pointer to the parameter list of the function, filled by the @ref NameAndTypeResolver. + ParameterList* m_returnParameters; }; +/// Definition of a variable as a statement inside a function. It requires a type name (which can +/// also be "var") but the actual assignment can be missing. +/// Examples: var a = 2; uint256 a; class VariableDefinition: public Statement { public: @@ -355,20 +399,29 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + VariableDeclaration const& getDeclaration() const { return *m_variable; } + Expression* getExpression() const { return m_value.get(); } + private: ASTPointer<VariableDeclaration> m_variable; - ASTPointer<Expression> m_value; ///< can be missing + ASTPointer<Expression> m_value; ///< the assigned value, can be missing }; -class Expression: public Statement +/** + * A statement that contains only an expression (i.e. an assignment, function call, ...). + */ +class ExpressionStatement: public Statement { public: - Expression(Location const& _location): Statement(_location) {} - std::shared_ptr<Type const> const& getType() const { return m_type; } + ExpressionStatement(Location const& _location, ASTPointer<Expression> _expression): + Statement(_location), m_expression(_expression) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void checkTypeRequirements() override; -protected: - //! Inferred type of the expression, only filled after a call to checkTypeRequirements(). - std::shared_ptr<Type const> m_type; + Expression& getExpression() const { return *m_expression; } + +private: + ASTPointer<Expression> m_expression; }; /// @} @@ -376,6 +429,34 @@ protected: /// Expressions /// @{ +/** + * An expression, i.e. something that has a value (which can also be of type "void" in case + * of some function calls). + * @abstract + */ +class Expression: public ASTNode +{ +public: + Expression(Location const& _location): ASTNode(_location), m_isLvalue(false) {} + virtual void checkTypeRequirements() = 0; + + std::shared_ptr<Type const> const& getType() const { return m_type; } + bool isLvalue() const { return m_isLvalue; } + + /// Helper function, infer the type via @ref checkTypeRequirements and then check that it + /// is implicitly convertible to @a _expectedType. If not, throw exception. + void expectType(Type const& _expectedType); + +protected: + //! Inferred type of the expression, only filled after a call to checkTypeRequirements(). + std::shared_ptr<Type const> m_type; + //! Whether or not this expression is an lvalue, i.e. something that can be assigned to. + //! This is set during calls to @a checkTypeRequirements() + bool m_isLvalue; +}; + +/// Assignment, can also be a compound assignment. +/// Examples: (a = 7 + 8) or (a *= 2) class Assignment: public Expression { public: @@ -386,7 +467,9 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getLeftHandSide() const { return *m_leftHandSide; } Token::Value getAssignmentOperator() const { return m_assigmentOperator; } + Expression& getRightHandSide() const { return *m_rightHandSide; } private: ASTPointer<Expression> m_leftHandSide; @@ -394,6 +477,8 @@ private: ASTPointer<Expression> m_rightHandSide; }; +/// Operation involving a unary operator, pre- or postfix. +/// Examples: ++i, delete x or !true class UnaryOperation: public Expression { public: @@ -413,6 +498,8 @@ private: bool m_isPrefix; }; +/// Operation involving a binary operator. +/// Examples: 1 + 2, true && false or 1 <= 4 class BinaryOperation: public Expression { public: @@ -422,6 +509,8 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getLeftExpression() const { return *m_left; } + Expression& getRightExpression() const { return *m_right; } Token::Value getOperator() const { return m_operator; } private: @@ -442,11 +531,19 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void checkTypeRequirements() override; + Expression& getExpression() const { return *m_expression; } + std::vector<ASTPointer<Expression>> const& getArguments() const { return m_arguments; } + + /// Returns true if this is not an actual function call, but an explicit type conversion + /// or constructor call. + bool isTypeConversion() const; + private: ASTPointer<Expression> m_expression; std::vector<ASTPointer<Expression>> m_arguments; }; +/// Access to a member of an object. Example: x.name class MemberAccess: public Expression { public: @@ -462,6 +559,7 @@ private: ASTPointer<ASTString> m_memberName; }; +/// Index access to an array. Example: a[2] class IndexAccess: public Expression { public: @@ -476,12 +574,15 @@ private: ASTPointer<Expression> m_index; }; +/// Primary expression, i.e. an expression that do not be divided any further like a literal or +/// a variable reference. class PrimaryExpression: public Expression { public: PrimaryExpression(Location const& _location): Expression(_location) {} }; +/// An identifier, i.e. a reference to a declaration by name like a variable or function. class Identifier: public PrimaryExpression { public: @@ -491,6 +592,7 @@ public: virtual void checkTypeRequirements() override; ASTString const& getName() const { return *m_name; } + void setReferencedDeclaration(Declaration& _referencedDeclaration) { m_referencedDeclaration = &_referencedDeclaration; } Declaration* getReferencedDeclaration() { return m_referencedDeclaration; } @@ -501,6 +603,9 @@ private: Declaration* m_referencedDeclaration; }; +/// An elementary type name expression is used in expressions like "a = uint32(2)" to change the +/// type of an expression explicitly. Here, "uint32" is the elementary type name expression and +/// "uint32(2)" is a @ref FunctionCall. class ElementaryTypeNameExpression: public PrimaryExpression { public: @@ -515,6 +620,7 @@ private: Token::Value m_typeToken; }; +/// A literal string or number. @see Type::literalToBigEndian is used to actually parse its value. class Literal: public PrimaryExpression { public: @@ -524,6 +630,7 @@ public: virtual void checkTypeRequirements() override; Token::Value getToken() const { return m_token; } + /// @returns the non-parsed value of the literal ASTString const& getValue() const { return *m_value; } private: diff --git a/ASTForward.h b/ASTForward.h index c9a780f5..2b0bd886 100644 --- a/ASTForward.h +++ b/ASTForward.h @@ -53,6 +53,7 @@ class Continue; class Break; class Return; class VariableDefinition; +class ExpressionStatement; class Expression; class Assignment; class UnaryOperation; diff --git a/ASTPrinter.cpp b/ASTPrinter.cpp index 44245ed4..eb9d92f0 100644 --- a/ASTPrinter.cpp +++ b/ASTPrinter.cpp @@ -23,17 +23,19 @@ #include <libsolidity/ASTPrinter.h> #include <libsolidity/AST.h> +using namespace std; + namespace dev { namespace solidity { -ASTPrinter::ASTPrinter(ASTPointer<ASTNode> const& _ast, std::string const& _source): +ASTPrinter::ASTPrinter(ASTPointer<ASTNode> const& _ast, string const& _source): m_indentation(0), m_source(_source), m_ast(_ast) { } -void ASTPrinter::print(std::ostream& _stream) +void ASTPrinter::print(ostream& _stream) { m_ostream = &_stream; m_ast->accept(*this); @@ -87,7 +89,7 @@ bool ASTPrinter::visit(TypeName& _node) bool ASTPrinter::visit(ElementaryTypeName& _node) { - writeLine(std::string("ElementaryTypeName ") + Token::toString(_node.getType())); + writeLine(string("ElementaryTypeName ") + Token::toString(_node.getTypeName())); printSourcePart(_node); return goDeeper(); } @@ -169,6 +171,13 @@ bool ASTPrinter::visit(VariableDefinition& _node) return goDeeper(); } +bool ASTPrinter::visit(ExpressionStatement& _node) +{ + writeLine("ExpressionStatement"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(Expression& _node) { writeLine("Expression"); @@ -179,7 +188,7 @@ bool ASTPrinter::visit(Expression& _node) bool ASTPrinter::visit(Assignment& _node) { - writeLine(std::string("Assignment using operator ") + Token::toString(_node.getAssignmentOperator())); + writeLine(string("Assignment using operator ") + Token::toString(_node.getAssignmentOperator())); printType(_node); printSourcePart(_node); return goDeeper(); @@ -187,7 +196,7 @@ bool ASTPrinter::visit(Assignment& _node) bool ASTPrinter::visit(UnaryOperation& _node) { - writeLine(std::string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") + + writeLine(string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") + ") " + Token::toString(_node.getOperator())); printType(_node); printSourcePart(_node); @@ -196,7 +205,7 @@ bool ASTPrinter::visit(UnaryOperation& _node) bool ASTPrinter::visit(BinaryOperation& _node) { - writeLine(std::string("BinaryOperation using operator ") + Token::toString(_node.getOperator())); + writeLine(string("BinaryOperation using operator ") + Token::toString(_node.getOperator())); printType(_node); printSourcePart(_node); return goDeeper(); @@ -236,7 +245,7 @@ bool ASTPrinter::visit(PrimaryExpression& _node) bool ASTPrinter::visit(Identifier& _node) { - writeLine(std::string("Identifier ") + _node.getName()); + writeLine(string("Identifier ") + _node.getName()); printType(_node); printSourcePart(_node); return goDeeper(); @@ -244,7 +253,7 @@ bool ASTPrinter::visit(Identifier& _node) bool ASTPrinter::visit(ElementaryTypeNameExpression& _node) { - writeLine(std::string("ElementaryTypeNameExpression ") + Token::toString(_node.getTypeToken())); + writeLine(string("ElementaryTypeNameExpression ") + Token::toString(_node.getTypeToken())); printType(_node); printSourcePart(_node); return goDeeper(); @@ -255,7 +264,7 @@ bool ASTPrinter::visit(Literal& _node) char const* tokenString = Token::toString(_node.getToken()); if (!tokenString) tokenString = "[no token]"; - writeLine(std::string("Literal, token: ") + tokenString + " value: " + _node.getValue()); + writeLine(string("Literal, token: ") + tokenString + " value: " + _node.getValue()); printType(_node); printSourcePart(_node); return goDeeper(); @@ -356,6 +365,11 @@ void ASTPrinter::endVisit(VariableDefinition&) m_indentation--; } +void ASTPrinter::endVisit(ExpressionStatement&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(Expression&) { m_indentation--; @@ -417,7 +431,7 @@ void ASTPrinter::printSourcePart(ASTNode const& _node) { Location const& location(_node.getLocation()); *m_ostream << getIndentation() << " Source: |" - << m_source.substr(location.start, location.end - location.start) << "|" << std::endl; + << m_source.substr(location.start, location.end - location.start) << "|" << endl; } } @@ -429,14 +443,14 @@ void ASTPrinter::printType(Expression const& _expression) *m_ostream << getIndentation() << " Type unknown.\n"; } -std::string ASTPrinter::getIndentation() const +string ASTPrinter::getIndentation() const { - return std::string(m_indentation * 2, ' '); + return string(m_indentation * 2, ' '); } -void ASTPrinter::writeLine(std::string const& _line) +void ASTPrinter::writeLine(string const& _line) { - *m_ostream << getIndentation() << _line << std::endl; + *m_ostream << getIndentation() << _line << endl; } } diff --git a/ASTPrinter.h b/ASTPrinter.h index 14592e2b..722c80c9 100644 --- a/ASTPrinter.h +++ b/ASTPrinter.h @@ -30,6 +30,7 @@ namespace dev namespace solidity { +/// Pretty-printer for the abstract syntax tree (the "pretty" is arguable) for debugging purposes. class ASTPrinter: public ASTVisitor { public: @@ -57,6 +58,7 @@ public: bool visit(Break& _node) override; bool visit(Return& _node) override; bool visit(VariableDefinition& _node) override; + bool visit(ExpressionStatement& _node) override; bool visit(Expression& _node) override; bool visit(Assignment& _node) override; bool visit(UnaryOperation& _node) override; @@ -88,6 +90,7 @@ public: void endVisit(Break&) override; void endVisit(Return&) override; void endVisit(VariableDefinition&) override; + void endVisit(ExpressionStatement&) override; void endVisit(Expression&) override; void endVisit(Assignment&) override; void endVisit(UnaryOperation&) override; diff --git a/ASTVisitor.h b/ASTVisitor.h index 72f28768..2a765e47 100644 --- a/ASTVisitor.h +++ b/ASTVisitor.h @@ -30,13 +30,15 @@ namespace dev namespace solidity { +/// Visitor interface for the abstract syntax tree. This class is tightly bound to the +/// implementation of @ref ASTNode::accept and its overrides. After a call to +/// @ref ASTNode::accept, the function visit for the appropriate parameter is called and then +/// (if it returns true) this continues recursively for all child nodes in document order +/// (there is an exception for contracts). After all child nodes have been visited, endVisit is +/// called for the node. 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; } @@ -56,6 +58,7 @@ public: virtual bool visit(Break&) { return true; } virtual bool visit(Return&) { return true; } virtual bool visit(VariableDefinition&) { return true; } + virtual bool visit(ExpressionStatement&) { return true; } virtual bool visit(Expression&) { return true; } virtual bool visit(Assignment&) { return true; } virtual bool visit(UnaryOperation&) { return true; } @@ -87,6 +90,7 @@ public: virtual void endVisit(Break&) { } virtual void endVisit(Return&) { } virtual void endVisit(VariableDefinition&) { } + virtual void endVisit(ExpressionStatement&) { } virtual void endVisit(Expression&) { } virtual void endVisit(Assignment&) { } virtual void endVisit(UnaryOperation&) { } diff --git a/CMakeLists.txt b/CMakeLists.txt index 757d0cc0..f425bba4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,8 @@ file(GLOB HEADERS "*.h") include_directories(..) -target_link_libraries(${EXECUTABLE} devcore) -target_link_libraries(${EXECUTABLE} evmface) +# @todo we only depend on Assembly, not on all of lll +target_link_libraries(${EXECUTABLE} evmface devcore lll) install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/Compiler.cpp b/Compiler.cpp new file mode 100644 index 00000000..fea88560 --- /dev/null +++ b/Compiler.cpp @@ -0,0 +1,208 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Solidity compiler. + */ + +#include <algorithm> +#include <libsolidity/AST.h> +#include <libsolidity/Compiler.h> +#include <libsolidity/ExpressionCompiler.h> + +using namespace std; + +namespace dev { +namespace solidity { + +bytes Compiler::compile(ContractDefinition& _contract) +{ + Compiler compiler; + compiler.compileContract(_contract); + return compiler.m_context.getAssembledBytecode(); +} + +void Compiler::compileContract(ContractDefinition& _contract) +{ + m_context = CompilerContext(); // clear it just in case + + //@todo constructor + //@todo register state variables + + for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions()) + m_context.addFunction(*function); + appendFunctionSelector(_contract.getDefinedFunctions()); + for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions()) + function->accept(*this); +} + +void Compiler::appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition>> const&) +{ + // filter public functions, and sort by name. Then select function from first byte, + // unpack arguments from calldata, push to stack and jump. Pack return values to + // output and return. +} + +bool Compiler::visit(FunctionDefinition& _function) +{ + //@todo to simplify this, the colling convention could by changed such that + // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] + // although note that this reduces the size of the visible stack + + m_context.startNewFunction(); + m_returnTag = m_context.newTag(); + m_breakTags.clear(); + m_continueTags.clear(); + + m_context << m_context.getFunctionEntryLabel(_function); + + // stack upon entry: [return address] [arg0] [arg1] ... [argn] + // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] + + unsigned const numArguments = _function.getParameters().size(); + unsigned const numReturnValues = _function.getReturnParameters().size(); + unsigned const numLocalVariables = _function.getLocalVariables().size(); + + for (ASTPointer<VariableDeclaration> const& variable: _function.getParameters() + _function.getReturnParameters()) + m_context.addVariable(*variable); + for (VariableDeclaration const* localVariable: _function.getLocalVariables()) + m_context.addVariable(*localVariable); + m_context.initializeLocalVariables(numReturnValues + numLocalVariables); + + _function.getBody().accept(*this); + + m_context << m_returnTag; + + // Now we need to re-shuffle the stack. For this we keep a record of the stack layout + // that shows the target positions of the elements, where "-1" denotes that this element needs + // to be removed from the stack. + // Note that the fact that the return arguments are of increasing index is vital for this + // algorithm to work. + + vector<int> stackLayout; + stackLayout.push_back(numReturnValues); // target of return address + stackLayout += vector<int>(numArguments, -1); // discard all arguments + for (unsigned i = 0; i < numReturnValues; ++i) + stackLayout.push_back(i); + stackLayout += vector<int>(numLocalVariables, -1); + + while (stackLayout.back() != int(stackLayout.size() - 1)) + if (stackLayout.back() < 0) + { + m_context << eth::Instruction::POP; + stackLayout.pop_back(); + } + else + { + m_context << eth::swapInstruction(stackLayout.size() - stackLayout.back() - 1); + swap(stackLayout[stackLayout.back()], stackLayout.back()); + } + + m_context << eth::Instruction::JUMP; + + return false; +} + +bool Compiler::visit(IfStatement& _ifStatement) +{ + ExpressionCompiler::compileExpression(m_context, _ifStatement.getCondition()); + eth::AssemblyItem trueTag = m_context.appendConditionalJump(); + if (_ifStatement.getFalseStatement()) + _ifStatement.getFalseStatement()->accept(*this); + eth::AssemblyItem endTag = m_context.appendJump(); + m_context << trueTag; + _ifStatement.getTrueStatement().accept(*this); + m_context << endTag; + return false; +} + +bool Compiler::visit(WhileStatement& _whileStatement) +{ + eth::AssemblyItem loopStart = m_context.newTag(); + eth::AssemblyItem loopEnd = m_context.newTag(); + m_continueTags.push_back(loopStart); + m_breakTags.push_back(loopEnd); + + m_context << loopStart; + ExpressionCompiler::compileExpression(m_context, _whileStatement.getCondition()); + m_context << eth::Instruction::NOT; + m_context.appendConditionalJumpTo(loopEnd); + + _whileStatement.getBody().accept(*this); + + m_context.appendJumpTo(loopStart); + m_context << loopEnd; + + m_continueTags.pop_back(); + m_breakTags.pop_back(); + return false; +} + +bool Compiler::visit(Continue&) +{ + assert(!m_continueTags.empty()); + m_context.appendJumpTo(m_continueTags.back()); + return false; +} + +bool Compiler::visit(Break&) +{ + assert(!m_breakTags.empty()); + m_context.appendJumpTo(m_breakTags.back()); + return false; +} + +bool Compiler::visit(Return& _return) +{ + //@todo modifications are needed to make this work with functions returning multiple values + if (Expression* expression = _return.getExpression()) + { + ExpressionCompiler::compileExpression(m_context, *expression); + VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters().getParameters().front(); + ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(), *firstVariable.getType()); + int stackPosition = m_context.getStackPositionOfVariable(firstVariable); + m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP; + } + m_context.appendJumpTo(m_returnTag); + return false; +} + +bool Compiler::visit(VariableDefinition& _variableDefinition) +{ + if (Expression* expression = _variableDefinition.getExpression()) + { + ExpressionCompiler::compileExpression(m_context, *expression); + ExpressionCompiler::cleanHigherOrderBitsIfNeeded(*expression->getType(), + *_variableDefinition.getDeclaration().getType()); + int stackPosition = m_context.getStackPositionOfVariable(_variableDefinition.getDeclaration()); + m_context << eth::swapInstruction(stackPosition) << eth::Instruction::POP; + } + return false; +} + +bool Compiler::visit(ExpressionStatement& _expressionStatement) +{ + Expression& expression = _expressionStatement.getExpression(); + ExpressionCompiler::compileExpression(m_context, expression); + if (expression.getType()->getCategory() != Type::Category::VOID) + m_context << eth::Instruction::POP; + return false; +} + +} +} diff --git a/Compiler.h b/Compiler.h new file mode 100644 index 00000000..2ffaaa8d --- /dev/null +++ b/Compiler.h @@ -0,0 +1,59 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Solidity AST to EVM bytecode compiler. + */ + +#include <libsolidity/ASTVisitor.h> +#include <libsolidity/CompilerUtilities.h> + +namespace dev { +namespace solidity { + +class Compiler: private ASTVisitor +{ +public: + /// Compile the given contract and return the EVM bytecode. + static bytes compile(ContractDefinition& _contract); + +private: + Compiler(): m_returnTag(m_context.newTag()) {} + + void compileContract(ContractDefinition& _contract); + void appendFunctionSelector(const std::vector<ASTPointer<FunctionDefinition> >& _functions); + + virtual bool visit(FunctionDefinition& _function) override; + virtual bool visit(IfStatement& _ifStatement) override; + virtual bool visit(WhileStatement& _whileStatement) override; + virtual bool visit(Continue& _continue) override; + virtual bool visit(Break& _break) override; + virtual bool visit(Return& _return) override; + virtual bool visit(VariableDefinition& _variableDefinition) override; + virtual bool visit(ExpressionStatement& _expressionStatement) override; + + bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); } + + CompilerContext m_context; + std::vector<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement + std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement + eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement +}; + +} +} diff --git a/CompilerUtilities.cpp b/CompilerUtilities.cpp new file mode 100644 index 00000000..b8f57618 --- /dev/null +++ b/CompilerUtilities.cpp @@ -0,0 +1,60 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Utilities for the solidity compiler. + */ + +#include <cassert> +#include <utility> +#include <numeric> +#include <libsolidity/AST.h> +#include <libsolidity/Compiler.h> + +using namespace std; + +namespace dev { +namespace solidity { + +void CompilerContext::initializeLocalVariables(unsigned _numVariables) +{ + if (_numVariables > 0) + { + *this << u256(0); + for (unsigned i = 1; i < _numVariables; ++i) + *this << eth::Instruction::DUP1; + m_asm.adjustDeposit(-_numVariables); + } +} + +int CompilerContext::getStackPositionOfVariable(const Declaration& _declaration) +{ + auto res = find(begin(m_localVariables), end(m_localVariables), &_declaration); + assert(res != m_localVariables.end()); + return end(m_localVariables) - res - 1 + m_asm.deposit(); +} + +eth::AssemblyItem CompilerContext::getFunctionEntryLabel(const FunctionDefinition& _function) const +{ + auto res = m_functionEntryLabels.find(&_function); + assert(res != m_functionEntryLabels.end()); + return res->second.tag(); +} + +} +} diff --git a/CompilerUtilities.h b/CompilerUtilities.h new file mode 100644 index 00000000..90367903 --- /dev/null +++ b/CompilerUtilities.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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Utilities for the solidity compiler. + */ + +#pragma once + +#include <libevmface/Instruction.h> +#include <liblll/Assembly.h> +#include <libsolidity/Types.h> + +namespace dev { +namespace solidity { + + +/** + * Context to be shared by all units that compile the same contract. + * It stores the generated bytecode and the position of identifiers in memory and on the stack. + */ +class CompilerContext +{ +public: + CompilerContext() {} + + void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); } + void initializeLocalVariables(unsigned _numVariables); + void addVariable(VariableDeclaration const& _declaration) { m_localVariables.push_back(&_declaration); } + /// Returns the distance of the given local variable from the top of the stack. + int getStackPositionOfVariable(Declaration const& _declaration); + + void addFunction(FunctionDefinition const& _function) { m_functionEntryLabels.insert(std::make_pair(&_function, m_asm.newTag())); } + eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const; + + void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } + + /// Appends a JUMPI instruction to a new tag and @returns the tag + eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } + /// Appends a JUMPI instruction to @a _tag + CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; } + /// Appends a JUMP to a new tag and @returns the tag + eth::AssemblyItem appendJump() { return m_asm.appendJump().tag(); } + /// Appends a JUMP to a specific tag + CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } + /// Appends pushing of a new tag and @returns the new tag. + eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } + /// @returns a new tag without pushing any opcodes or data + eth::AssemblyItem newTag() { return m_asm.newTag(); } + + /// Append elements to the current instruction list and adjust @a m_stackOffset. + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } + CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } + + bytes getAssembledBytecode() { return m_asm.assemble(); } +private: + eth::Assembly m_asm; + + /// Offsets of local variables on the stack. + std::vector<Declaration const*> m_localVariables; + /// Labels pointing to the entry points of funcitons. + std::map<FunctionDefinition const*, eth::AssemblyItem> m_functionEntryLabels; +}; + +} +} diff --git a/Exceptions.h b/Exceptions.h index 5a48c47d..1d981e7c 100644 --- a/Exceptions.h +++ b/Exceptions.h @@ -34,6 +34,7 @@ namespace solidity struct ParserError: virtual Exception {}; struct TypeError: virtual Exception {}; struct DeclarationError: virtual Exception {}; +struct CompilerError: virtual Exception {}; typedef boost::error_info<struct tag_sourcePosition, int> errinfo_sourcePosition; typedef boost::error_info<struct tag_sourceLocation, Location> errinfo_sourceLocation; diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp new file mode 100644 index 00000000..76fcc298 --- /dev/null +++ b/ExpressionCompiler.cpp @@ -0,0 +1,408 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Solidity AST to EVM bytecode compiler for expressions. + */ + +#include <cassert> +#include <utility> +#include <numeric> +#include <libsolidity/AST.h> +#include <libsolidity/ExpressionCompiler.h> + +using namespace std; + +namespace dev { +namespace solidity { + +void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression& _expression) +{ + ExpressionCompiler compiler(_context); + _expression.accept(compiler); +} + +bool ExpressionCompiler::visit(Assignment& _assignment) +{ + m_currentLValue = nullptr; + + Expression& rightHandSide = _assignment.getRightHandSide(); + rightHandSide.accept(*this); + Type const& resultType = *_assignment.getType(); + cleanHigherOrderBitsIfNeeded(*rightHandSide.getType(), resultType); + _assignment.getLeftHandSide().accept(*this); + + Token::Value op = _assignment.getAssignmentOperator(); + if (op != Token::ASSIGN) + { + // compound assignment + m_context << eth::Instruction::SWAP1; + appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), resultType); + } + else + m_context << eth::Instruction::POP; //@todo do not retrieve the value in the first place + + storeInLValue(_assignment); + return false; +} + +void ExpressionCompiler::endVisit(UnaryOperation& _unaryOperation) +{ + //@todo type checking and creating code for an operator should be in the same place: + // the operator should know how to convert itself and to which types it applies, so + // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that + // represents the operator + switch (_unaryOperation.getOperator()) + { + case Token::NOT: // ! + m_context << eth::Instruction::NOT; + break; + case Token::BIT_NOT: // ~ + m_context << eth::Instruction::BNOT; + break; + case Token::DELETE: // delete + { + // a -> a xor a (= 0). + // @todo semantics change for complex types + m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; + storeInLValue(_unaryOperation); + break; + } + case Token::INC: // ++ (pre- or postfix) + case Token::DEC: // -- (pre- or postfix) + if (!_unaryOperation.isPrefixOperation()) + m_context << eth::Instruction::DUP1; + m_context << u256(1); + if (_unaryOperation.getOperator() == Token::INC) + m_context << eth::Instruction::ADD; + else + m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap + if (_unaryOperation.isPrefixOperation()) + storeInLValue(_unaryOperation); + else + moveToLValue(_unaryOperation); + break; + case Token::ADD: // + + // unary add, so basically no-op + break; + case Token::SUB: // - + m_context << u256(0) << eth::Instruction::SUB; + break; + default: + assert(false); // invalid operation + } +} + +bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation) +{ + Expression& leftExpression = _binaryOperation.getLeftExpression(); + Expression& rightExpression = _binaryOperation.getRightExpression(); + Type const& resultType = *_binaryOperation.getType(); + Token::Value const op = _binaryOperation.getOperator(); + + if (op == Token::AND || op == Token::OR) + { + // special case: short-circuiting + appendAndOrOperatorCode(_binaryOperation); + } + else if (Token::isCompareOp(op)) + { + leftExpression.accept(*this); + rightExpression.accept(*this); + + // the types to compare have to be the same, but the resulting type is always bool + assert(*leftExpression.getType() == *rightExpression.getType()); + appendCompareOperatorCode(op, *leftExpression.getType()); + } + else + { + leftExpression.accept(*this); + cleanHigherOrderBitsIfNeeded(*leftExpression.getType(), resultType); + rightExpression.accept(*this); + cleanHigherOrderBitsIfNeeded(*rightExpression.getType(), resultType); + appendOrdinaryBinaryOperatorCode(op, resultType); + } + + // do not visit the child nodes, we already did that explicitly + return false; +} + +bool ExpressionCompiler::visit(FunctionCall& _functionCall) +{ + if (_functionCall.isTypeConversion()) + { + //@todo we only have integers and bools for now which cannot be explicitly converted + assert(_functionCall.getArguments().size() == 1); + Expression& firstArgument = *_functionCall.getArguments().front(); + firstArgument.accept(*this); + cleanHigherOrderBitsIfNeeded(*firstArgument.getType(), *_functionCall.getType()); + } + else + { + // Calling convention: Caller pushes return address and arguments + // Callee removes them and pushes return values + m_currentLValue = nullptr; + _functionCall.getExpression().accept(*this); + FunctionDefinition const* function = dynamic_cast<FunctionDefinition*>(m_currentLValue); + assert(function); + + eth::AssemblyItem returnLabel = m_context.pushNewTag(); + std::vector<ASTPointer<Expression>> const& arguments = _functionCall.getArguments(); + assert(arguments.size() == function->getParameters().size()); + for (unsigned i = 0; i < arguments.size(); ++i) + { + arguments[i]->accept(*this); + cleanHigherOrderBitsIfNeeded(*arguments[i]->getType(), + *function->getParameters()[i]->getType()); + } + + m_context.appendJumpTo(m_context.getFunctionEntryLabel(*function)); + m_context << returnLabel; + + // callee adds return parameters, but removes arguments and return label + m_context.adjustStackOffset(function->getReturnParameters().size() - arguments.size() - 1); + + // @todo for now, the return value of a function is its first return value, so remove + // all others + for (unsigned i = 1; i < function->getReturnParameters().size(); ++i) + m_context << eth::Instruction::POP; + } + return false; +} + +void ExpressionCompiler::endVisit(MemberAccess&) +{ + +} + +void ExpressionCompiler::endVisit(IndexAccess&) +{ + +} + +void ExpressionCompiler::endVisit(Identifier& _identifier) +{ + m_currentLValue = _identifier.getReferencedDeclaration(); + switch (_identifier.getType()->getCategory()) + { + case Type::Category::BOOL: + case Type::Category::INTEGER: + case Type::Category::REAL: + { + //@todo we also have to check where to retrieve them from once we add storage variables + unsigned stackPos = stackPositionOfLValue(); + if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_identifier.getLocation()) + << errinfo_comment("Stack too deep.")); + m_context << eth::dupInstruction(stackPos + 1); + break; + } + default: + break; + } +} + +void ExpressionCompiler::endVisit(Literal& _literal) +{ + switch (_literal.getType()->getCategory()) + { + case Type::Category::INTEGER: + case Type::Category::BOOL: + m_context << _literal.getType()->literalValue(_literal); + break; + default: + assert(false); // @todo + } +} + +void ExpressionCompiler::cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType) +{ + // If the type of one of the operands is extended, we need to remove all + // higher-order bits that we might have ignored in previous operations. + // @todo: store in the AST whether the operand might have "dirty" higher + // order bits + + if (_typeOnStack == _targetType) + return; + if (_typeOnStack.getCategory() == Type::Category::INTEGER && + _targetType.getCategory() == Type::Category::INTEGER) + { + //@todo + } + else + { + // If we get here, there is either an implementation missing to clean higher oder bits + // for non-integer types that are explicitly convertible or we got here in error. + assert(!_typeOnStack.isExplicitlyConvertibleTo(_targetType)); + assert(false); // these types should not be convertible. + } +} + +void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation& _binaryOperation) +{ + Token::Value const op = _binaryOperation.getOperator(); + assert(op == Token::OR || op == Token::AND); + + _binaryOperation.getLeftExpression().accept(*this); + m_context << eth::Instruction::DUP1; + if (op == Token::AND) + m_context << eth::Instruction::NOT; + eth::AssemblyItem endLabel = m_context.appendConditionalJump(); + _binaryOperation.getRightExpression().accept(*this); + m_context << endLabel; +} + +void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) +{ + if (_operator == Token::EQ || _operator == Token::NE) + { + m_context << eth::Instruction::EQ; + if (_operator == Token::NE) + m_context << eth::Instruction::NOT; + } + else + { + IntegerType const* type = dynamic_cast<IntegerType const*>(&_type); + assert(type); + bool const isSigned = type->isSigned(); + + // note that EVM opcodes compare like "stack[0] < stack[1]", + // but our left value is at stack[1], so everyhing is reversed. + switch (_operator) + { + case Token::GTE: + m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT) + << eth::Instruction::NOT; + break; + case Token::LTE: + m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT) + << eth::Instruction::NOT; + break; + case Token::GT: + m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT); + break; + case Token::LT: + m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT); + break; + default: + assert(false); + } + } +} + +void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type) +{ + if (Token::isArithmeticOp(_operator)) + appendArithmeticOperatorCode(_operator, _type); + else if (Token::isBitOp(_operator)) + appendBitOperatorCode(_operator); + else if (Token::isShiftOp(_operator)) + appendShiftOperatorCode(_operator); + else + assert(false); // unknown binary operator +} + +void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) +{ + IntegerType const* type = dynamic_cast<IntegerType const*>(&_type); + assert(type); + bool const isSigned = type->isSigned(); + + switch (_operator) + { + case Token::ADD: + m_context << eth::Instruction::ADD; + break; + case Token::SUB: + m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; + break; + case Token::MUL: + m_context << eth::Instruction::MUL; + break; + case Token::DIV: + m_context << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); + break; + case Token::MOD: + m_context << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + break; + default: + assert(false); + } +} + +void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) +{ + switch (_operator) + { + case Token::BIT_OR: + m_context << eth::Instruction::OR; + break; + case Token::BIT_AND: + m_context << eth::Instruction::AND; + break; + case Token::BIT_XOR: + m_context << eth::Instruction::XOR; + break; + default: + assert(false); + } +} + +void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) +{ + switch (_operator) + { + case Token::SHL: + assert(false); //@todo + break; + case Token::SAR: + assert(false); //@todo + break; + default: + assert(false); + } +} + +void ExpressionCompiler::storeInLValue(Expression const& _expression) +{ + moveToLValue(_expression); + unsigned stackPos = stackPositionOfLValue(); + if (stackPos > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Stack too deep.")); + m_context << eth::dupInstruction(stackPos + 1); +} + +void ExpressionCompiler::moveToLValue(Expression const& _expression) +{ + unsigned stackPos = stackPositionOfLValue(); + if (stackPos > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Stack too deep.")); + else if (stackPos > 0) + m_context << eth::swapInstruction(stackPos) << eth::Instruction::POP; +} + +unsigned ExpressionCompiler::stackPositionOfLValue() const +{ + assert(m_currentLValue); + return m_context.getStackPositionOfVariable(*m_currentLValue); +} + +} +} diff --git a/ExpressionCompiler.h b/ExpressionCompiler.h new file mode 100644 index 00000000..28a04f52 --- /dev/null +++ b/ExpressionCompiler.h @@ -0,0 +1,79 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2014 + * Solidity AST to EVM bytecode compiler for expressions. + */ + +#include <libsolidity/ASTVisitor.h> +#include <libsolidity/CompilerUtilities.h> + +namespace dev { +namespace solidity { + +/// Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream +/// of EVM instructions. It needs a compiler context that is the same for the whole compilation +/// unit. +class ExpressionCompiler: private ASTVisitor +{ +public: + /// Compile the given @a _expression into the @a _context. + static void compileExpression(CompilerContext& _context, Expression& _expression); + + /// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type. + static void cleanHigherOrderBitsIfNeeded(Type const& _typeOnStack, Type const& _targetType); + +private: + ExpressionCompiler(CompilerContext& _compilerContext): m_currentLValue(nullptr), m_context(_compilerContext) {} + + virtual bool visit(Assignment& _assignment) override; + virtual void endVisit(UnaryOperation& _unaryOperation) override; + virtual bool visit(BinaryOperation& _binaryOperation) override; + virtual bool visit(FunctionCall& _functionCall) override; + virtual void endVisit(MemberAccess& _memberAccess) override; + virtual void endVisit(IndexAccess& _indexAccess) override; + virtual void endVisit(Identifier& _identifier) override; + virtual void endVisit(Literal& _literal) override; + + ///@{ + ///@name Append code for various operator types + void appendAndOrOperatorCode(BinaryOperation& _binaryOperation); + void appendCompareOperatorCode(Token::Value _operator, Type const& _type); + void appendOrdinaryBinaryOperatorCode(Token::Value _operator, Type const& _type); + + void appendArithmeticOperatorCode(Token::Value _operator, Type const& _type); + void appendBitOperatorCode(Token::Value _operator); + void appendShiftOperatorCode(Token::Value _operator); + /// @} + + /// Stores the value on top of the stack in the current lvalue and copies that value to the + /// top of the stack again + void storeInLValue(Expression const& _expression); + /// The same as storeInLValue but do not again retrieve the value to the top of the stack. + void moveToLValue(Expression const& _expression); + /// Returns the position of @a m_currentLValue in the stack, where 0 is the top of the stack. + unsigned stackPositionOfLValue() const; + void adjustStackOffset(eth::Instruction _instruction); + + Declaration* m_currentLValue; + CompilerContext& m_context; +}; + + +} +} diff --git a/NameAndTypeResolver.cpp b/NameAndTypeResolver.cpp index e9d90dc8..4b77ed13 100644 --- a/NameAndTypeResolver.cpp +++ b/NameAndTypeResolver.cpp @@ -20,11 +20,12 @@ * Parser part that determines the declarations corresponding to names and the types of expressions. */ +#include <cassert> #include <libsolidity/NameAndTypeResolver.h> - #include <libsolidity/AST.h> #include <libsolidity/Exceptions.h> -#include <boost/assert.hpp> + +using namespace std; namespace dev { @@ -54,12 +55,15 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) m_currentScope = &m_scopes[function.get()]; function->getBody().checkTypeRequirements(); } + m_currentScope = &m_scopes[nullptr]; } -void NameAndTypeResolver::reset() +Declaration* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const { - m_scopes.clear(); - m_currentScope = nullptr; + auto iterator = m_scopes.find(_scope); + if (iterator == end(m_scopes)) + return nullptr; + return iterator->second.resolveName(_name, false); } Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive) @@ -67,8 +71,13 @@ Declaration* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name return m_currentScope->resolveName(_name, _recursive); } +void NameAndTypeResolver::reset() +{ + m_scopes.clear(); + m_currentScope = nullptr; +} -DeclarationRegistrationHelper::DeclarationRegistrationHelper(std::map<ASTNode*, Scope>& _scopes, +DeclarationRegistrationHelper::DeclarationRegistrationHelper(map<ASTNode const*, Scope>& _scopes, ASTNode& _astRoot): m_scopes(_scopes), m_currentScope(&m_scopes[nullptr]) { @@ -100,42 +109,48 @@ void DeclarationRegistrationHelper::endVisit(StructDefinition&) bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) { registerDeclaration(_function, true); + m_currentFunction = &_function; return true; } void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) { + m_currentFunction = nullptr; closeCurrentScope(); } -bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) +void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefinition) { - registerDeclaration(_declaration, false); - return true; + // Register the local variables with the function + // This does not fit here perfectly, but it saves us another AST visit. + assert(m_currentFunction); + m_currentFunction->addLocalVariable(_variableDefinition.getDeclaration()); } -void DeclarationRegistrationHelper::endVisit(VariableDeclaration&) +bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) { + registerDeclaration(_declaration, false); + return true; } void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _node) { - std::map<ASTNode*, Scope>::iterator iter; + map<ASTNode const*, Scope>::iterator iter; bool newlyAdded; - std::tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope)); - BOOST_ASSERT(newlyAdded); + tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope)); + assert(newlyAdded); m_currentScope = &iter->second; } void DeclarationRegistrationHelper::closeCurrentScope() { - BOOST_ASSERT(m_currentScope); - m_currentScope = m_currentScope->getOuterScope(); + assert(m_currentScope); + m_currentScope = m_currentScope->getEnclosingScope(); } void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) { - BOOST_ASSERT(m_currentScope); + assert(m_currentScope); if (!m_currentScope->registerDeclaration(_declaration)) BOOST_THROW_EXCEPTION(DeclarationError() << errinfo_sourceLocation(_declaration.getLocation()) << errinfo_comment("Identifier already declared.")); @@ -162,7 +177,7 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) bool ReferencesResolver::visit(Return& _return) { - BOOST_ASSERT(m_returnParameters); + assert(m_returnParameters); _return.setFunctionReturnParameters(*m_returnParameters); return true; } diff --git a/NameAndTypeResolver.h b/NameAndTypeResolver.h index 7abcbb0c..cdc334a6 100644 --- a/NameAndTypeResolver.h +++ b/NameAndTypeResolver.h @@ -41,6 +41,14 @@ public: NameAndTypeResolver() {} void resolveNamesAndTypes(ContractDefinition& _contract); + + /// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted, + /// the global scope is used (i.e. the one containing only the contract). + /// @returns a pointer to the declaration on success or nullptr on failure. + Declaration* resolveName(ASTString const& _name, Declaration const* _scope = nullptr) const; + + /// Resolves a name in the "current" scope. Should only be called during the initial + /// resolving phase. Declaration* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true); private: @@ -48,7 +56,7 @@ private: //! 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<ASTNode*, Scope> m_scopes; + std::map<ASTNode const*, Scope> m_scopes; Scope* m_currentScope; }; @@ -58,7 +66,7 @@ private: class DeclarationRegistrationHelper: private ASTVisitor { public: - DeclarationRegistrationHelper(std::map<ASTNode*, Scope>& _scopes, ASTNode& _astRoot); + DeclarationRegistrationHelper(std::map<ASTNode const*, Scope>& _scopes, ASTNode& _astRoot); private: bool visit(ContractDefinition& _contract); @@ -67,15 +75,16 @@ private: void endVisit(StructDefinition& _struct); bool visit(FunctionDefinition& _function); void endVisit(FunctionDefinition& _function); + void endVisit(VariableDefinition& _variableDefinition); bool visit(VariableDeclaration& _declaration); - void endVisit(VariableDeclaration& _declaration); void enterNewSubScope(ASTNode& _node); void closeCurrentScope(); void registerDeclaration(Declaration& _declaration, bool _opensScope); - std::map<ASTNode*, Scope>& m_scopes; + std::map<ASTNode const*, Scope>& m_scopes; Scope* m_currentScope; + FunctionDefinition* m_currentFunction; }; //! Resolves references to declarations (of variables and types) and also establishes the link @@ -285,9 +285,9 @@ ASTPointer<Statement> Parser::parseStatement() } break; default: - // distinguish between variable definition (and potentially assignment) and expressions + // distinguish between variable definition (and potentially assignment) and expression statement // (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 + // We have a variable definition if we get 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 || @@ -295,8 +295,8 @@ ASTPointer<Statement> Parser::parseStatement() (m_scanner->getCurrentToken() == Token::IDENTIFIER && m_scanner->peekNextToken() == Token::IDENTIFIER)) statement = parseVariableDefinition(); - else // "ordinary" expression - statement = parseExpression(); + else // "ordinary" expression statement + statement = parseExpressionStatement(); } expectToken(Token::SEMICOLON); return statement; @@ -351,6 +351,14 @@ ASTPointer<VariableDefinition> Parser::parseVariableDefinition() return nodeFactory.createNode<VariableDefinition>(variable, value); } +ASTPointer<ExpressionStatement> Parser::parseExpressionStatement() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer<Expression> expression = parseExpression(); + nodeFactory.setEndPositionFromNode(expression); + return nodeFactory.createNode<ExpressionStatement>(expression); +} + ASTPointer<Expression> Parser::parseExpression() { ASTNodeFactory nodeFactory(*this); @@ -455,8 +463,7 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() { case Token::TRUE_LITERAL: case Token::FALSE_LITERAL: - expression = nodeFactory.createNode<Literal>(token, ASTPointer<ASTString>()); - m_scanner->next(); + expression = nodeFactory.createNode<Literal>(token, getLiteralAndAdvance()); break; case Token::NUMBER: case Token::STRING_LITERAL: @@ -44,8 +44,8 @@ private: /// End position of the current token int getEndPosition() const; - /// Parsing functions for the AST nodes - /// @{ + ///@{ + ///@name Parsing functions for the AST nodes ASTPointer<ContractDefinition> parseContractDefinition(); ASTPointer<FunctionDefinition> parseFunctionDefinition(bool _isPublic); ASTPointer<StructDefinition> parseStructDefinition(); @@ -58,22 +58,24 @@ private: ASTPointer<IfStatement> parseIfStatement(); ASTPointer<WhileStatement> parseWhileStatement(); ASTPointer<VariableDefinition> parseVariableDefinition(); + ASTPointer<ExpressionStatement> parseExpressionStatement(); ASTPointer<Expression> parseExpression(); ASTPointer<Expression> parseBinaryExpression(int _minPrecedence = 4); ASTPointer<Expression> parseUnaryExpression(); ASTPointer<Expression> parseLeftHandSideExpression(); ASTPointer<Expression> parsePrimaryExpression(); std::vector<ASTPointer<Expression>> parseFunctionCallArguments(); - /// @} + ///@} + + ///@{ + ///@name Helper functions - /// Helper functions - /// @{ /// If current token value is not _value, throw exception otherwise advance token. void expectToken(Token::Value _value); Token::Value expectAssignmentOperator(); ASTPointer<ASTString> expectIdentifierToken(); ASTPointer<ASTString> getLiteralAndAdvance(); - /// @} + ///@} /// Creates a @ref ParserError exception and annotates it with the current position and the /// given @a _description. diff --git a/Scanner.cpp b/Scanner.cpp index 35da248a..3148de52 100644 --- a/Scanner.cpp +++ b/Scanner.cpp @@ -50,11 +50,13 @@ * Solidity scanner. */ +#include <cassert> #include <algorithm> #include <tuple> - #include <libsolidity/Scanner.h> +using namespace std; + namespace dev { namespace solidity @@ -118,7 +120,7 @@ void Scanner::reset(CharStream const& _source) bool Scanner::scanHexNumber(char& o_scannedNumber, int _expectedLength) { - BOOST_ASSERT(_expectedLength <= 4); // prevent overflow + assert(_expectedLength <= 4); // prevent overflow char x = 0; for (int i = 0; i < _expectedLength; i++) { @@ -178,7 +180,7 @@ Token::Value Scanner::skipSingleLineComment() Token::Value Scanner::skipMultiLineComment() { - BOOST_ASSERT(m_char == '*'); + assert(m_char == '*'); advance(); while (!isSourcePastEndOfInput()) { @@ -471,7 +473,7 @@ void Scanner::scanDecimalDigits() Token::Value Scanner::scanNumber(bool _periodSeen) { - BOOST_ASSERT(IsDecimalDigit(m_char)); // the first digit of the number or the fraction + 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) @@ -513,7 +515,7 @@ Token::Value Scanner::scanNumber(bool _periodSeen) // 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 + assert(kind != HEX); // 'e'/'E' must be scanned as part of the hex number if (kind != DECIMAL) return Token::ILLEGAL; // scan exponent addLiteralCharAndAdvance(); @@ -607,9 +609,9 @@ Token::Value Scanner::scanNumber(bool _periodSeen) KEYWORD("while", Token::WHILE) \ -static Token::Value KeywordOrIdentifierToken(std::string const& input) +static Token::Value KeywordOrIdentifierToken(string const& input) { - BOOST_ASSERT(!input.empty()); + assert(!input.empty()); int const kMinLength = 2; int const kMaxLength = 10; if (input.size() < kMinLength || input.size() > kMaxLength) @@ -637,7 +639,7 @@ case ch: Token::Value Scanner::scanIdentifierOrKeyword() { - BOOST_ASSERT(IsIdentifierStart(m_char)); + assert(IsIdentifierStart(m_char)); LiteralScope literal(this); addLiteralCharAndAdvance(); // Scan the rest of the identifier characters. @@ -659,42 +661,41 @@ char CharStream::advanceAndGet() char CharStream::rollback(size_t _amount) { - BOOST_ASSERT(m_pos >= _amount); + assert(m_pos >= _amount); m_pos -= _amount; return get(); } -std::string CharStream::getLineAtPosition(int _position) const +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<size_type>(m_source.size(), _position); + using size_type = string::size_type; + size_type searchStart = min<size_type>(m_source.size(), _position); if (searchStart > 0) searchStart--; size_type lineStart = m_source.rfind('\n', searchStart); - if (lineStart == std::string::npos) + if (lineStart == string::npos) lineStart = 0; else lineStart++; - return m_source.substr(lineStart, - std::min(m_source.find('\n', lineStart), - m_source.size()) - lineStart); + return m_source.substr(lineStart, min(m_source.find('\n', lineStart), + m_source.size()) - lineStart); } -std::tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const +tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const { - using size_type = std::string::size_type; - size_type searchPosition = std::min<size_type>(m_source.size(), _position); - int lineNumber = std::count(m_source.begin(), m_source.begin() + searchPosition, '\n'); + using size_type = string::size_type; + size_type searchPosition = min<size_type>(m_source.size(), _position); + int lineNumber = 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; + lineStart = lineStart == string::npos ? 0 : lineStart + 1; } - return std::tuple<int, int>(lineNumber, searchPosition - lineStart); + return tuple<int, int>(lineNumber, searchPosition - lineStart); } @@ -52,8 +52,6 @@ #pragma once -#include <boost/assert.hpp> - #include <libdevcore/Common.h> #include <libdevcore/Log.h> #include <libdevcore/CommonData.h> @@ -81,12 +79,13 @@ public: char advanceAndGet(); char rollback(size_t _amount); + ///@{ + ///@name Error printing helper functions /// 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<int, int> translatePositionToLineColumn(int _position) const; - /// @} + ///@} private: std::string m_source; @@ -119,29 +118,31 @@ public: /// Returns the next token and advances input. Token::Value next(); - /// Information about the current token - /// @{ + ///@{ + ///@name 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; } - /// @} + ///@} + + ///@{ + ///@name Information about the next token - /// 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. + ///@{ + ///@name Error printing helper functions + /// 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<int, int> translatePositionToLineColumn(int _position) const { return m_source.translatePositionToLineColumn(_position); } - /// @} + ///@} private: // Used for the current and look-ahead token. @@ -152,13 +153,13 @@ private: std::string literal; }; - /// Literal buffer support - /// @{ + ///@{ + ///@name 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); } @@ -169,9 +170,10 @@ private: bool scanHexNumber(char& o_scannedNumber, int _expectedLength); - // Scans a single JavaScript token. + /// Scans a single JavaScript token. void scanToken(); + /// Skips all whitespace and @returns true if something was skipped. bool skipWhitespace(); Token::Value skipSingleLineComment(); Token::Value skipMultiLineComment(); @@ -41,8 +41,8 @@ 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) - return m_outerScope->resolveName(_name, true); + if (_recursive && m_enclosingScope) + return m_enclosingScope->resolveName(_name, true); return nullptr; } @@ -32,18 +32,20 @@ namespace dev namespace solidity { +/// Container that stores mappings betwee names and declarations. It also contains a link to the +/// enclosing scope. class Scope { public: - explicit Scope(Scope* _outerScope = nullptr): m_outerScope(_outerScope) {} + explicit Scope(Scope* _enclosingScope = nullptr): m_enclosingScope(_enclosingScope) {} /// 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; } + Scope* getEnclosingScope() const { return m_enclosingScope; } private: - Scope* m_outerScope; + Scope* m_enclosingScope; std::map<ASTString, Declaration*> m_declarations; }; diff --git a/SourceReferenceFormatter.cpp b/SourceReferenceFormatter.cpp index b270342c..d3f2152a 100644 --- a/SourceReferenceFormatter.cpp +++ b/SourceReferenceFormatter.cpp @@ -24,57 +24,59 @@ #include <libsolidity/Scanner.h> #include <libsolidity/Exceptions.h> +using namespace std; + namespace dev { namespace solidity { -void SourceReferenceFormatter::printSourceLocation(std::ostream& _stream, +void SourceReferenceFormatter::printSourceLocation(ostream& _stream, Location const& _location, Scanner const& _scanner) { int startLine; int startColumn; - std::tie(startLine, startColumn) = _scanner.translatePositionToLineColumn(_location.start); + tie(startLine, startColumn) = _scanner.translatePositionToLineColumn(_location.start); _stream << "starting at line " << (startLine + 1) << ", column " << (startColumn + 1) << "\n"; int endLine; int endColumn; - std::tie(endLine, endColumn) = _scanner.translatePositionToLineColumn(_location.end); + tie(endLine, endColumn) = _scanner.translatePositionToLineColumn(_location.end); if (startLine == endLine) { - _stream << _scanner.getLineAtPosition(_location.start) << std::endl - << std::string(startColumn, ' ') << "^"; + _stream << _scanner.getLineAtPosition(_location.start) << endl + << string(startColumn, ' ') << "^"; if (endColumn > startColumn + 2) - _stream << std::string(endColumn - startColumn - 2, '-'); + _stream << string(endColumn - startColumn - 2, '-'); if (endColumn > startColumn + 1) _stream << "^"; - _stream << std::endl; + _stream << endl; } else - _stream << _scanner.getLineAtPosition(_location.start) << std::endl - << std::string(startColumn, ' ') << "^\n" + _stream << _scanner.getLineAtPosition(_location.start) << endl + << string(startColumn, ' ') << "^\n" << "Spanning multiple lines.\n"; } -void SourceReferenceFormatter::printSourcePosition(std::ostream& _stream, +void SourceReferenceFormatter::printSourcePosition(ostream& _stream, int _position, const Scanner& _scanner) { int line; int column; - std::tie(line, column) = _scanner.translatePositionToLineColumn(_position); - _stream << "at line " << (line + 1) << ", column " << (column + 1) << std::endl - << _scanner.getLineAtPosition(_position) << std::endl - << std::string(column, ' ') << "^" << std::endl; + tie(line, column) = _scanner.translatePositionToLineColumn(_position); + _stream << "at line " << (line + 1) << ", column " << (column + 1) << endl + << _scanner.getLineAtPosition(_position) << endl + << string(column, ' ') << "^" << endl; } -void SourceReferenceFormatter::printExceptionInformation(std::ostream& _stream, +void SourceReferenceFormatter::printExceptionInformation(ostream& _stream, Exception const& _exception, - std::string const& _name, + string const& _name, Scanner const& _scanner) { _stream << _name; - if (std::string const* description = boost::get_error_info<errinfo_comment>(_exception)) + if (string const* description = boost::get_error_info<errinfo_comment>(_exception)) _stream << ": " << *description; if (int const* position = boost::get_error_info<errinfo_sourcePosition>(_exception)) @@ -42,8 +42,7 @@ #pragma once -#include <boost/assert.hpp> - +#include <cassert> #include <libdevcore/Common.h> #include <libdevcore/Log.h> @@ -225,7 +224,7 @@ public: // (e.g. "LT" for the token LT). static char const* getName(Value tok) { - BOOST_ASSERT(tok < NUM_TOKENS); // tok is unsigned + assert(tok < NUM_TOKENS); // tok is unsigned return m_name[tok]; } @@ -236,6 +235,7 @@ public: 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 isArithmeticOp(Value op) { return ADD <= op && op <= MOD; } static bool isCompareOp(Value op) { return EQ <= op && op <= IN; } static bool isOrderedRelationalCompareOp(Value op) { @@ -251,7 +251,7 @@ public: static Value negateCompareOp(Value op) { - BOOST_ASSERT(isArithmeticCompareOp(op)); + assert(isArithmeticCompareOp(op)); switch (op) { case EQ: @@ -267,14 +267,14 @@ public: case GTE: return LT; default: - BOOST_ASSERT(false); // should not get here + assert(false); // should not get here return op; } } static Value reverseCompareOp(Value op) { - BOOST_ASSERT(isArithmeticCompareOp(op)); + assert(isArithmeticCompareOp(op)); switch (op) { case EQ: @@ -290,14 +290,14 @@ public: case GTE: return LTE; default: - BOOST_ASSERT(false); // should not get here + assert(false); // should not get here return op; } } static Value AssignmentToBinaryOp(Value op) { - BOOST_ASSERT(isAssignmentOp(op) && op != ASSIGN); + assert(isAssignmentOp(op) && op != ASSIGN); return Token::Value(op + (BIT_OR - ASSIGN_BIT_OR)); } @@ -311,7 +311,7 @@ public: // have a (unique) string (e.g. an IDENTIFIER). static char const* toString(Value tok) { - BOOST_ASSERT(tok < NUM_TOKENS); // tok is unsigned. + assert(tok < NUM_TOKENS); // tok is unsigned. return m_string[tok]; } @@ -319,7 +319,7 @@ public: // operators; returns 0 otherwise. static int precedence(Value tok) { - BOOST_ASSERT(tok < NUM_TOKENS); // tok is unsigned. + assert(tok < NUM_TOKENS); // tok is unsigned. return m_precedence[tok]; } @@ -20,7 +20,9 @@ * Solidity data types */ +#include <cassert> #include <libdevcore/CommonIO.h> +#include <libdevcore/CommonData.h> #include <libsolidity/Types.h> #include <libsolidity/AST.h> @@ -50,7 +52,7 @@ std::shared_ptr<Type> Type::fromElementaryTypeName(Token::Value _typeToken) else if (_typeToken == Token::BOOL) return std::make_shared<BoolType>(); else - BOOST_ASSERT(false); // @todo add other tyes + assert(false); // @todo add other tyes return std::shared_ptr<Type>(); } @@ -61,7 +63,7 @@ std::shared_ptr<Type> Type::fromUserDefinedTypeName(UserDefinedTypeName const& _ std::shared_ptr<Type> Type::fromMapping(Mapping const&) { - BOOST_ASSERT(false); //@todo not yet implemented + assert(false); //@todo not yet implemented return std::shared_ptr<Type>(); } @@ -92,12 +94,12 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): { if (isAddress()) _bits = 160; - BOOST_ASSERT(_bits > 0 && _bits <= 256 && _bits % 8 == 0); + assert(_bits > 0 && _bits <= 256 && _bits % 8 == 0); } bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const { - if (_convertTo.getCategory() != Category::INTEGER) + if (_convertTo.getCategory() != getCategory()) return false; IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); if (convertTo.m_bits < m_bits) @@ -114,7 +116,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - return _convertTo.getCategory() == Category::INTEGER; + return _convertTo.getCategory() == getCategory(); } bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const @@ -129,7 +131,24 @@ bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const bool IntegerType::acceptsUnaryOperator(Token::Value _operator) const { - return _operator == Token::DELETE || (!isAddress() && _operator == Token::BIT_NOT); + if (_operator == Token::DELETE) + return true; + if (isAddress()) + return false; + if (_operator == Token::BIT_NOT) + return true; + if (isHash()) + return false; + return _operator == Token::ADD || _operator == Token::SUB || + _operator == Token::INC || _operator == Token::DEC; +} + +bool IntegerType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + IntegerType const& other = dynamic_cast<IntegerType const&>(_other); + return other.m_bits == m_bits && other.m_modifier == m_modifier; } std::string IntegerType::toString() const @@ -140,11 +159,19 @@ std::string IntegerType::toString() const return prefix + dev::toString(m_bits); } +u256 IntegerType::literalValue(const Literal& _literal) const +{ + bigint value(_literal.getValue()); + //@todo check that the number is not too large + //@todo does this work for signed numbers? + return u256(value); +} + 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) + if (_convertTo.getCategory() == getCategory()) { IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo); if (!convertTo.isAddress()) @@ -153,22 +180,55 @@ bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const return isImplicitlyConvertibleTo(_convertTo); } -bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const +u256 BoolType::literalValue(const Literal& _literal) const { - if (_convertTo.getCategory() != Category::CONTRACT) + if (_literal.getToken() == Token::TRUE_LITERAL) + return u256(1); + else if (_literal.getToken() == Token::FALSE_LITERAL) + return u256(0); + else + assert(false); +} + +bool ContractType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) return false; - ContractType const& convertTo = dynamic_cast<ContractType const&>(_convertTo); - return &m_contract == &convertTo.m_contract; + ContractType const& other = dynamic_cast<ContractType const&>(_other); + return other.m_contract == m_contract; } -bool StructType::isImplicitlyConvertibleTo(Type const& _convertTo) const +bool StructType::operator==(const Type& _other) const { - if (_convertTo.getCategory() != Category::STRUCT) + if (_other.getCategory() != getCategory()) return false; - StructType const& convertTo = dynamic_cast<StructType const&>(_convertTo); - return &m_struct == &convertTo.m_struct; + StructType const& other = dynamic_cast<StructType const&>(_other); + return other.m_struct == m_struct; } +bool FunctionType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + FunctionType const& other = dynamic_cast<FunctionType const&>(_other); + return other.m_function == m_function; +} + +bool MappingType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + MappingType const& other = dynamic_cast<MappingType const&>(_other); + return *other.m_keyType == *m_keyType && *other.m_valueType == *m_valueType; +} + +bool TypeType::operator==(const Type& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + TypeType const& other = dynamic_cast<TypeType const&>(_other); + return *getActualType() == *other.getActualType(); +} } } @@ -25,7 +25,7 @@ #include <memory> #include <string> #include <boost/noncopyable.hpp> -#include <boost/assert.hpp> +#include <libdevcore/Common.h> #include <libsolidity/ASTForward.h> #include <libsolidity/Token.h> @@ -36,6 +36,7 @@ namespace solidity // @todo realMxN, string<N>, mapping +/// Abstract base class that forms the root of the type hierarchy. class Type: private boost::noncopyable { public: @@ -44,15 +45,19 @@ public: INTEGER, BOOL, REAL, STRING, CONTRACT, STRUCT, FUNCTION, MAPPING, VOID, TYPE }; - //! factory functions that convert an AST TypeName to a Type. + ///@{ + ///@name Factory functions + /// Factory functions that convert an AST @ref TypeName to a Type. static std::shared_ptr<Type> fromElementaryTypeName(Token::Value _typeToken); static std::shared_ptr<Type> fromUserDefinedTypeName(UserDefinedTypeName const& _typeName); static std::shared_ptr<Type> fromMapping(Mapping const& _typeName); + /// @} + /// Auto-detect the proper type for a literal static std::shared_ptr<Type> forLiteral(Literal const& _literal); virtual Category getCategory() const = 0; - virtual bool isImplicitlyConvertibleTo(Type const&) const { return false; } + virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const { return isImplicitlyConvertibleTo(_convertTo); @@ -60,9 +65,14 @@ public: virtual bool acceptsBinaryOperator(Token::Value) const { return false; } virtual bool acceptsUnaryOperator(Token::Value) const { return false; } + virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); } + virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } + virtual std::string toString() const = 0; + virtual u256 literalValue(Literal const&) const { assert(false); } }; +/// Any kind of integer type including hash and address. class IntegerType: public Type { public: @@ -81,7 +91,10 @@ public: virtual bool acceptsBinaryOperator(Token::Value _operator) const override; virtual bool acceptsUnaryOperator(Token::Value _operator) const override; + virtual bool operator==(Type const& _other) const override; + virtual std::string toString() const override; + virtual u256 literalValue(Literal const& _literal) const override; int getNumBits() const { return m_bits; } bool isHash() const { return m_modifier == Modifier::HASH || m_modifier == Modifier::ADDRESS; } @@ -93,14 +106,11 @@ private: Modifier m_modifier; }; +/// The boolean type. 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 { @@ -110,15 +120,19 @@ public: { return _operator == Token::NOT || _operator == Token::DELETE; } + virtual std::string toString() const override { return "bool"; } + virtual u256 literalValue(Literal const& _literal) const override; }; +/// The type of a contract instance, there is one distinct type for each contract definition. class ContractType: public Type { public: virtual Category getCategory() const override { return Category::CONTRACT; } ContractType(ContractDefinition const& _contract): m_contract(_contract) {} - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const; + + virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override { return "contract{...}"; } @@ -126,17 +140,18 @@ private: ContractDefinition const& m_contract; }; +/// The type of a struct instance, there is one distinct type per struct definition. class StructType: public Type { public: virtual Category getCategory() const override { 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; } + virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override { return "struct{...}"; } @@ -144,6 +159,7 @@ private: StructDefinition const& m_struct; }; +/// The type of a function, there is one distinct type per function definition. class FunctionType: public Type { public: @@ -154,10 +170,13 @@ public: virtual std::string toString() const override { return "function(...)returns(...)"; } + virtual bool operator==(Type const& _other) const override; + private: FunctionDefinition const& m_function; }; +/// The type of a mapping, there is one distinct type per key/value type pair. class MappingType: public Type { public: @@ -165,19 +184,26 @@ public: MappingType() {} virtual std::string toString() const override { return "mapping(...=>...)"; } + virtual bool operator==(Type const& _other) const override; + private: - //@todo + std::shared_ptr<Type const> m_keyType; + std::shared_ptr<Type const> m_valueType; }; -//@todo should be changed into "empty anonymous struct" +/// The void type, can only be implicitly used as the type that is returned by functions without +/// return parameters. class VoidType: public Type { public: virtual Category getCategory() const override { return Category::VOID; } VoidType() {} + virtual std::string toString() const override { return "void"; } }; +/// The type of a type reference. The type of "uint32" when used in "a = uint32(2)" is an example +/// of a TypeType. class TypeType: public Type { public: @@ -186,6 +212,8 @@ public: std::shared_ptr<Type const> const& getActualType() const { return m_actualType; } + virtual bool operator==(Type const& _other) const override; + virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } private: |