From 36bedf6a7998b0dbd48800998e9179789d0a3e1c Mon Sep 17 00:00:00 2001 From: Marek Kotewicz Date: Sat, 21 Feb 2015 13:41:25 +0100 Subject: [PATCH 001/221] checking jsonrpccpp version when looking for library. version 0.4 exact required --- cmake/EthDependencies.cmake | 9 +----- ...sonRpcCpp.cmake => Findjson_rpc_cpp.cmake} | 29 +++++++++++++++++-- 2 files changed, 27 insertions(+), 11 deletions(-) rename cmake/{FindJsonRpcCpp.cmake => Findjson_rpc_cpp.cmake} (65%) diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 3f6cbcd77..1b21280cb 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -45,16 +45,9 @@ find_package (Jsoncpp 0.60 REQUIRED) message(" - Jsoncpp header: ${JSONCPP_INCLUDE_DIRS}") message(" - Jsoncpp lib : ${JSONCPP_LIBRARIES}") -# TODO the JsonRpcCpp package does not yet check for correct version number -# json-rpc-cpp support is currently not mandatory -# TODO make headless client optional # TODO get rid of -DETH_JSONRPC if (JSONRPC) - - find_package (JsonRpcCpp 0.3.2) - if (NOT JSON_RPC_CPP_FOUND) - message (FATAL_ERROR "JSONRPC 0.3.2. not found") - endif() + find_package (json_rpc_cpp 0.4 EXACT REQUIRED) message (" - json-rpc-cpp header: ${JSON_RPC_CPP_INCLUDE_DIRS}") message (" - json-rpc-cpp lib : ${JSON_RPC_CPP_LIBRARIES}") add_definitions(-DETH_JSONRPC) diff --git a/cmake/FindJsonRpcCpp.cmake b/cmake/Findjson_rpc_cpp.cmake similarity index 65% rename from cmake/FindJsonRpcCpp.cmake rename to cmake/Findjson_rpc_cpp.cmake index 2ca176f68..8b705aeeb 100644 --- a/cmake/FindJsonRpcCpp.cmake +++ b/cmake/Findjson_rpc_cpp.cmake @@ -10,6 +10,11 @@ # JSON_RPC_CPP_SERVER_LIBRARIES, the libraries needed to use json-rpc-cpp-server # JSON_RPC_CPP_CLIENT_LIBRARIES, the libraries needed to use json-rpc-cpp-client # JSON_RCP_CPP_FOUND, If false, do not try to use json-rpc-cpp. +# JSON_RPC_CPP_VERSION, version of library +# JSON_RPC_CPP_VERSION_MAJOR +# JSON_RPC_CPP_VERSION_MINOR +# JSON_RPC_CPP_VERSION_PATCH + # only look in default directories find_path( @@ -90,10 +95,28 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() +if (JSON_RPC_CPP_INCLUDE_DIR) + set (JSON_RPC_CPP_VERSION_HEADER "${JSON_RPC_CPP_INCLUDE_DIR}/jsonrpccpp/version.h") + if (EXISTS ${JSON_RPC_CPP_VERSION_HEADER}) + file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_MAJOR REGEX "^#define JSONRPC_CPP_MAJOR_VERSION[ \t]+[0-9]+$") + file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_MINOR REGEX "^#define JSONRPC_CPP_MINOR_VERSION[ \t]+[0-9]+$") + file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_PATCH REGEX "^#define JSONRPC_CPP_PATCH_VERSION[ \t]+[0-9]+$") + string (REGEX REPLACE "^#define JSONRPC_CPP_MAJOR_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_MAJOR ${JSON_RPC_CPP_VERSION_MAJOR}) + string (REGEX REPLACE "^#define JSONRPC_CPP_MINOR_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_MINOR ${JSON_RPC_CPP_VERSION_MINOR}) + string (REGEX REPLACE "^#define JSONRPC_CPP_PATCH_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_PATCH ${JSON_RPC_CPP_VERSION_PATCH}) + set (JSON_RPC_CPP_VERSION ${JSON_RPC_CPP_VERSION_MAJOR}.${JSON_RPC_CPP_VERSION_MINOR}.${JSON_RPC_CPP_VERSION_PATCH}) + endif() +endif() + # handle the QUIETLY and REQUIRED arguments and set JSON_RPC_CPP_FOUND to TRUE # if all listed variables are TRUE, hide their existence from configuration view include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(json_rpc_cpp DEFAULT_MSG - JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY JSON_RPC_CPP_INCLUDE_DIR) -mark_as_advanced (JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY JSON_RPC_CPP_INCLUDE_DIR) + +find_package_handle_standard_args( + json_rpc_cpp + REQUIRED_VARS JSON_RPC_CPP_INCLUDE_DIR JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY + VERSION_VAR JSON_RPC_CPP_VERSION +) + +mark_as_advanced (JSON_RPC_CPP_INCLUDE_DIR JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY) From 2505b8ed9007a9c4c1423d3ad460d7ed8f81b8ef Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 16 Mar 2015 16:15:13 +0100 Subject: [PATCH 002/221] Provide access to storage offsets via contract type. --- libsolidity/Compiler.cpp | 17 +++-------------- libsolidity/Types.cpp | 26 +++++++++++++++++++++++--- libsolidity/Types.h | 4 ++++ 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index db61cc4a1..8e2634499 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -20,12 +20,12 @@ * Solidity compiler. */ +#include #include #include #include #include #include -#include #include #include @@ -274,19 +274,8 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) void Compiler::registerStateVariables(ContractDefinition const& _contract) { - vector variables; - for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts())) - for (ASTPointer const& variable: contract->getStateVariables()) - if (!variable->isConstant()) - variables.push_back(variable.get()); - TypePointers types; - for (auto variable: variables) - types.push_back(variable->getType()); - StorageOffsets offsets; - offsets.computeOffsets(types); - for (size_t index = 0; index < variables.size(); ++index) - if (auto const* offset = offsets.getOffset(index)) - m_context.addStateVariable(*variables[index], offset->first, offset->second); + for (auto const& var: ContractType(_contract).getStateVariables()) + m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); } void Compiler::initializeStateVariables(ContractDefinition const& _contract) diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 04f86b922..ed3cb8fd1 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -20,14 +20,14 @@ * Solidity data types */ +#include +#include +#include #include #include #include -#include #include -#include - using namespace std; namespace dev @@ -812,6 +812,26 @@ u256 ContractType::getFunctionIdentifier(string const& _functionName) const return Invalid256; } +vector> ContractType::getStateVariables() const +{ + vector variables; + for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.getLinearizedBaseContracts())) + for (ASTPointer const& variable: contract->getStateVariables()) + if (!variable->isConstant()) + variables.push_back(variable.get()); + TypePointers types; + for (auto variable: variables) + types.push_back(variable->getType()); + StorageOffsets offsets; + offsets.computeOffsets(types); + + vector> variablesAndOffsets; + for (size_t index = 0; index < variables.size(); ++index) + if (auto const* offset = offsets.getOffset(index)) + variablesAndOffsets.push_back(make_tuple(variables[index], offset->first, offset->second)); + return variablesAndOffsets; +} + TypePointer StructType::unaryOperatorResult(Token::Value _operator) const { return _operator == Token::Delete ? make_shared() : TypePointer(); diff --git a/libsolidity/Types.h b/libsolidity/Types.h index e7601fde8..c7dbcdeb9 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -403,6 +403,10 @@ public: /// not exist. u256 getFunctionIdentifier(std::string const& _functionName) const; + /// @returns a list of all state variables (including inherited) of the contract and their + /// offsets in storage. + std::vector> getStateVariables() const; + private: ContractDefinition const& m_contract; /// If true, it is the "super" type of the current contract, i.e. it contains only inherited From 85505d45c01196e7f1733876dbf3c3f31fb2712a Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 16 Mar 2015 17:52:19 +0100 Subject: [PATCH 003/221] Packing for arrays. --- libsolidity/ArrayUtils.cpp | 206 +++++++++++++++++++++++------ libsolidity/ArrayUtils.h | 6 + libsolidity/ExpressionCompiler.cpp | 71 ++++++---- libsolidity/Types.cpp | 18 ++- libsolidity/Types.h | 15 ++- test/SolidityEndToEndTest.cpp | 119 ++++++++++++++++- test/SolidityTypes.cpp | 11 ++ 7 files changed, 369 insertions(+), 77 deletions(-) diff --git a/libsolidity/ArrayUtils.cpp b/libsolidity/ArrayUtils.cpp index 2596e4afa..a46dea3c8 100644 --- a/libsolidity/ArrayUtils.cpp +++ b/libsolidity/ArrayUtils.cpp @@ -52,15 +52,21 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // TODO unroll loop for small sizes bool sourceIsStorage = _sourceType.getLocation() == ArrayType::Location::Storage; + bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; + bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->getStorageBytes() <= 16; + bool haveByteOffsetTarget = !directCopy && targetBaseType->getStorageBytes() <= 16; + unsigned byteOffsetSize = (haveByteOffsetSource ? 1 : 0) + (haveByteOffsetTarget ? 1 : 0); // stack: source_ref [source_byte_off] [source_length] target_ref target_byte_off // store target_ref - m_context << eth::Instruction::POP; //@todo + // arrays always start at zero byte offset, pop offset + m_context << eth::Instruction::POP; for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i) m_context << eth::swapInstruction(i); // stack: target_ref source_ref [source_byte_off] [source_length] if (sourceIsStorage) - m_context << eth::Instruction::POP; //@todo + // arrays always start at zero byte offset, pop offset + m_context << eth::Instruction::POP; // stack: target_ref source_ref [source_length] // retrieve source length if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized()) @@ -81,7 +87,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; - m_context << u256(0); //@todo + m_context << u256(0); return; } // compute hashes (data positions) @@ -97,8 +103,8 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack: target_ref target_data_end source_length target_data_pos source_ref // skip copying if source length is zero m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); + eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized()) CompilerUtils(m_context).computeHashStatic(); @@ -107,18 +113,24 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons convertLengthToSize(_sourceType); m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + if (haveByteOffsetTarget) + m_context << u256(0); + if (haveByteOffsetSource) + m_context << u256(0); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] eth::AssemblyItem copyLoopStart = m_context.newTag(); m_context << copyLoopStart; // check for loop condition m_context - << eth::Instruction::DUP3 << eth::Instruction::DUP2 + << eth::dupInstruction(3 + byteOffsetSize) << eth::dupInstruction(2 + byteOffsetSize) << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); m_context.appendConditionalJumpTo(copyLoopEnd); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] // copy if (sourceBaseType->getCategory() == Type::Category::Array) { - //@todo + solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); m_context << eth::Instruction::DUP3; if (sourceIsStorage) m_context << u256(0); @@ -129,36 +141,80 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons ); m_context << eth::Instruction::POP << eth::Instruction::POP; } + else if (directCopy) + { + solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); + m_context + << eth::Instruction::DUP3 << eth::Instruction::SLOAD + << eth::Instruction::DUP3 << eth::Instruction::SSTORE; + } else { - m_context << eth::Instruction::DUP3; + // Note that we have to copy each element on its own in case conversion is involved. + // We might copy too much if there is padding at the last element, but this way end + // checking is easier. + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + m_context << eth::dupInstruction(3 + byteOffsetSize); if (_sourceType.getLocation() == ArrayType::Location::Storage) { - m_context << u256(0); + if (haveByteOffsetSource) + m_context << eth::Instruction::DUP2; + else + m_context << u256(0); StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); } else if (sourceBaseType->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false); else solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString()); - solAssert(2 + sourceBaseType->getSizeOnStack() <= 16, "Stack too deep."); - m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()) << u256(0); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] ... + solAssert(2 + byteOffsetSize + sourceBaseType->getSizeOnStack() <= 16, "Stack too deep."); + // fetch target storage reference + m_context << eth::dupInstruction(2 + byteOffsetSize + sourceBaseType->getSizeOnStack()); + if (haveByteOffsetTarget) + m_context << eth::dupInstruction(1 + byteOffsetSize + sourceBaseType->getSizeOnStack()); + else + m_context << u256(0); StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); } + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] // increment source - m_context - << eth::Instruction::SWAP2 - << (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize()) - << eth::Instruction::ADD - << eth::Instruction::SWAP2; + if (haveByteOffsetSource) + incrementByteOffset(sourceBaseType->getStorageBytes(), 1, haveByteOffsetTarget ? 5 : 4); + else + m_context + << eth::swapInstruction(2 + byteOffsetSize) + << (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize()) + << eth::Instruction::ADD + << eth::swapInstruction(2 + byteOffsetSize); // increment target - m_context - << eth::Instruction::SWAP1 - << targetBaseType->getStorageSize() - << eth::Instruction::ADD - << eth::Instruction::SWAP1; + if (haveByteOffsetTarget) + incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2); + else + m_context + << eth::swapInstruction(1 + byteOffsetSize) + << targetBaseType->getStorageSize() + << eth::Instruction::ADD + << eth::swapInstruction(1 + byteOffsetSize); m_context.appendJumpTo(copyLoopStart); m_context << copyLoopEnd; + if (haveByteOffsetTarget) + { + // clear elements that might be left over in the current slot in target + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] + m_context << eth::dupInstruction(byteOffsetSize) << eth::Instruction::ISZERO; + eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump(); + m_context << eth::dupInstruction(2 + byteOffsetSize) << eth::dupInstruction(1 + byteOffsetSize); + StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true); + incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2); + m_context.appendJumpTo(copyLoopEnd); + + m_context << copyCleanupLoopEnd; + m_context << eth::Instruction::POP; // might pop the source, but then target is popped next + } + if (haveByteOffsetSource) + m_context << eth::Instruction::POP; + m_context << copyLoopEndWithoutByteOffset; // zero-out leftovers in target // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end @@ -166,41 +222,61 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack: target_ref target_data_end target_data_pos_updated clearStorageLoop(*targetBaseType); m_context << eth::Instruction::POP; - m_context << u256(0); //@todo + m_context << u256(0); } void ArrayUtils::clearArray(ArrayType const& _type) const { unsigned stackHeightStart = m_context.getStackHeight(); solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); - if (_type.isDynamicallySized()) + if (_type.getBaseType()->getStorageBytes() < 32) { - m_context << eth::Instruction::POP; // remove byte offset - clearDynamicArray(_type); + solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); + solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid storage size for type."); } + if (_type.getBaseType()->isValueType()) + solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid size for value type."); + + m_context << eth::Instruction::POP; // remove byte offset + if (_type.isDynamicallySized()) + clearDynamicArray(_type); else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping) - m_context << eth::Instruction::POP << eth::Instruction::POP; - else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value + m_context << eth::Instruction::POP; + else if (_type.getBaseType()->isValueType() && _type.getStorageSize() <= 5) + { + // unroll loop for small arrays @todo choose a good value + // Note that we loop over storage slots here, not elements. + for (unsigned i = 1; i < _type.getStorageSize(); ++i) + m_context + << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::ADD; + m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } + else if (!_type.getBaseType()->isValueType() && _type.getLength() <= 4) { - solAssert(!_type.isByteArray(), ""); + // unroll loop for small arrays @todo choose a good value + solAssert(_type.getBaseType()->getStorageBytes() >= 32, "Invalid storage size."); for (unsigned i = 1; i < _type.getLength(); ++i) { + m_context << u256(0); StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false); - m_context << eth::Instruction::SWAP1; - m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; - m_context << eth::Instruction::SWAP1; + m_context + << eth::Instruction::POP + << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; } + m_context << u256(0); StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true); } else { - solAssert(!_type.isByteArray(), ""); - m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::DUP1 << _type.getLength(); convertLengthToSize(_type); m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; - clearStorageLoop(*_type.getBaseType()); - m_context << eth::Instruction::POP << eth::Instruction::POP; + if (_type.getBaseType()->getStorageBytes() < 32) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.getBaseType()); + m_context << eth::Instruction::POP; } solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); } @@ -224,7 +300,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD << eth::Instruction::SWAP1; // stack: data_pos_end data_pos - if (_type.isByteArray()) + if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32) clearStorageLoop(IntegerType(256)); else clearStorageLoop(*_type.getBaseType()); @@ -237,6 +313,8 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const { solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.isDynamicallySized(), ""); + if (!_type.isByteArray() && _type.getBaseType()->getStorageBytes() < 32) + solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); unsigned stackHeightStart = m_context.getStackHeight(); eth::AssemblyItem resizeEnd = m_context.newTag(); @@ -266,7 +344,7 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const // stack: ref new_length data_pos new_size delete_end m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; // stack: ref new_length delete_end delete_start - if (_type.isByteArray()) + if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32) clearStorageLoop(IntegerType(256)); else clearStorageLoop(*_type.getBaseType()); @@ -294,7 +372,7 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const eth::AssemblyItem zeroLoopEnd = m_context.newTag(); m_context.appendConditionalJumpTo(zeroLoopEnd); // delete - m_context << u256(0); //@todo + m_context << u256(0); StorageItem(m_context, _type).setToZero(SourceLocation(), false); m_context << eth::Instruction::POP; // increment @@ -313,7 +391,20 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con if (_arrayType.isByteArray()) m_context << u256(31) << eth::Instruction::ADD << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - else if (_arrayType.getBaseType()->getStorageSize() > 1) + else if (_arrayType.getBaseType()->getStorageSize() <= 1) + { + unsigned baseBytes = _arrayType.getBaseType()->getStorageBytes(); + if (baseBytes == 0) + m_context << eth::Instruction::POP << u256(1); + else if (baseBytes <= 16) + { + unsigned itemsPerSlot = 32 / baseBytes; + m_context + << u256(itemsPerSlot - 1) << eth::Instruction::ADD + << u256(itemsPerSlot) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + } + } + else m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; } else @@ -349,3 +440,38 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const } } +void ArrayUtils::incrementByteOffset(unsigned byteSize, unsigned byteOffsetPosition, unsigned storageOffsetPosition) const +{ + solAssert(byteSize < 32, ""); + // We do the following, but avoiding jumps: + // byteOffset += byteSize + // if (byteOffset + byteSize > 32) + // { + // storageOffset++; + // byteOffset = 0; + // } + if (byteOffsetPosition > 1) + m_context << eth::swapInstruction(byteOffsetPosition - 1); + m_context << u256(byteSize) << eth::Instruction::ADD; + if (byteOffsetPosition > 1) + m_context << eth::swapInstruction(byteOffsetPosition - 1); + // compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32 + m_context + << u256(32) << eth::dupInstruction(1 + byteOffsetPosition) << u256(byteSize - 1) + << eth::Instruction::ADD << eth::Instruction::DIV; + // increment storage offset if X == 1 (just add X to it) + // stack: X + m_context + << eth::swapInstruction(storageOffsetPosition) << eth::dupInstruction(storageOffsetPosition + 1) + << eth::Instruction::ADD << eth::swapInstruction(storageOffsetPosition); + // stack: X + // set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X) + m_context << u256(1) << eth::Instruction::SUB; + // stack: 1 - X + if (byteOffsetPosition == 1) + m_context << eth::Instruction::MUL; + else + m_context + << eth::dupInstruction(byteOffsetPosition + 1) << eth::Instruction::MUL + << eth::swapInstruction(byteOffsetPosition) << eth::Instruction::POP; +} diff --git a/libsolidity/ArrayUtils.h b/libsolidity/ArrayUtils.h index c7b05b6d3..6e4ceec1c 100644 --- a/libsolidity/ArrayUtils.h +++ b/libsolidity/ArrayUtils.h @@ -72,6 +72,12 @@ public: void retrieveLength(ArrayType const& _arrayType) const; private: + /// Adds the given number of bytes to a storage byte offset counter and also increments + /// the storage offset if adding this number again would increase the counter over 32. + /// @param byteOffsetPosition the stack offset of the storage byte offset + /// @param storageOffsetPosition the stack offset of the storage slot offset + void incrementByteOffset(unsigned byteSize, unsigned byteOffsetPosition, unsigned storageOffsetPosition) const; + CompilerContext& m_context; }; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 61b17f663..144827d44 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -753,7 +753,6 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) } else if (baseType.getCategory() == Type::Category::Array) { - // stack layout: [storage_byte_offset] [] ArrayType const& arrayType = dynamic_cast(baseType); solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); ArrayType::Location location = arrayType.getLocation(); @@ -762,6 +761,11 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) location == ArrayType::Location::Memory ? eth::Instruction::MLOAD : eth::Instruction::CALLDATALOAD; + // remove storage byte offset + if (location == ArrayType::Location::Storage) + m_context << eth::Instruction::POP; + + // stack layout: [] _indexAccess.getIndexExpression()->accept(*this); // retrieve length if (!arrayType.isDynamicallySized()) @@ -769,11 +773,9 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) else if (location == ArrayType::Location::CallData) // length is stored on the stack m_context << eth::Instruction::SWAP1; - else if (location == ArrayType::Location::Storage) - m_context << eth::Instruction::DUP3 << load; else m_context << eth::Instruction::DUP2 << load; - // stack: [storage_byte_offset] + // stack: // check out-of-bounds access m_context << eth::Instruction::DUP2 << eth::Instruction::LT; eth::AssemblyItem legalAccess = m_context.appendConditionalJump(); @@ -781,23 +783,22 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) m_context << eth::Instruction::STOP; m_context << legalAccess; - // stack: [storage_byte_offset] + // stack: if (arrayType.isByteArray()) - // byte array is packed differently, especially in storage switch (location) { case ArrayType::Location::Storage: // byte array index storage lvalue on stack (goal): // = - m_context << u256(32) << eth::Instruction::SWAP3; + m_context << u256(32) << eth::Instruction::SWAP2; CompilerUtils(m_context).computeHashStatic(); - // stack: 32 storage_byte_offset index data_ref + // stack: 32 index data_ref m_context - << eth::Instruction::DUP4 << eth::Instruction::DUP3 + << eth::Instruction::DUP3 << eth::Instruction::DUP3 << eth::Instruction::DIV << eth::Instruction::ADD - // stack: 32 storage_byte_offset index (data_ref + index / 32) - << eth::Instruction::SWAP3 << eth::Instruction::SWAP2 - << eth::Instruction::POP << eth::Instruction::MOD; + // stack: 32 index (data_ref + index / 32) + << eth::Instruction::SWAP2 << eth::Instruction::SWAP1 + << eth::Instruction::MOD; setLValue(_indexAccess); break; case ArrayType::Location::CallData: @@ -811,36 +812,50 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) } else { - // stack: [storage_byte_offset] - if (location == ArrayType::Location::Storage) - //@todo use byte offset, remove it for now - m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; - u256 elementSize = - location == ArrayType::Location::Storage ? - arrayType.getBaseType()->getStorageSize() : - arrayType.getBaseType()->getCalldataEncodedSize(); - solAssert(elementSize != 0, "Invalid element size."); - if (elementSize > 1) - m_context << elementSize << eth::Instruction::MUL; + // stack: + m_context << eth::Instruction::SWAP1; if (arrayType.isDynamicallySized()) { if (location == ArrayType::Location::Storage) - { - m_context << eth::Instruction::SWAP1; CompilerUtils(m_context).computeHashStatic(); - } else if (location == ArrayType::Location::Memory) m_context << u256(32) << eth::Instruction::ADD; } - m_context << eth::Instruction::ADD; + // stack: switch (location) { case ArrayType::Location::CallData: + m_context + << eth::Instruction::SWAP1 << arrayType.getBaseType()->getCalldataEncodedSize() + << eth::Instruction::MUL << eth::Instruction::ADD; if (arrayType.getBaseType()->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic(*arrayType.getBaseType(), true, true, false); break; case ArrayType::Location::Storage: - m_context << u256(0); // @todo + m_context << eth::Instruction::SWAP1; + if (arrayType.getBaseType()->getStorageBytes() <= 16) + { + // stack: + // goal: + // = <(index % itemsPerSlot) * byteSize> + unsigned byteSize = arrayType.getBaseType()->getStorageBytes(); + unsigned itemsPerSlot = 32 / byteSize; + m_context << u256(itemsPerSlot) << eth::Instruction::SWAP2; + // stack: itemsPerSlot index data_ref + m_context + << eth::Instruction::DUP3 << eth::Instruction::DUP3 + << eth::Instruction::DIV << eth::Instruction::ADD + // stack: itemsPerSlot index (data_ref + index / itemsPerSlot) + << eth::Instruction::SWAP2 << eth::Instruction::SWAP1 + << eth::Instruction::MOD + << u256(byteSize) << eth::Instruction::MUL; + } + else + { + if (arrayType.getBaseType()->getStorageSize() != 1) + m_context << arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; + m_context << eth::Instruction::ADD << u256(0); + } setLValueToStorageItem(_indexAccess); break; case ArrayType::Location::Memory: diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index ed3cb8fd1..14ea4c73c 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -706,13 +706,21 @@ u256 ArrayType::getStorageSize() const { if (isDynamicallySized()) return 1; - else + + bigint size; + unsigned baseBytes = getBaseType()->getStorageBytes(); + if (baseBytes == 0) + size = 1; + else if (baseBytes < 32) { - bigint size = bigint(getLength()) * getBaseType()->getStorageSize(); - if (size >= bigint(1) << 256) - BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage.")); - return max(1, u256(size)); + unsigned itemsPerSlot = 32 / baseBytes; + size = (bigint(getLength()) + (itemsPerSlot - 1)) / itemsPerSlot; } + else + size = bigint(getLength()) * getBaseType()->getStorageSize(); + if (size >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage.")); + return max(1, u256(size)); } unsigned ArrayType::getSizeOnStack() const diff --git a/libsolidity/Types.h b/libsolidity/Types.h index c7dbcdeb9..c90aabda1 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -335,13 +335,22 @@ public: /// Constructor for a byte array ("bytes") explicit ArrayType(Location _location): - m_location(_location), m_isByteArray(true), m_baseType(std::make_shared(8)) {} + m_location(_location), + m_isByteArray(true), + m_baseType(std::make_shared(8)) + {} /// Constructor for a dynamically sized array type ("type[]") ArrayType(Location _location, const TypePointer &_baseType): - m_location(_location), m_baseType(_baseType) {} + m_location(_location), + m_baseType(_baseType) + {} /// Constructor for a fixed-size array type ("type[20]") ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length): - m_location(_location), m_baseType(_baseType), m_hasDynamicLength(false), m_length(_length) {} + m_location(_location), + m_baseType(_baseType), + m_hasDynamicLength(false), + m_length(_length) + {} virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index ce7a472bc..48ad183ae 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -3040,6 +3040,123 @@ BOOST_AUTO_TEST_CASE(array_copy_storage_storage_static_dynamic) BOOST_CHECK(callContractFunction("test()") == encodeArgs(9, 4)); } +BOOST_AUTO_TEST_CASE(array_copy_different_packing) +{ + char const* sourceCode = R"( + contract c { + bytes8[] data1; // 4 per slot + bytes10[] data2; // 3 per slot + function test() returns (bytes10 a, bytes10 b, bytes10 c, bytes10 d, bytes10 e) { + data1.length = 9; + for (uint i = 0; i < data1.length; ++i) + data1[i] = bytes8(i); + data2 = data1; + a = data2[1]; + b = data2[2]; + c = data2[3]; + d = data2[4]; + e = data2[5]; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs( + asString(fromHex("0000000000000001")), + asString(fromHex("0000000000000002")), + asString(fromHex("0000000000000003")), + asString(fromHex("0000000000000004")), + asString(fromHex("0000000000000005")) + )); +} + +BOOST_AUTO_TEST_CASE(array_copy_target_simple) +{ + char const* sourceCode = R"( + contract c { + bytes8[9] data1; // 4 per slot + bytes17[10] data2; // 1 per slot, no offset counter + function test() returns (bytes17 a, bytes17 b, bytes17 c, bytes17 d, bytes17 e) { + for (uint i = 0; i < data1.length; ++i) + data1[i] = bytes8(i); + data2[8] = data2[9] = 2; + data2 = data1; + a = data2[1]; + b = data2[2]; + c = data2[3]; + d = data2[4]; + e = data2[9]; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs( + asString(fromHex("0000000000000001")), + asString(fromHex("0000000000000002")), + asString(fromHex("0000000000000003")), + asString(fromHex("0000000000000004")), + asString(fromHex("0000000000000000")) + )); +} + +BOOST_AUTO_TEST_CASE(array_copy_target_leftover) +{ + // test that leftover elements in the last slot of target are correctly cleared during assignment + char const* sourceCode = R"( + contract c { + byte[10] data1; + bytes2[32] data2; + function test() returns (uint check, uint res1, uint res2) { + uint i; + for (i = 0; i < data2.length; ++i) + data2[i] = 0xffff; + check = uint(data2[31]) * 0x10000 | uint(data2[14]); + for (i = 0; i < data1.length; ++i) + data1[i] = byte(uint8(1 + i)); + data2 = data1; + for (i = 0; i < 16; ++i) + res1 |= uint(data2[i]) * 0x10000**i; + for (i = 0; i < 16; ++i) + res2 |= uint(data2[16 + i]) * 0x10000**i; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs( + u256("0xffffffff"), + asString(fromHex("0000000000000000""000000000a000900""0800070006000500""0400030002000100")), + asString(fromHex("0000000000000000""0000000000000000""0000000000000000""0000000000000000")) + )); +} + +BOOST_AUTO_TEST_CASE(array_copy_target_leftover2) +{ + // since the copy always copies whole slots, we have to make sure that the source size maxes + // out a whole slot and at the same time there are still elements left in the target at that point + char const* sourceCode = R"( + contract c { + bytes8[4] data1; // fits into one slot + bytes10[6] data2; // 4 elements need two slots + function test() returns (bytes10 r1, bytes10 r2, bytes10 r3) { + data1[0] = 1; + data1[1] = 2; + data1[2] = 3; + data1[3] = 4; + for (uint i = 0; i < data2.length; ++i) + data2[i] = bytes10(0xffff00 | (1 + i)); + data2 = data1; + r1 = data2[3]; + r2 = data2[4]; + r3 = data2[5]; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs( + asString(fromHex("0000000000000004")), + asString(fromHex("0000000000000000")), + asString(fromHex("0000000000000000")) + )); +} BOOST_AUTO_TEST_CASE(array_copy_storage_storage_struct) { char const* sourceCode = R"( @@ -3166,7 +3283,7 @@ BOOST_AUTO_TEST_CASE(array_copy_nested_array) char const* sourceCode = R"( contract c { uint[4][] a; - uint[5][] b; + uint[10][] b; uint[][] c; function test(uint[2][] d) external returns (uint) { a = d; diff --git a/test/SolidityTypes.cpp b/test/SolidityTypes.cpp index 8defd1d89..6b6306479 100644 --- a/test/SolidityTypes.cpp +++ b/test/SolidityTypes.cpp @@ -75,6 +75,17 @@ BOOST_AUTO_TEST_CASE(storage_layout_mapping) BOOST_CHECK(*members.getMemberStorageOffset("final") == make_pair(u256(3), unsigned(0))); } +BOOST_AUTO_TEST_CASE(storage_layout_arrays) +{ + BOOST_CHECK(ArrayType(ArrayType::Location::Storage, make_shared(1), 32).getStorageSize() == 1); + BOOST_CHECK(ArrayType(ArrayType::Location::Storage, make_shared(1), 33).getStorageSize() == 2); + BOOST_CHECK(ArrayType(ArrayType::Location::Storage, make_shared(2), 31).getStorageSize() == 2); + BOOST_CHECK(ArrayType(ArrayType::Location::Storage, make_shared(7), 8).getStorageSize() == 2); + BOOST_CHECK(ArrayType(ArrayType::Location::Storage, make_shared(7), 9).getStorageSize() == 3); + BOOST_CHECK(ArrayType(ArrayType::Location::Storage, make_shared(31), 9).getStorageSize() == 9); + BOOST_CHECK(ArrayType(ArrayType::Location::Storage, make_shared(32), 9).getStorageSize() == 9); +} + BOOST_AUTO_TEST_SUITE_END() } From 523ce4d178933120e71bb9097e07cc13021459ae Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 17 Mar 2015 18:34:57 +0100 Subject: [PATCH 004/221] Disallowed special case of bytes0 arrays. --- libsolidity/Types.cpp | 2 ++ test/SolidityEndToEndTest.cpp | 24 ++++++++++++++++++++++++ test/SolidityNameAndTypeResolution.cpp | 10 ++++++++++ 3 files changed, 36 insertions(+) diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 14ea4c73c..a718ee4f9 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -184,6 +184,8 @@ TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length TypePointer baseType = _baseTypeName.toType(); if (!baseType) BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Invalid type name.")); + if (baseType->getStorageBytes() == 0) + BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Illegal base type of storage size zero for array.")); if (_length) { if (!_length->getType()) diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index 48ad183ae..cbcff405b 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -3549,6 +3549,30 @@ BOOST_AUTO_TEST_CASE(packed_storage_structs_delete) BOOST_CHECK(m_state.storage(m_contractAddress).empty()); } +BOOST_AUTO_TEST_CASE(packed_storage_structs_with_bytes0) +{ + char const* sourceCode = R"( + contract C { + struct str { uint8 a; bytes0 b; uint8 c; } + uint8 a; + bytes0 x; + uint8 b; + str data; + function test() returns (bool) { + a = 2; + b = 3; + data.a = 4; + data.c = 5; + delete x; + delete data.b; + return a == 2 && b == 3 && data.a == 4 && data.c == 5; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityNameAndTypeResolution.cpp b/test/SolidityNameAndTypeResolution.cpp index b83709d83..591cf053a 100644 --- a/test/SolidityNameAndTypeResolution.cpp +++ b/test/SolidityNameAndTypeResolution.cpp @@ -1446,6 +1446,16 @@ BOOST_AUTO_TEST_CASE(local_const_variable) BOOST_CHECK_THROW(parseTextAndResolveNames(text), ParserError); } +BOOST_AUTO_TEST_CASE(bytes0_array) +{ + char const* text = R"( + contract Foo { + bytes0[] illegalArray; + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + BOOST_AUTO_TEST_SUITE_END() } From 7eb9aa3466c13c9c7274cbfa2cba19917ba2639f Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Tue, 17 Mar 2015 17:35:18 +0100 Subject: [PATCH 005/221] Explicit conversion between bytes and int of any size - Allowing aforementioned conversion and adding tests for it --- libsolidity/Types.cpp | 10 ++-------- test/SolidityEndToEndTest.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 04f86b922..4b5dddacd 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -536,14 +536,8 @@ bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (_convertTo.getCategory() == Category::Integer) - { - IntegerType const& convertTo = dynamic_cast(_convertTo); - if (m_bytes * 8 <= convertTo.getNumBits()) - return true; - } - - return _convertTo.getCategory() == Category::Contract || + return _convertTo.getCategory() == Category::Integer || + _convertTo.getCategory() == Category::Contract || _convertTo.getCategory() == getCategory(); } diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index ce7a472bc..6ad84341a 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -1192,6 +1192,18 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_different_size) encodeArgs(u160("0x6161626361626361626361616263616263616263"))); } +BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_greater_size) +{ + char const* sourceCode = R"( + contract Test { + function bytesToUint(bytes4 s) returns (uint64 h) { + return uint64(s); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("bytesToUint(bytes4)", string("abcd")) == + encodeArgs(u256("0x61626364"))); +} BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_different_min_size) { @@ -1220,6 +1232,19 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_different_min_size) encodeArgs(string("a"))); } +BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_greater_size) +{ + char const* sourceCode = R"( + contract Test { + function UintToBytes(uint16 h) returns (bytes8 s) { + return bytes8(h); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("UintToBytes(uint16)", u256("0x6162")) == + encodeArgs(string("\0\0\0\0\0\0ab", 8))); +} + BOOST_AUTO_TEST_CASE(send_ether) { char const* sourceCode = "contract test {\n" From 87dcb3358292c26765c1654b13d3d572a84f5cc1 Mon Sep 17 00:00:00 2001 From: arkpar Date: Wed, 18 Mar 2015 18:02:08 +0100 Subject: [PATCH 006/221] separate resource file for qml (faster build) common splitter style --- mix/CMakeLists.txt | 2 +- mix/qml.qrc | 65 +++++++++++++++++++ mix/qml/Debugger.qml | 8 +-- mix/qml/MainContent.qml | 14 +---- mix/qml/Splitter.qml | 14 +++++ mix/qml/TransactionLog.qml | 4 ++ mix/qml/WebPreview.qml | 2 +- mix/res.qrc | 124 ++++++++++--------------------------- 8 files changed, 121 insertions(+), 112 deletions(-) create mode 100644 mix/qml.qrc create mode 100644 mix/qml/Splitter.qml diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index 0ef9c6274..d57dafcac 100644 --- a/mix/CMakeLists.txt +++ b/mix/CMakeLists.txt @@ -16,7 +16,7 @@ include_directories(${Boost_INCLUDE_DIRS}) include_directories(BEFORE ..) find_package (Qt5WebEngine QUIET) -qt5_add_resources(UI_RESOURCES res.qrc) +qt5_add_resources(UI_RESOURCES res.qrc qml.qrc) file(GLOB HEADERS "*.h") diff --git a/mix/qml.qrc b/mix/qml.qrc new file mode 100644 index 000000000..bed954741 --- /dev/null +++ b/mix/qml.qrc @@ -0,0 +1,65 @@ + + + qml/AlertMessageDialog.qml + qml/BasicMessage.qml + qml/BigIntValue.qml + qml/CallStack.qml + qml/CodeEditorStyle.qml + qml/CodeEditorView.qml + qml/CommonSeparator.qml + qml/ContractLibrary.qml + qml/DebugBasicInfo.qml + qml/DebugInfoList.qml + qml/Debugger.qml + qml/DebuggerPaneStyle.qml + qml/DefaultLabel.qml + qml/DefaultTextField.qml + qml/DeploymentDialog.qml + qml/Ether.qml + qml/EtherValue.qml + qml/FilesSection.qml + qml/ItemDelegateDataDump.qml + qml/LogsPane.qml + qml/LogsPaneStyle.qml + qml/MainContent.qml + qml/ModalDialog.qml + qml/NewProjectDialog.qml + qml/ProjectFilesStyle.qml + qml/ProjectList.qml + qml/ProjectModel.qml + qml/QBoolTypeView.qml + qml/QHashTypeView.qml + qml/QIntTypeView.qml + qml/QRealTypeView.qml + qml/QStringTypeView.qml + qml/QVariableDeclaration.qml + qml/QVariableDefinition.qml + qml/SourceSansProBold.qml + qml/SourceSansProLight.qml + qml/SourceSansProRegular.qml + qml/Splitter.qml + qml/StateDialog.qml + qml/StateDialogStyle.qml + qml/StateList.qml + qml/StateListModel.qml + qml/StateStyle.qml + qml/StatusPane.qml + qml/StatusPaneStyle.qml + qml/StepActionImage.qml + qml/StorageView.qml + qml/StructView.qml + qml/Style.qml + qml/TabStyle.qml + qml/TransactionDialog.qml + qml/TransactionLog.qml + qml/VariablesView.qml + qml/WebPreviewStyle.qml + qml/js/Debugger.js + qml/js/ErrorLocationFormater.js + qml/js/ProjectModel.js + qml/js/QEtherHelper.js + qml/js/TransactionHelper.js + qml/main.qml + qml/qmldir + + diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index c4b6502b0..b3b67aa5c 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -158,19 +158,15 @@ Rectangle { } } - SplitView { + Splitter { id: debugScrollArea anchors.fill: parent orientation: Qt.Vertical - handleDelegate: Rectangle { - height: machineStates.sideMargin - color: "transparent" - } TransactionLog { id: transactionLog Layout.fillWidth: true - Layout.minimumHeight: 120 + Layout.minimumHeight: 130 height: 250 anchors.top: parent.top anchors.left: parent.left diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index c9495b81f..9be44ccae 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -158,14 +158,9 @@ Rectangle { property alias rightViewWidth: rightView.width } - SplitView + Splitter { anchors.fill: parent - handleDelegate: Rectangle { - width: 1 - height: 1 - color: "#8c8c8c" - } orientation: Qt.Horizontal ProjectList { @@ -183,12 +178,7 @@ Rectangle { Layout.fillHeight: true Layout.fillWidth: true - SplitView { - handleDelegate: Rectangle { - width: 1 - height: 1 - color: "#8c8c8c" - } + Splitter { id: codeWebSplitter anchors.fill: parent orientation: Qt.Vertical diff --git a/mix/qml/Splitter.qml b/mix/qml/Splitter.qml new file mode 100644 index 000000000..96e823795 --- /dev/null +++ b/mix/qml/Splitter.qml @@ -0,0 +1,14 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.3 + +SplitView +{ + handleDelegate: Rectangle { + width: 4 + height: 4 + color: "#cccccc" + } +} + + + diff --git a/mix/qml/TransactionLog.qml b/mix/qml/TransactionLog.qml index 315028c74..887a14c07 100644 --- a/mix/qml/TransactionLog.qml +++ b/mix/qml/TransactionLog.qml @@ -167,6 +167,10 @@ Item { } } } + Rectangle { + height: 6 + color: "transparent" + } } Connections { diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index bff4be66f..d72d0a886 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -292,7 +292,7 @@ Item { color: WebPreviewStyle.general.separatorColor } - SplitView + Splitter { Layout.preferredWidth: parent.width Layout.fillHeight: true diff --git a/mix/res.qrc b/mix/res.qrc index fa50d6dd8..e509bdcc2 100644 --- a/mix/res.qrc +++ b/mix/res.qrc @@ -1,65 +1,5 @@ - qml/main.qml - qml/AlertMessageDialog.qml - qml/BasicMessage.qml - qml/Debugger.qml - qml/MainContent.qml - qml/ModalDialog.qml - qml/ProjectList.qml - qml/StateDialog.qml - qml/StateList.qml - qml/StateListModel.qml - qml/img/dappProjectIcon.png - qml/img/jumpintoback.png - qml/img/jumpintoforward.png - qml/img/jumpoutback.png - qml/img/jumpoutforward.png - qml/img/jumpoverback.png - qml/img/jumpoverforward.png - qml/img/jumpoverforwarddisabled.png - qml/StepActionImage.qml - qml/img/jumpintobackdisabled.png - qml/img/jumpintoforwarddisabled.png - qml/img/jumpoutbackdisabled.png - qml/img/jumpoutforwarddisabled.png - qml/img/jumpoverbackdisabled.png - qml/DebugBasicInfo.qml - qml/js/ErrorLocationFormater.js - qml/img/closedtriangleindicator.png - qml/img/opentriangleindicator.png - qml/img/bugiconactive.png - qml/img/bugiconinactive.png - qml/DebugInfoList.qml - qml/ItemDelegateDataDump.qml - qml/StatusPane.qml - qml/TabStyle.qml - qml/TransactionDialog.qml - qml/js/Debugger.js - qml/NewProjectDialog.qml - qml/ProjectModel.qml - qml/CodeEditorView.qml - qml/js/ProjectModel.js - qml/Ether.qml - qml/EtherValue.qml - qml/BigIntValue.qml - qml/QVariableDefinition.qml - qml/js/QEtherHelper.js - qml/js/TransactionHelper.js - qml/QBoolTypeView.qml - qml/QIntTypeView.qml - qml/QRealTypeView.qml - qml/QStringTypeView.qml - qml/QHashTypeView.qml - qml/ContractLibrary.qml - stdc/config.sol - stdc/namereg.sol - stdc/std.sol - qml/TransactionLog.qml - res/mix_256x256x32.png - qml/QVariableDeclaration.qml - qml/qmldir - qml/FilesSection.qml qml/fonts/SourceSansPro-Black.ttf qml/fonts/SourceSansPro-BlackIt.ttf qml/fonts/SourceSansPro-Bold.ttf @@ -75,43 +15,43 @@ qml/fonts/SourceSerifPro-Bold.ttf qml/fonts/SourceSerifPro-Regular.ttf qml/fonts/SourceSerifPro-Semibold.ttf + qml/img/available_updates.png + qml/img/b64.png + qml/img/broom.png + qml/img/bugiconactive.png + qml/img/bugiconinactive.png + qml/img/closedtriangleindicator.png qml/img/closedtriangleindicator_filesproject.png - qml/img/opentriangleindicator_filesproject.png - qml/img/projecticon.png - qml/SourceSansProRegular.qml - qml/SourceSansProBold.qml - qml/SourceSansProLight.qml - qml/StateDialogStyle.qml - qml/ProjectFilesStyle.qml - qml/DebuggerPaneStyle.qml - qml/CodeEditorStyle.qml - qml/StatusPaneStyle.qml - qml/StateStyle.qml - qml/img/plus.png + qml/img/console.png + qml/img/copy.png + qml/img/dappProjectIcon.png qml/img/delete_sign.png qml/img/edit.png - qml/DefaultLabel.qml - qml/DefaultTextField.qml - qml/CommonSeparator.qml - qml/Style.qml - qml/WebPreviewStyle.qml - qml/img/available_updates.png - qml/DeploymentDialog.qml - qml/img/search_filled.png - qml/StorageView.qml - qml/CallStack.qml + qml/img/exit.png qml/img/help.png + qml/img/jumpintoback.png + qml/img/jumpintobackdisabled.png + qml/img/jumpintoforward.png + qml/img/jumpintoforwarddisabled.png + qml/img/jumpoutback.png + qml/img/jumpoutbackdisabled.png + qml/img/jumpoutforward.png + qml/img/jumpoutforwarddisabled.png + qml/img/jumpoverback.png + qml/img/jumpoverbackdisabled.png + qml/img/jumpoverforward.png + qml/img/jumpoverforwarddisabled.png + qml/img/note.png qml/img/openedfolder.png - qml/img/b64.png - qml/img/exit.png + qml/img/opentriangleindicator.png + qml/img/opentriangleindicator_filesproject.png + qml/img/plus.png + qml/img/projecticon.png qml/img/run.png - qml/img/note.png - qml/LogsPane.qml - qml/img/copy.png - qml/img/broom.png - qml/LogsPaneStyle.qml - qml/StructView.qml - qml/img/console.png - qml/VariablesView.qml + qml/img/search_filled.png + res/mix_256x256x32.png + stdc/config.sol + stdc/namereg.sol + stdc/std.sol From e4e8a316a0d6e38c6e20807f46c5b160afb1bb62 Mon Sep 17 00:00:00 2001 From: arkpar Date: Wed, 18 Mar 2015 18:04:15 +0100 Subject: [PATCH 007/221] saving on close fixed --- mix/qml/CodeEditorView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index a24fd094f..4d8559200 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -118,7 +118,7 @@ Item { var doc = editorListModel.get(i); var editor = editors.itemAt(i).item; if (editor) - fileIo.writeFile(doc.path, item.getText()); + fileIo.writeFile(doc.path, editor.getText()); } } From b3d43bd5111a3190e95dc865782cf51b881ec440 Mon Sep 17 00:00:00 2001 From: arkpar Date: Wed, 18 Mar 2015 20:15:41 +0100 Subject: [PATCH 008/221] refactored code to use native qml object hierarchy instead of forcing object properties from c++ --- mix/AppContext.cpp | 122 -------------------------- mix/AppContext.h | 88 ------------------- mix/ClientModel.cpp | 26 ++---- mix/ClientModel.h | 10 +-- mix/{StatusPane.cpp => Clipboard.cpp} | 39 +++----- mix/{StatusPane.h => Clipboard.h} | 29 +++--- mix/CodeEditorExtensionManager.cpp | 88 ------------------- mix/CodeEditorExtensionManager.h | 71 --------------- mix/CodeModel.cpp | 3 +- mix/CodeModel.h | 2 +- mix/Extension.cpp | 82 ----------------- mix/Extension.h | 75 ---------------- mix/MixApplication.cpp | 42 ++++++--- mix/MixApplication.h | 4 - mix/QContractDefinition.cpp | 3 +- mix/qml/ContractLibrary.qml | 4 +- mix/qml/DebugInfoList.qml | 2 +- mix/qml/DeploymentDialog.qml | 2 +- mix/qml/LogsPane.qml | 4 +- mix/qml/MainContent.qml | 4 - mix/qml/ProjectModel.qml | 5 +- mix/qml/TransactionLog.qml | 2 +- mix/qml/WebCodeEditor.qml | 4 +- mix/qml/WebPreview.qml | 11 +-- mix/qml/main.qml | 37 +++++++- 25 files changed, 125 insertions(+), 634 deletions(-) delete mode 100644 mix/AppContext.cpp delete mode 100644 mix/AppContext.h rename mix/{StatusPane.cpp => Clipboard.cpp} (51%) rename mix/{StatusPane.h => Clipboard.h} (67%) delete mode 100644 mix/CodeEditorExtensionManager.cpp delete mode 100644 mix/CodeEditorExtensionManager.h delete mode 100644 mix/Extension.cpp delete mode 100644 mix/Extension.h diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp deleted file mode 100644 index 9e8f15b68..000000000 --- a/mix/AppContext.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - 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 . -*/ -/** @file AppContext.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * Provides access to the current QQmlApplicationEngine which is used to add QML file on the fly. - * In the future this class can be extended to add more variable related to the context of the application. - * For now AppContext provides reference to: - * - QQmlApplicationEngine - * - dev::WebThreeDirect (and dev::eth::Client) - * - KeyEventManager - */ - -#include -#include -#include -#include -#include -#include -#include "CodeModel.h" -#include "FileIo.h" -#include "ClientModel.h" -#include "CodeEditorExtensionManager.h" -#include "Exceptions.h" -#include "QEther.h" -#include "QVariableDefinition.h" -#include "HttpServer.h" -#include "AppContext.h" -#include "SortFilterProxyModel.h" - -using namespace dev; -using namespace dev::eth; -using namespace dev::mix; - -const QString c_projectFileName = "project.mix"; - -AppContext::AppContext(QQmlApplicationEngine* _engine) -{ - m_applicationEngine = _engine; - m_codeModel.reset(new CodeModel(this)); - m_clientModel.reset(new ClientModel(this)); - m_fileIo.reset(new FileIo()); - connect(QApplication::clipboard(), &QClipboard::dataChanged, [this] { emit clipboardChanged();}); -} - -AppContext::~AppContext() -{ -} - -void AppContext::load() -{ - m_applicationEngine->rootContext()->setContextProperty("appContext", this); - QFont f; - m_applicationEngine->rootContext()->setContextProperty("systemPointSize", f.pointSize()); - qmlRegisterType("org.ethereum.qml", 1, 0, "FileIo"); - m_applicationEngine->rootContext()->setContextProperty("codeModel", m_codeModel.get()); - m_applicationEngine->rootContext()->setContextProperty("fileIo", m_fileIo.get()); - qmlRegisterType("org.ethereum.qml.QEther", 1, 0, "QEther"); - qmlRegisterType("org.ethereum.qml.QBigInt", 1, 0, "QBigInt"); - qmlRegisterType("org.ethereum.qml.QVariableDeclaration", 1, 0, "QVariableDeclaration"); - qmlRegisterType("org.ethereum.qml.RecordLogEntry", 1, 0, "RecordLogEntry"); - qmlRegisterType("org.ethereum.qml.SortFilterProxyModel", 1, 0, "SortFilterProxyModel"); - qmlRegisterType("org.ethereum.qml.QSolidityType", 1, 0, "QSolidityType"); - QQmlComponent projectModelComponent(m_applicationEngine, QUrl("qrc:/qml/ProjectModel.qml")); - QObject* projectModel = projectModelComponent.create(); - if (projectModelComponent.isError()) - { - QmlLoadException exception; - for (auto const& e : projectModelComponent.errors()) - exception << QmlErrorInfo(e); - BOOST_THROW_EXCEPTION(exception); - } - m_applicationEngine->rootContext()->setContextProperty("projectModel", projectModel); - qmlRegisterType("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager"); - qmlRegisterType("HttpServer", 1, 0, "HttpServer"); - - m_applicationEngine->load(QUrl("qrc:/qml/main.qml")); - QWindow *window = qobject_cast(m_applicationEngine->rootObjects().at(0)); - window->setIcon(QIcon(":/res/mix_256x256x32.png")); - appLoaded(); -} - -QQmlApplicationEngine* AppContext::appEngine() -{ - return m_applicationEngine; -} - -void AppContext::displayMessageDialog(QString _title, QString _message) -{ - // TODO : move to a UI dedicated layer. - QObject* dialogWin = m_applicationEngine->rootObjects().at(0)->findChild("alertMessageDialog", Qt::FindChildrenRecursively); - QObject* dialogWinComponent = m_applicationEngine->rootObjects().at(0)->findChild("alertMessageDialogContent", Qt::FindChildrenRecursively); - dialogWinComponent->setProperty("source", QString("qrc:/qml/BasicMessage.qml")); - dialogWin->setProperty("title", _title); - dialogWin->setProperty("width", "250"); - dialogWin->setProperty("height", "100"); - dialogWin->findChild("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message); - QMetaObject::invokeMethod(dialogWin, "open"); -} - -QString AppContext::clipboard() const -{ - QClipboard *clipboard = QApplication::clipboard(); - return clipboard->text(); -} - -void AppContext::toClipboard(QString _text) -{ - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(_text); -} diff --git a/mix/AppContext.h b/mix/AppContext.h deleted file mode 100644 index 268771207..000000000 --- a/mix/AppContext.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - 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 . -*/ -/** @file AppContext.h - * @author Yann yann@ethdev.com - * @date 2014 - * Provides access to the current QQmlApplicationEngine which is used to add QML file on the fly. - * In the future this class can be extended to add more variable related to the context of the application. - * For now AppContext provides reference to: - * - QQmlApplicationEngine - * - dev::WebThreeDirect (and dev::eth::Client) - * - KeyEventManager - */ - -#pragma once - -#include -#include -#include - -class QQmlApplicationEngine; - -namespace dev -{ -namespace mix -{ - -class CodeModel; -class ClientModel; -class FileIo; -/** - * @brief Provides access to application scope variable. - */ - -class AppContext: public QObject -{ - Q_OBJECT - Q_PROPERTY(QString clipboard READ clipboard WRITE toClipboard NOTIFY clipboardChanged) - -public: - AppContext(QQmlApplicationEngine* _engine); - virtual ~AppContext(); - /// Load the UI from qml files - void load(); - /// Get the current QQMLApplicationEngine instance. - QQmlApplicationEngine* appEngine(); - /// Get code model - CodeModel* codeModel() { return m_codeModel.get(); } - /// Get client model - ClientModel* clientModel() { return m_clientModel.get(); } - /// Display an alert message. - void displayMessageDialog(QString _title, QString _message); - /// Copy text to clipboard - Q_INVOKABLE void toClipboard(QString _text); - /// Get text from clipboard - QString clipboard() const; - -signals: - /// Triggered once components have been loaded - void appLoaded(); - void clipboardChanged(); - -private: - QQmlApplicationEngine* m_applicationEngine; //owned by app - std::unique_ptr m_codeModel; - std::unique_ptr m_clientModel; - std::unique_ptr m_fileIo; - -public slots: - /// Delete the current instance when application quit. - void quitApplication() {} -}; - -} -} diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 7fdba2720..be269ad5b 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -21,6 +21,7 @@ // Make sure boost/asio.hpp is included before windows.h. #include +#include "ClientModel.h" #include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include "AppContext.h" #include "DebuggingStateWrapper.h" #include "Exceptions.h" #include "QContractDefinition.h" @@ -39,7 +39,6 @@ #include "CodeModel.h" #include "QEther.h" #include "Web3Server.h" -#include "ClientModel.h" #include "MixClient.h" using namespace dev; @@ -67,8 +66,8 @@ private: }; -ClientModel::ClientModel(AppContext* _context): - m_context(_context), m_running(false), m_rpcConnector(new RpcConnector()) +ClientModel::ClientModel(): + m_running(false), m_rpcConnector(new RpcConnector()) { qRegisterMetaType("QBigInt*"); qRegisterMetaType("QVariableDefinition*"); @@ -87,7 +86,6 @@ ClientModel::ClientModel(AppContext* _context): m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), m_client->userAccounts(), m_client.get())); connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection); - _context->appEngine()->rootContext()->setContextProperty("clientModel", this); } ClientModel::~ClientModel() @@ -184,8 +182,8 @@ void ClientModel::setupState(QVariantMap _state) } else { - if (contractId.isEmpty() && m_context->codeModel()->hasContract()) //TODO: This is to support old project files, remove later - contractId = m_context->codeModel()->contracts().keys()[0]; + if (contractId.isEmpty() && m_codeModel->hasContract()) //TODO: This is to support old project files, remove later + contractId = m_codeModel->contracts().keys()[0]; TransactionSettings transactionSettings(contractId, functionId, value, gas, gasPrice, Secret(sender.toStdString())); transactionSettings.parameterValues = transaction.value("parameters").toMap(); @@ -220,7 +218,7 @@ void ClientModel::executeSequence(std::vector const& _seque if (!transaction.stdContractUrl.isEmpty()) { //std contract - dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.contractId, transaction.stdContractUrl); + dev::bytes const& stdContractCode = m_codeModel->getStdContractCode(transaction.contractId, transaction.stdContractUrl); TransactionSettings stdTransaction = transaction; stdTransaction.gas = 500000;// TODO: get this from std contracts library Address address = deployContract(stdContractCode, stdTransaction); @@ -230,7 +228,7 @@ void ClientModel::executeSequence(std::vector const& _seque else { //encode data - CompiledContract const& compilerRes = m_context->codeModel()->contract(transaction.contractId); + CompiledContract const& compilerRes = m_codeModel->contract(transaction.contractId); QFunctionDefinition const* f = nullptr; bytes contractCode = compilerRes.bytes(); std::shared_ptr contractDef = compilerRes.sharedContract(); @@ -320,7 +318,7 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) auto nameIter = m_contractNames.find(code.address); if (nameIter != m_contractNames.end()) { - CompiledContract const& compilerRes = m_context->codeModel()->contract(nameIter->second); + CompiledContract const& compilerRes = m_codeModel->contract(nameIter->second); eth::AssemblyItems assemblyItems = !_t.isConstructor() ? compilerRes.assemblyItems() : compilerRes.constructorAssemblyItems(); codes.back()->setDocument(compilerRes.documentId()); codeItems.push_back(std::move(assemblyItems)); @@ -439,12 +437,6 @@ void ClientModel::debugRecord(unsigned _index) showDebuggerForTransaction(e); } -void ClientModel::showDebugError(QString const& _error) -{ - //TODO: change that to a signal - m_context->displayMessageDialog(tr("Debugger"), _error); -} - Address ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction) { Address newAddress = m_client->transact(_ctrTransaction.sender, _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice); @@ -527,7 +519,7 @@ void ClientModel::onNewTransaction() auto contractAddressIter = m_contractNames.find(contractAddress); if (contractAddressIter != m_contractNames.end()) { - CompiledContract const& compilerRes = m_context->codeModel()->contract(contractAddressIter->second); + CompiledContract const& compilerRes = m_codeModel->contract(contractAddressIter->second); const QContractDefinition* def = compilerRes.contract(); contract = def->name(); if (abi) diff --git a/mix/ClientModel.h b/mix/ClientModel.h index 5c3615a2b..a5d89d859 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -34,13 +34,13 @@ namespace dev namespace mix { -class AppContext; class Web3Server; class RpcConnector; class QEther; class QDebugData; class MixClient; class QVariableDefinition; +class CodeModel; struct SolidityType; /// Backend transaction config class @@ -127,7 +127,7 @@ class ClientModel: public QObject Q_OBJECT public: - ClientModel(AppContext* _context); + ClientModel(); ~ClientModel(); /// @returns true if currently executing contract code Q_PROPERTY(bool running MEMBER m_running NOTIFY runStateChanged) @@ -143,6 +143,8 @@ public: Q_INVOKABLE QString apiCall(QString const& _message); /// Simulate mining. Creates a new block Q_INVOKABLE void mine(); + /// Get/set code model. Should be set from qml + Q_PROPERTY(CodeModel* codeModel MEMBER m_codeModel) public slots: /// Setup state, run transaction sequence, show debugger for the last transaction @@ -157,8 +159,6 @@ public slots: private slots: /// Update UI with machine states result. Display a modal dialog. void showDebugger(); - /// Update UI with transaction run error. - void showDebugError(QString const& _error); signals: /// Transaction execution started @@ -201,7 +201,6 @@ private: void showDebuggerForTransaction(ExecutionResult const& _t); QVariant formatValue(SolidityType const& _type, dev::u256 const& _value); - AppContext* m_context; std::atomic m_running; std::atomic m_mining; std::unique_ptr m_client; @@ -211,6 +210,7 @@ private: std::map m_contractNames; std::map m_stdContractAddresses; std::map m_stdContractNames; + CodeModel* m_codeModel = nullptr; }; } diff --git a/mix/StatusPane.cpp b/mix/Clipboard.cpp similarity index 51% rename from mix/StatusPane.cpp rename to mix/Clipboard.cpp index 9022b7033..b364a6b31 100644 --- a/mix/StatusPane.cpp +++ b/mix/Clipboard.cpp @@ -1,55 +1,40 @@ /* 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 . */ -/** @file ConstantCompilationControl.cpp +/** @file Clipboard.cpp * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. + * @date 2015 */ -#include -#include -#include +#include "Clipboard.h" #include -#include -#include -#include -#include "StatusPane.h" -#include "QContractDefinition.h" -#include "AppContext.h" -#include "CodeModel.h" +#include using namespace dev::mix; -StatusPane::StatusPane(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::HeaderView) +Clipboard::Clipboard() { - _context->appEngine()->rootContext()->setContextProperty("statusPane", this); + connect(QApplication::clipboard(), &QClipboard::dataChanged, [this] { emit clipboardChanged();}); } -QString StatusPane::contentUrl() const +QString Clipboard::text() const { - return QStringLiteral("qrc:/qml/StatusPane.qml"); + QClipboard *clipboard = QApplication::clipboard(); + return clipboard->text(); } -QString StatusPane::title() const +void Clipboard::setText(QString _text) { - return QApplication::tr("compiler"); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(_text); } - -void StatusPane::start() const -{ -} - diff --git a/mix/StatusPane.h b/mix/Clipboard.h similarity index 67% rename from mix/StatusPane.h rename to mix/Clipboard.h index 28b5b449b..317698e41 100644 --- a/mix/StatusPane.h +++ b/mix/Clipboard.h @@ -1,25 +1,27 @@ /* 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 . */ -/** @file ConstantCompilationControl.h +/** @file Clipboard.h * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. + * @date 2015 */ #pragma once -#include "Extension.h" +#include namespace dev { @@ -27,20 +29,23 @@ namespace mix { /** - * @brief Extension which display assembly code of the contract being edited. + * @brief Provides access to system clipboard */ -class StatusPane: public Extension + +class Clipboard: public QObject { Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText NOTIFY clipboardChanged) public: - StatusPane(AppContext* _appContext); - ~StatusPane() {} - void start() const override; - QString title() const override; - QString contentUrl() const override; + Clipboard(); + /// Copy text to clipboard + void setText(QString _text); + /// Get text from clipboard + QString text() const; -public slots: +signals: + void clipboardChanged(); }; } diff --git a/mix/CodeEditorExtensionManager.cpp b/mix/CodeEditorExtensionManager.cpp deleted file mode 100644 index 7f1f5d216..000000000 --- a/mix/CodeEditorExtensionManager.cpp +++ /dev/null @@ -1,88 +0,0 @@ -/* - 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 . -*/ -/** @file CodeEditorExtensionManager.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#include -#include -#include -#include -#include -#include "StatusPane.h" -#include "AppContext.h" -#include "MixApplication.h" -#include "CodeModel.h" -#include "ClientModel.h" -#include "CodeHighlighter.h" -#include "CodeEditorExtensionManager.h" - -using namespace dev::mix; - -CodeEditorExtensionManager::CodeEditorExtensionManager(): - m_appContext(static_cast(QApplication::instance())->context()) -{ -} - -CodeEditorExtensionManager::~CodeEditorExtensionManager() -{ - m_features.clear(); -} - -void CodeEditorExtensionManager::loadEditor(QQuickItem* _editor) -{ - if (!_editor) - return; -} - -void CodeEditorExtensionManager::initExtension(std::shared_ptr _ext) -{ - if (!_ext->contentUrl().isEmpty()) - { - try - { - if (_ext->getDisplayBehavior() == ExtensionDisplayBehavior::RightView) - _ext->addTabOn(m_rightView); - if (_ext->getDisplayBehavior() == ExtensionDisplayBehavior::HeaderView) - _ext->addTabOn(m_headerView); - } - catch (...) - { - qDebug() << "Exception when adding tab into view."; - return; - } - } - _ext->start(); - m_features.append(_ext); -} - -void CodeEditorExtensionManager::applyCodeHighlight() -{ - //TODO: reimplement -} - -void CodeEditorExtensionManager::setRightView(QQuickItem* _rightView) -{ - m_rightView = _rightView; -} - -void CodeEditorExtensionManager::setHeaderView(QQuickItem* _headerView) -{ - m_headerView = _headerView; -} diff --git a/mix/CodeEditorExtensionManager.h b/mix/CodeEditorExtensionManager.h deleted file mode 100644 index ebfe2d8a3..000000000 --- a/mix/CodeEditorExtensionManager.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - 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 . -*/ -/** @file CodeEditorExtensionManager.h - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#pragma once - -#include -#include -#include -#include -#include "StatusPane.h" - -namespace dev -{ -namespace mix -{ - - -class AppContext; - -/** - * @brief Init and provides connection between extensions. - */ -class CodeEditorExtensionManager: public QObject -{ - Q_OBJECT - - Q_PROPERTY(QQuickItem* headerView MEMBER m_headerView WRITE setHeaderView) - Q_PROPERTY(QQuickItem* rightView MEMBER m_rightView WRITE setRightView) - -public: - CodeEditorExtensionManager(); - ~CodeEditorExtensionManager(); - /// Initialize extension. - void initExtension(std::shared_ptr); - /// Set current tab view - void setHeaderView(QQuickItem*); - /// Set current right tab view. - void setRightView(QQuickItem*); - -private slots: - void applyCodeHighlight(); - -private: - QVector> m_features; - QQuickItem* m_headerView; - QQuickItem* m_rightView; - AppContext* m_appContext; - void loadEditor(QQuickItem* _editor); -}; - -} -} diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index fb55c4fc7..16f40cb28 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -129,8 +129,7 @@ QString CompiledContract::codeHex() const return QString::fromStdString(toJS(m_bytes)); } -CodeModel::CodeModel(QObject* _parent): - QObject(_parent), +CodeModel::CodeModel(): m_compiling(false), m_codeHighlighterSettings(new CodeHighlighterSettings()), m_backgroundWorker(this), diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 07e93498a..03be459f1 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -125,7 +125,7 @@ class CodeModel: public QObject Q_OBJECT public: - CodeModel(QObject* _parent); + CodeModel(); ~CodeModel(); Q_PROPERTY(QVariantMap contracts READ contracts NOTIFY codeChanged) diff --git a/mix/Extension.cpp b/mix/Extension.cpp deleted file mode 100644 index eae842279..000000000 --- a/mix/Extension.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - 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 . -*/ -/** @file Extension.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - - -#include -#include -#include - -#include -#include -#include "Extension.h" -#include "AppContext.h" - -using namespace dev; -using namespace dev::mix; - -Extension::Extension(AppContext* _context) -{ - init(_context); -} - -Extension::Extension(AppContext* _context, ExtensionDisplayBehavior _displayBehavior) -{ - init(_context); - m_displayBehavior = _displayBehavior; -} - -void Extension::init(AppContext* _context) -{ - m_ctx = _context; - m_appEngine = m_ctx->appEngine(); -} - -void Extension::addTabOn(QObject* _view) -{ - if (contentUrl() == "") - return; - - QVariant returnValue; - QQmlComponent* component = new QQmlComponent( - m_appEngine, - QUrl(contentUrl()), _view); - - QMetaObject::invokeMethod(_view, "addTab", - Q_RETURN_ARG(QVariant, returnValue), - Q_ARG(QVariant, this->title()), - Q_ARG(QVariant, QVariant::fromValue(component))); - - m_view = qvariant_cast(returnValue); -} - -void Extension::addContentOn(QObject* _view) -{ - Q_UNUSED(_view); - if (m_displayBehavior == ExtensionDisplayBehavior::ModalDialog) - { - QQmlComponent* component = new QQmlComponent(m_appEngine, QUrl(contentUrl()), _view); - QObject* dialogWin = m_appEngine->rootObjects().at(0)->findChild("dialog", Qt::FindChildrenRecursively); - QObject* dialogWinComponent = m_appEngine->rootObjects().at(0)->findChild("modalDialogContent", Qt::FindChildrenRecursively); - dialogWinComponent->setProperty("sourceComponent", QVariant::fromValue(component)); - dialogWin->setProperty("title", title()); - QMetaObject::invokeMethod(dialogWin, "open"); - } - //TODO add more view type. -} - diff --git a/mix/Extension.h b/mix/Extension.h deleted file mode 100644 index 98daf2918..000000000 --- a/mix/Extension.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - 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 . -*/ -/** @file Extension.h - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#pragma once - -#include -#include - -class QQmlApplicationEngine; - -namespace dev -{ -namespace mix -{ - -class AppContext; - -enum ExtensionDisplayBehavior -{ - HeaderView, - RightView, - ModalDialog -}; - - -class Extension: public QObject -{ - Q_OBJECT - -public: - Extension(AppContext* _context); - Extension(AppContext* _context, ExtensionDisplayBehavior _displayBehavior); - /// Return the QML url of the view to display. - virtual QString contentUrl() const { return ""; } - /// Return the title of this extension. - virtual QString title() const { return ""; } - /// Initialize extension. - virtual void start() const {} - /// Add the view define in contentUrl() in the _view QObject. - void addContentOn(QObject* _view); - /// Add the view define in contentUrl() in the _view QObject (_view has to be a tab). - void addTabOn(QObject* _view); - /// Modify the display behavior of this extension. - void setDisplayBehavior(ExtensionDisplayBehavior _displayBehavior) { m_displayBehavior = _displayBehavior; } - /// Get the display behavior of thi extension. - ExtensionDisplayBehavior getDisplayBehavior() { return m_displayBehavior; } - -protected: - QObject* m_view; - ExtensionDisplayBehavior m_displayBehavior; - AppContext* m_ctx; - QQmlApplicationEngine* m_appEngine; - -private: - void init(AppContext* _context); -}; - -} -} diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index 821167a3f..028f1cb0b 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -19,22 +19,26 @@ * @date 2014 */ -#include +#include "MixApplication.h" #include - +#include +#include #ifdef ETH_HAVE_WEBENGINE #include #endif - -#include "MixApplication.h" -#include "AppContext.h" - -#include +#include "CodeModel.h" +#include "ClientModel.h" +#include "FileIo.h" +#include "QEther.h" +#include "QVariableDeclaration.h" +#include "SortFilterProxyModel.h" +#include "Clipboard.h" +#include "HttpServer.h" using namespace dev::mix; MixApplication::MixApplication(int& _argc, char* _argv[]): - QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()), m_appContext(new AppContext(m_engine.get())) + QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()) { setOrganizationName(tr("Ethereum")); setOrganizationDomain(tr("ethereum.org")); @@ -43,8 +47,26 @@ MixApplication::MixApplication(int& _argc, char* _argv[]): #ifdef ETH_HAVE_WEBENGINE QtWebEngine::initialize(); #endif - QObject::connect(this, SIGNAL(lastWindowClosed()), context(), SLOT(quitApplication())); //use to kill ApplicationContext and other stuff - m_appContext->load(); + + QFont f; + m_engine->rootContext()->setContextProperty("systemPointSize", f.pointSize()); + qmlRegisterType("org.ethereum.qml.CodeModel", 1, 0, "CodeModel"); + qmlRegisterType("org.ethereum.qml.ClientModel", 1, 0, "ClientModel"); + qmlRegisterType("org.ethereum.qml.FileIo", 1, 0, "FileIo"); + qmlRegisterType("org.ethereum.qml.QEther", 1, 0, "QEther"); + qmlRegisterType("org.ethereum.qml.QBigInt", 1, 0, "QBigInt"); + qmlRegisterType("org.ethereum.qml.QVariableDeclaration", 1, 0, "QVariableDeclaration"); + qmlRegisterType("org.ethereum.qml.RecordLogEntry", 1, 0, "RecordLogEntry"); + qmlRegisterType("org.ethereum.qml.SortFilterProxyModel", 1, 0, "SortFilterProxyModel"); + qmlRegisterType("org.ethereum.qml.QSolidityType", 1, 0, "QSolidityType"); + qmlRegisterType("org.ethereum.qml.Clipboard", 1, 0, "Clipboard"); + qmlRegisterType("HttpServer", 1, 0, "HttpServer"); + qRegisterMetaType("CodeModel*"); + qRegisterMetaType("ClientModel*"); + + m_engine->load(QUrl("qrc:/qml/main.qml")); + QWindow *window = qobject_cast(m_engine->rootObjects().at(0)); + window->setIcon(QIcon(":/res/mix_256x256x32.png")); } MixApplication::~MixApplication() diff --git a/mix/MixApplication.h b/mix/MixApplication.h index acfe4e547..49a6e0047 100644 --- a/mix/MixApplication.h +++ b/mix/MixApplication.h @@ -33,8 +33,6 @@ namespace dev namespace mix { -class AppContext; - class MixApplication: public QApplication { Q_OBJECT @@ -42,12 +40,10 @@ class MixApplication: public QApplication public: MixApplication(int& _argc, char* _argv[]); virtual ~MixApplication(); - AppContext* context() { return m_appContext.get(); } QQmlApplicationEngine* engine() { return m_engine.get(); } private: std::unique_ptr m_engine; - std::unique_ptr m_appContext; }; } diff --git a/mix/QContractDefinition.cpp b/mix/QContractDefinition.cpp index 899e804c5..51b37e399 100644 --- a/mix/QContractDefinition.cpp +++ b/mix/QContractDefinition.cpp @@ -21,14 +21,13 @@ #include +#include "QContractDefinition.h" #include #include #include #include #include #include -#include "AppContext.h" -#include "QContractDefinition.h" using namespace dev::solidity; using namespace dev::mix; diff --git a/mix/qml/ContractLibrary.qml b/mix/qml/ContractLibrary.qml index 557f1cc53..4f3afafc6 100644 --- a/mix/qml/ContractLibrary.qml +++ b/mix/qml/ContractLibrary.qml @@ -5,8 +5,8 @@ Item { property alias model: contractListModel; Connections { - target: appContext - Component.onCompleted: { + target: mainApplication + onLoaded: { //TODO: load a list, dependencies, ets, from external files contractListModel.append({ diff --git a/mix/qml/DebugInfoList.qml b/mix/qml/DebugInfoList.qml index 17b97166e..5b1a67519 100644 --- a/mix/qml/DebugInfoList.qml +++ b/mix/qml/DebugInfoList.qml @@ -136,7 +136,7 @@ ColumnLayout { var str = ""; for (var i = 0; i < listModel.length; i++) str += listModel[i] + "\n"; - appContext.toClipboard(str); + clipboard.text = str; } } diff --git a/mix/qml/DeploymentDialog.qml b/mix/qml/DeploymentDialog.qml index 046dd1689..c194977cf 100644 --- a/mix/qml/DeploymentDialog.qml +++ b/mix/qml/DeploymentDialog.qml @@ -223,7 +223,7 @@ Window { enabled: deploymentDialog.packageBase64 !== "" tooltip: qsTr("Copy Base64 conversion to ClipBoard") onTriggered: { - appContext.toClipboard(deploymentDialog.packageBase64); + clipboard.text = deploymentDialog.packageBase64; } } diff --git a/mix/qml/LogsPane.qml b/mix/qml/LogsPane.qml index bef616f21..b40dfc4c7 100644 --- a/mix/qml/LogsPane.qml +++ b/mix/qml/LogsPane.qml @@ -67,7 +67,7 @@ Rectangle var log = logsModel.get(k); content += log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content + "\n"; } - appContext.toClipboard(content); + clipboard.text = content; } } @@ -207,7 +207,7 @@ Rectangle { var log = logsModel.get(logsTable.currentRow); if (log) - appContext.toClipboard(log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content); + clipboard.text = (log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content); } model: SortFilterProxyModel { diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 9be44ccae..67034f108 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -2,7 +2,6 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.0 import QtQuick.Controls.Styles 1.1 -import CodeEditorExtensionManager 1.0 import Qt.labs.settings 1.0 import org.ethereum.qml.QEther 1.0 import "js/QEtherHelper.js" as QEtherHelper @@ -101,9 +100,6 @@ Rectangle { rightView.displayCompilationErrorIfAny(); } - CodeEditorExtensionManager { - } - Settings { id: mainSettings property alias codeWebOrientation: codeWebSplitter.orientation diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index 7c9fdf4ac..a4e219973 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -69,8 +69,8 @@ Item { function formatAppUrl() { ProjectModelCode.formatAppUrl(url); } Connections { - target: appContext - onAppLoaded: { + target: mainApplication + onLoaded: { if (projectSettings.lastProjectPath && projectSettings.lastProjectPath !== "") projectModel.loadProject(projectSettings.lastProjectPath) } @@ -173,7 +173,6 @@ Item { { target: projectModel onProjectClosed: { - projectSettings.lastProjectPath = ""; projectPath = ""; } } diff --git a/mix/qml/TransactionLog.qml b/mix/qml/TransactionLog.qml index 887a14c07..e1c1f6b0b 100644 --- a/mix/qml/TransactionLog.qml +++ b/mix/qml/TransactionLog.qml @@ -163,7 +163,7 @@ Item { Keys.onPressed: { if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C && currentRow >=0 && currentRow < logTable.model.count) { var item = logTable.model.get(currentRow); - appContext.toClipboard(item.returned); + clipboard.text = item.returned; } } } diff --git a/mix/qml/WebCodeEditor.qml b/mix/qml/WebCodeEditor.qml index c424eeddd..5cf786d5f 100644 --- a/mix/qml/WebCodeEditor.qml +++ b/mix/qml/WebCodeEditor.qml @@ -34,7 +34,7 @@ Item { function syncClipboard() { if (Qt.platform.os == "osx") { - var text = appContext.clipboard; + var text = clipboard.text; editorBrowser.runJavaScript("setClipboardBase64(\"" + Qt.btoa(text) + "\")"); } } @@ -59,7 +59,7 @@ Item { } Connections { - target: appContext + target: clipboard onClipboardChanged: syncClipboard() } diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index d72d0a886..8507c9b46 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -58,16 +58,11 @@ Item { function changePage() { setPreviewUrl(urlInput.text); - /*if (pageCombo.currentIndex >= 0 && pageCombo.currentIndex < pageListModel.count) { - urlInput.text = httpServer.url + "/" + pageListModel.get(pageCombo.currentIndex).documentId; - setPreviewUrl(httpServer.url + "/" + pageListModel.get(pageCombo.currentIndex).documentId); - } else { - setPreviewUrl(""); - }*/ } + Connections { - target: appContext - onAppLoaded: { + target: mainApplication + onLoaded: { //We need to load the container using file scheme so that web security would allow loading local files in iframe var containerPage = fileIo.readFile("qrc:///qml/html/WebContainer.html"); webView.loadHtml(containerPage, httpServer.url + "/WebContainer.html") diff --git a/mix/qml/main.qml b/mix/qml/main.qml index 0c21e6513..5544d4639 100644 --- a/mix/qml/main.qml +++ b/mix/qml/main.qml @@ -7,9 +7,15 @@ import QtQuick.Window 2.1 import QtQuick.PrivateWidgets 1.1 import Qt.labs.settings 1.0 import org.ethereum.qml.QEther 1.0 +import org.ethereum.qml.CodeModel 1.0 +import org.ethereum.qml.ClientModel 1.0 +import org.ethereum.qml.FileIo 1.0 +import org.ethereum.qml.Clipboard 1.0 ApplicationWindow { + id: mainApplication + signal loaded; visible: true width: 1200 height: 800 @@ -17,8 +23,28 @@ ApplicationWindow { minimumHeight: 300 title: qsTr("Mix") - Connections - { + CodeModel { + id: codeModel + } + + ClientModel { + id: clientModel + codeModel: codeModel + } + + ProjectModel { + id: projectModel + } + + FileIo { + id: fileIo + } + + Clipboard { + id: clipboard + } + + Connections { target: mainApplication onClosing: { @@ -27,8 +53,11 @@ ApplicationWindow { } } - function close() - { + Component.onCompleted: { + loaded(); + } + + function close() { projectModel.appIsClosing = true; if (projectModel.projectPath !== "") projectModel.closeProject(function() { Qt.quit(); }) From 67ef8d1aeabb2a2a3125d8dd23f3184c865d27e9 Mon Sep 17 00:00:00 2001 From: Lefteris Karapetsas Date: Wed, 18 Mar 2015 17:42:11 +0100 Subject: [PATCH 009/221] Reorganizing conversion EndToEndTests - Adding/Renaming tests so that we have one for each of the following: * FixedBytes to Uint all sizes * FixedBytes to FixedBytes all sizes * Uint to FixedBytes all sizes - Grouped them together location-wise to make more sense --- test/SolidityEndToEndTest.cpp | 77 +++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index 6ad84341a..0bd27a5fc 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -1127,45 +1127,43 @@ BOOST_AUTO_TEST_CASE(type_conversions_cleanup) 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22})); } - +// fixed bytes to fixed bytes conversion tests BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_smaller_size) { char const* sourceCode = R"( contract Test { - function pipeTrough(bytes3 input) returns (bytes2 ret) { + function bytesToBytes(bytes4 input) returns (bytes2 ret) { return bytes2(input); } })"; compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("pipeTrough(bytes3)", "abc") == encodeArgs("ab")); + BOOST_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd") == encodeArgs("ab")); } -BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_size) +BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_greater_size) { char const* sourceCode = R"( contract Test { - function uintToBytes(uint256 h) returns (bytes32 s) { - return bytes32(h); + function bytesToBytes(bytes2 input) returns (bytes4 ret) { + return bytes4(input); } })"; compileAndRun(sourceCode); - u256 a("0x6162630000000000000000000000000000000000000000000000000000000000"); - BOOST_CHECK(callContractFunction("uintToBytes(uint256)", a) == encodeArgs(a)); + BOOST_CHECK(callContractFunction("bytesToBytes(bytes2)", "ab") == encodeArgs("ab")); } -BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_different_size) +BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_same_size) { char const* sourceCode = R"( contract Test { - function uintToBytes(uint160 h) returns (bytes20 s) { - return bytes20(h); + function bytesToBytes(bytes4 input) returns (bytes4 ret) { + return bytes4(input); } })"; compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("uintToBytes(uint160)", - u160("0x6161626361626361626361616263616263616263")) == encodeArgs(string("aabcabcabcaabcabcabc"))); + BOOST_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd") == encodeArgs("abcd")); } - +// fixed bytes to uint conversion tests BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_size) { char const* sourceCode = R"( @@ -1179,17 +1177,30 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_size) encodeArgs(u256("0x6162633200000000000000000000000000000000000000000000000000000000"))); } -BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_different_size) +BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_min_size) +{ + char const* sourceCode = R"( + contract Test { + function bytesToUint(bytes1 s) returns (uint8 h) { + return uint8(s); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("bytesToUint(bytes1)", string("a")) == + encodeArgs(u256("0x61"))); +} + +BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_smaller_size) { char const* sourceCode = R"( contract Test { - function bytesToUint(bytes20 s) returns (uint160 h) { - return uint160(s); + function bytesToUint(bytes4 s) returns (uint16 h) { + return uint16(s); } })"; compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("bytesToUint(bytes20)", string("aabcabcabcaabcabcabc")) == - encodeArgs(u160("0x6161626361626361626361616263616263616263"))); + BOOST_CHECK(callContractFunction("bytesToUint(bytes4)", string("abcd")) == + encodeArgs(u256("0x6364"))); } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_greater_size) @@ -1204,22 +1215,21 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_greater_size) BOOST_CHECK(callContractFunction("bytesToUint(bytes4)", string("abcd")) == encodeArgs(u256("0x61626364"))); } - -BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_different_min_size) +// uint fixed bytes conversion tests +BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_size) { char const* sourceCode = R"( contract Test { - function bytesToUint(bytes1 s) returns (uint8 h) { - return uint8(s); + function uintToBytes(uint256 h) returns (bytes32 s) { + return bytes32(h); } })"; compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("bytesToUint(bytes1)", string("a")) == - encodeArgs(u256("0x61"))); + u256 a("0x6162630000000000000000000000000000000000000000000000000000000000"); + BOOST_CHECK(callContractFunction("uintToBytes(uint256)", a) == encodeArgs(a)); } - -BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_different_min_size) +BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_min_size) { char const* sourceCode = R"( contract Test { @@ -1232,6 +1242,19 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_different_min_size) encodeArgs(string("a"))); } +BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_smaller_size) +{ + char const* sourceCode = R"( + contract Test { + function uintToBytes(uint32 h) returns (bytes2 s) { + return bytes2(h); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("uintToBytes(uint32)", + u160("0x61626364")) == encodeArgs(string("cd"))); +} + BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_greater_size) { char const* sourceCode = R"( From cc1be11d6c36d1da99af09181bb756b83c9dc54e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 19 Mar 2015 12:07:10 +0100 Subject: [PATCH 010/221] Constant folding in div-based operators disabled There is a bug in LLVM arbitrary precision division algorithm. --- evmjit/libevmjit/Arith256.cpp | 46 ++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/evmjit/libevmjit/Arith256.cpp b/evmjit/libevmjit/Arith256.cpp index f24626750..e88fda432 100644 --- a/evmjit/libevmjit/Arith256.cpp +++ b/evmjit/libevmjit/Arith256.cpp @@ -371,17 +371,18 @@ llvm::Value* Arith256::mul(llvm::Value* _arg1, llvm::Value* _arg2) std::pair Arith256::div(llvm::Value* _arg1, llvm::Value* _arg2) { - if (auto c1 = llvm::dyn_cast(_arg1)) - { - if (auto c2 = llvm::dyn_cast(_arg2)) - { - if (!c2->getValue()) - return std::make_pair(Constant::get(0), Constant::get(0)); - auto div = Constant::get(c1->getValue().udiv(c2->getValue())); - auto mod = Constant::get(c1->getValue().urem(c2->getValue())); - return std::make_pair(div, mod); - } - } + // FIXME: Disabled because of llvm::APInt::urem bug +// if (auto c1 = llvm::dyn_cast(_arg1)) +// { +// if (auto c2 = llvm::dyn_cast(_arg2)) +// { +// if (!c2->getValue()) +// return std::make_pair(Constant::get(0), Constant::get(0)); +// auto div = Constant::get(c1->getValue().udiv(c2->getValue())); +// auto mod = Constant::get(c1->getValue().urem(c2->getValue())); +// return std::make_pair(div, mod); +// } +// } auto r = createCall(getDivFunc(Type::Word), {_arg1, _arg2}); auto div = m_builder.CreateExtractValue(r, 0, "div"); @@ -391,17 +392,18 @@ std::pair Arith256::div(llvm::Value* _arg1, llvm::Va std::pair Arith256::sdiv(llvm::Value* _x, llvm::Value* _y) { - if (auto c1 = llvm::dyn_cast(_x)) - { - if (auto c2 = llvm::dyn_cast(_y)) - { - if (!c2->getValue()) - return std::make_pair(Constant::get(0), Constant::get(0)); - auto div = Constant::get(c1->getValue().sdiv(c2->getValue())); - auto mod = Constant::get(c1->getValue().srem(c2->getValue())); - return std::make_pair(div, mod); - } - } + // FIXME: Disabled because of llvm::APInt::urem bug +// if (auto c1 = llvm::dyn_cast(_x)) +// { +// if (auto c2 = llvm::dyn_cast(_y)) +// { +// if (!c2->getValue()) +// return std::make_pair(Constant::get(0), Constant::get(0)); +// auto div = Constant::get(c1->getValue().sdiv(c2->getValue())); +// auto mod = Constant::get(c1->getValue().srem(c2->getValue())); +// return std::make_pair(div, mod); +// } +// } auto xIsNeg = m_builder.CreateICmpSLT(_x, Constant::get(0)); auto xNeg = m_builder.CreateSub(Constant::get(0), _x); From 16a778073cc1361ee4b691d3d5524163f76720be Mon Sep 17 00:00:00 2001 From: arkpar Date: Thu, 19 Mar 2015 12:53:29 +0100 Subject: [PATCH 011/221] update states on contract rename --- mix/CodeModel.cpp | 7 ++++++- mix/CodeModel.h | 2 ++ mix/qml/ProjectList.qml | 9 +++++---- mix/qml/StateListModel.qml | 39 +++++++++++++++++++++++++++++++++----- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 16f40cb28..eea3b4b78 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -258,11 +258,16 @@ void CodeModel::runCompilationJob(int _jobId) CompiledContract* contract = new CompiledContract(cs, name, source); QQmlEngine::setObjectOwnership(contract, QQmlEngine::CppOwnership); result[name] = contract; - CompiledContract* prevContract = m_contractMap.value(name); + CompiledContract* prevContract = nullptr; + for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); ++c) + if (c.value()->documentId() == contract->documentId()) + prevContract = c.value(); if (prevContract != nullptr && prevContract->contractInterface() != result[name]->contractInterface()) emit contractInterfaceChanged(name); if (prevContract == nullptr) emit newContractCompiled(name); + else if (prevContract->contract()->name() != name) + emit contractRenamed(contract->documentId(), prevContract->contract()->name(), name); } releaseContracts(); m_contractMap.swap(result); diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 03be459f1..a4ca31e08 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -165,6 +165,8 @@ signals: void contractInterfaceChanged(QString _documentId); /// Emitted if there is a new contract compiled for the first time void newContractCompiled(QString _documentId); + /// Emitted if a contract name has been changed + void contractRenamed(QString _documentId, QString _oldName, QString _newName); public slots: /// Update code model on source code change diff --git a/mix/qml/ProjectList.qml b/mix/qml/ProjectList.qml index f3964f094..0cbd7155a 100644 --- a/mix/qml/ProjectList.qml +++ b/mix/qml/ProjectList.qml @@ -111,15 +111,16 @@ Item { Connections { target: codeModel - onCompilationComplete: { - if (modelData === "Contracts") { + onContractRenamed: { + if (modelData === "Contracts") + { var ci = 0; for (var si = 0; si < projectModel.listModel.count; si++) { var document = projectModel.listModel.get(si); if (document.isContract) { var compiledDoc = codeModel.contractByDocumentId(document.documentId); - if (compiledDoc && compiledDoc.documentId === document.documentId && compiledDoc.contract.name !== document.name) { - document.name = compiledDoc.contract.name; + if (_documentId === document.documentId && _newName !== document.name) { + document.name = _newName; projectModel.listModel.set(si, document); sectionModel.set(ci, document); } diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index 50ecd16f3..11716e7d9 100644 --- a/mix/qml/StateListModel.qml +++ b/mix/qml/StateListModel.qml @@ -128,6 +128,9 @@ Item { onNewContractCompiled: { stateListModel.addNewContracts(); } + onContractRenamed: { + stateListModel.renameContracts(_oldName, _newName); + } } StateDialog { @@ -206,11 +209,36 @@ Item { return item; } + function renameContracts(oldName, newName) { + var changed = false; + for(var c in codeModel.contracts) { + for (var s = 0; s < stateListModel.count; s++) { + var state = stateList[s]; + for (var t = 0; t < state.transactions.length; t++) { + var transaction = state.transactions[t]; + if (transaction.contractId === oldName) { + transaction.contractId = newName; + if (transaction.functionId === oldName) + transaction.functionId = newName; + changed = true; + state.transactions[t] = transaction; + } + + } + stateListModel.set(s, state); + stateList[s] = state; + } + } + if (changed) + save(); + } + function addNewContracts() { //add new contracts for all states + var changed = false; for(var c in codeModel.contracts) { for (var s = 0; s < stateListModel.count; s++) { - var state = stateList[s];//toPlainStateItem(stateListModel.get(s)); + var state = stateList[s]; for (var t = 0; t < state.transactions.length; t++) { var transaction = state.transactions[t]; if (transaction.functionId === c && transaction.contractId === c) @@ -223,13 +251,14 @@ Item { ctorTr.contractId = c; ctorTr.sender = state.accounts[0].secret; state.transactions.push(ctorTr); - var item = state;//fromPlainStateItem(state); - stateListModel.set(s, item); - stateList[s] = item; + changed = true; + stateListModel.set(s, state); + stateList[s] = state; } } } - save(); + if (changed) + save(); } function addState() { From e5fb2024683f12cc2d49db8ca08f7aefbbe687e8 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 19 Mar 2015 13:56:06 +0100 Subject: [PATCH 012/221] Add JavaScript autocompletion. --- mix/qml/html/cm/acorn.js | 2864 ++++++++++++++++++++++++++++++++ mix/qml/html/cm/acorn_loose.js | 1164 +++++++++++++ mix/qml/html/cm/comment.js | 87 + mix/qml/html/cm/def.js | 547 ++++++ mix/qml/html/cm/doc_comment.js | 402 +++++ mix/qml/html/cm/ecma5spec.js | 969 +++++++++++ mix/qml/html/cm/infer.js | 1615 ++++++++++++++++++ mix/qml/html/cm/show-hint.js | 139 +- mix/qml/html/cm/signal.js | 26 + mix/qml/html/cm/tern.js | 697 ++++++++ mix/qml/html/cm/ternserver.js | 994 +++++++++++ mix/qml/html/cm/walk.js | 359 ++++ mix/qml/html/codeeditor.html | 11 + mix/qml/html/codeeditor.js | 20 +- mix/web.qrc | 11 + 15 files changed, 9857 insertions(+), 48 deletions(-) create mode 100644 mix/qml/html/cm/acorn.js create mode 100644 mix/qml/html/cm/acorn_loose.js create mode 100644 mix/qml/html/cm/comment.js create mode 100644 mix/qml/html/cm/def.js create mode 100644 mix/qml/html/cm/doc_comment.js create mode 100644 mix/qml/html/cm/ecma5spec.js create mode 100644 mix/qml/html/cm/infer.js create mode 100644 mix/qml/html/cm/signal.js create mode 100644 mix/qml/html/cm/tern.js create mode 100644 mix/qml/html/cm/ternserver.js create mode 100644 mix/qml/html/cm/walk.js diff --git a/mix/qml/html/cm/acorn.js b/mix/qml/html/cm/acorn.js new file mode 100644 index 000000000..2e3305062 --- /dev/null +++ b/mix/qml/html/cm/acorn.js @@ -0,0 +1,2864 @@ +// Acorn is a tiny, fast JavaScript parser written in JavaScript. +// +// Acorn was written by Marijn Haverbeke and various contributors and +// released under an MIT license. The Unicode regexps (for identifiers +// and whitespace) were taken from [Esprima](http://esprima.org) by +// Ariya Hidayat. +// +// Git repositories for Acorn are available at +// +// http://marijnhaverbeke.nl/git/acorn +// https://github.com/marijnh/acorn.git +// +// Please use the [github bug tracker][ghbt] to report issues. +// +// [ghbt]: https://github.com/marijnh/acorn/issues +// +// This file defines the main parser interface. The library also comes +// with a [error-tolerant parser][dammit] and an +// [abstract syntax tree walker][walk], defined in other files. +// +// [dammit]: acorn_loose.js +// [walk]: util/walk.js + +(function(root, mod) { + if (typeof exports == "object" && typeof module == "object") return mod(exports); // CommonJS + if (typeof define == "function" && define.amd) return define(["exports"], mod); // AMD + mod(root.acorn || (root.acorn = {})); // Plain browser env +})(this, function(exports) { + "use strict"; + + exports.version = "0.12.1"; + + // The main exported interface (under `self.acorn` when in the + // browser) is a `parse` function that takes a code string and + // returns an abstract syntax tree as specified by [Mozilla parser + // API][api], with the caveat that inline XML is not recognized. + // + // [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API + + var options, input, inputLen, sourceFile; + + exports.parse = function(inpt, opts) { + input = String(inpt); inputLen = input.length; + setOptions(opts); + initTokenState(); + var startPos = options.locations ? [tokPos, curPosition()] : tokPos; + initParserState(); + return parseTopLevel(options.program || startNodeAt(startPos)); + }; + + // A second optional argument can be given to further configure + // the parser process. These options are recognized: + + var defaultOptions = exports.defaultOptions = { + // `ecmaVersion` indicates the ECMAScript version to parse. Must + // be either 3, or 5, or 6. This influences support for strict + // mode, the set of reserved words, support for getters and + // setters and other features. + ecmaVersion: 5, + // Turn on `strictSemicolons` to prevent the parser from doing + // automatic semicolon insertion. + strictSemicolons: false, + // When `allowTrailingCommas` is false, the parser will not allow + // trailing commas in array and object literals. + allowTrailingCommas: true, + // By default, reserved words are not enforced. Enable + // `forbidReserved` to enforce them. When this option has the + // value "everywhere", reserved words and keywords can also not be + // used as property names. + forbidReserved: false, + // When enabled, a return at the top level is not considered an + // error. + allowReturnOutsideFunction: false, + // When enabled, import/export statements are not constrained to + // appearing at the top of the program. + allowImportExportEverywhere: false, + // When enabled, hashbang directive in the beginning of file + // is allowed and treated as a line comment. + allowHashBang: false, + // When `locations` is on, `loc` properties holding objects with + // `start` and `end` properties in `{line, column}` form (with + // line being 1-based and column 0-based) will be attached to the + // nodes. + locations: false, + // A function can be passed as `onToken` option, which will + // cause Acorn to call that function with object in the same + // format as tokenize() returns. Note that you are not + // allowed to call the parser from the callback—that will + // corrupt its internal state. + onToken: null, + // A function can be passed as `onComment` option, which will + // cause Acorn to call that function with `(block, text, start, + // end)` parameters whenever a comment is skipped. `block` is a + // boolean indicating whether this is a block (`/* */`) comment, + // `text` is the content of the comment, and `start` and `end` are + // character offsets that denote the start and end of the comment. + // When the `locations` option is on, two more parameters are + // passed, the full `{line, column}` locations of the start and + // end of the comments. Note that you are not allowed to call the + // parser from the callback—that will corrupt its internal state. + onComment: null, + // Nodes have their start and end characters offsets recorded in + // `start` and `end` properties (directly on the node, rather than + // the `loc` object, which holds line/column data. To also add a + // [semi-standardized][range] `range` property holding a `[start, + // end]` array with the same numbers, set the `ranges` option to + // `true`. + // + // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678 + ranges: false, + // It is possible to parse multiple files into a single AST by + // passing the tree produced by parsing the first file as + // `program` option in subsequent parses. This will add the + // toplevel forms of the parsed file to the `Program` (top) node + // of an existing parse tree. + program: null, + // When `locations` is on, you can pass this to record the source + // file in every node's `loc` object. + sourceFile: null, + // This value, if given, is stored in every node, whether + // `locations` is on or off. + directSourceFile: null, + // When enabled, parenthesized expressions are represented by + // (non-standard) ParenthesizedExpression nodes + preserveParens: false + }; + + // This function tries to parse a single expression at a given + // offset in a string. Useful for parsing mixed-language formats + // that embed JavaScript expressions. + + exports.parseExpressionAt = function(inpt, pos, opts) { + input = String(inpt); inputLen = input.length; + setOptions(opts); + initTokenState(pos); + initParserState(); + return parseExpression(); + }; + + var isArray = function (obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + + function setOptions(opts) { + options = {}; + for (var opt in defaultOptions) + options[opt] = opts && has(opts, opt) ? opts[opt] : defaultOptions[opt]; + sourceFile = options.sourceFile || null; + if (isArray(options.onToken)) { + var tokens = options.onToken; + options.onToken = function (token) { + tokens.push(token); + }; + } + if (isArray(options.onComment)) { + var comments = options.onComment; + options.onComment = function (block, text, start, end, startLoc, endLoc) { + var comment = { + type: block ? 'Block' : 'Line', + value: text, + start: start, + end: end + }; + if (options.locations) { + comment.loc = new SourceLocation(); + comment.loc.start = startLoc; + comment.loc.end = endLoc; + } + if (options.ranges) + comment.range = [start, end]; + comments.push(comment); + }; + } + isKeyword = options.ecmaVersion >= 6 ? isEcma6Keyword : isEcma5AndLessKeyword; + } + + // The `getLineInfo` function is mostly useful when the + // `locations` option is off (for performance reasons) and you + // want to find the line/column position for a given character + // offset. `input` should be the code string that the offset refers + // into. + + var getLineInfo = exports.getLineInfo = function(input, offset) { + for (var line = 1, cur = 0;;) { + lineBreak.lastIndex = cur; + var match = lineBreak.exec(input); + if (match && match.index < offset) { + ++line; + cur = match.index + match[0].length; + } else break; + } + return {line: line, column: offset - cur}; + }; + + function Token() { + this.type = tokType; + this.value = tokVal; + this.start = tokStart; + this.end = tokEnd; + if (options.locations) { + this.loc = new SourceLocation(); + this.loc.end = tokEndLoc; + } + if (options.ranges) + this.range = [tokStart, tokEnd]; + } + + exports.Token = Token; + + // Acorn is organized as a tokenizer and a recursive-descent parser. + // The `tokenize` export provides an interface to the tokenizer. + // Because the tokenizer is optimized for being efficiently used by + // the Acorn parser itself, this interface is somewhat crude and not + // very modular. Performing another parse or call to `tokenize` will + // reset the internal state, and invalidate existing tokenizers. + + exports.tokenize = function(inpt, opts) { + input = String(inpt); inputLen = input.length; + setOptions(opts); + initTokenState(); + skipSpace(); + + function getToken() { + lastEnd = tokEnd; + readToken(); + return new Token(); + } + getToken.jumpTo = function(pos, exprAllowed) { + tokPos = pos; + if (options.locations) { + tokCurLine = 1; + tokLineStart = lineBreak.lastIndex = 0; + var match; + while ((match = lineBreak.exec(input)) && match.index < pos) { + ++tokCurLine; + tokLineStart = match.index + match[0].length; + } + } + tokExprAllowed = !!exprAllowed; + skipSpace(); + }; + getToken.current = function() { return new Token(); }; + if (typeof Symbol !== 'undefined') { + getToken[Symbol.iterator] = function () { + return { + next: function () { + var token = getToken(); + return { + done: token.type === _eof, + value: token + }; + } + }; + }; + } + getToken.options = options; + return getToken; + }; + + // State is kept in (closure-)global variables. We already saw the + // `options`, `input`, and `inputLen` variables above. + + // The current position of the tokenizer in the input. + + var tokPos; + + // The start and end offsets of the current token. + + var tokStart, tokEnd; + + // When `options.locations` is true, these hold objects + // containing the tokens start and end line/column pairs. + + var tokStartLoc, tokEndLoc; + + // The type and value of the current token. Token types are objects, + // named by variables against which they can be compared, and + // holding properties that describe them (indicating, for example, + // the precedence of an infix operator, and the original name of a + // keyword token). The kind of value that's held in `tokVal` depends + // on the type of the token. For literals, it is the literal value, + // for operators, the operator name, and so on. + + var tokType, tokVal; + + // Internal state for the tokenizer. To distinguish between division + // operators and regular expressions, it remembers whether the last + // token was one that is allowed to be followed by an expression. In + // some cases, notably after ')' or '}' tokens, the situation + // depends on the context before the matching opening bracket, so + // tokContext keeps a stack of information about current bracketed + // forms. + + var tokContext, tokExprAllowed; + + // When `options.locations` is true, these are used to keep + // track of the current line, and know when a new line has been + // entered. + + var tokCurLine, tokLineStart; + + // These store the position of the previous token, which is useful + // when finishing a node and assigning its `end` position. + + var lastStart, lastEnd, lastEndLoc; + + // This is the parser's state. `inFunction` is used to reject + // `return` statements outside of functions, `inGenerator` to + // reject `yield`s outside of generators, `labels` to verify + // that `break` and `continue` have somewhere to jump to, and + // `strict` indicates whether strict mode is on. + + var inFunction, inGenerator, labels, strict; + + function initParserState() { + lastStart = lastEnd = tokPos; + if (options.locations) lastEndLoc = curPosition(); + inFunction = inGenerator = false; + labels = []; + skipSpace(); + readToken(); + } + + // This function is used to raise exceptions on parse errors. It + // takes an offset integer (into the current `input`) to indicate + // the location of the error, attaches the position to the end + // of the error message, and then raises a `SyntaxError` with that + // message. + + function raise(pos, message) { + var loc = getLineInfo(input, pos); + message += " (" + loc.line + ":" + loc.column + ")"; + var err = new SyntaxError(message); + err.pos = pos; err.loc = loc; err.raisedAt = tokPos; + throw err; + } + + function fullCharCodeAtPos() { + var code = input.charCodeAt(tokPos); + if (code <= 0xd7ff || code >= 0xe000) return code; + var next = input.charCodeAt(tokPos + 1); + return (code << 10) + next - 0x35fdc00; + } + + function skipChar(code) { + if (code <= 0xffff) tokPos++; + else tokPos += 2; + } + + // Reused empty array added for node fields that are always empty. + + var empty = []; + + // ## Token types + + // The assignment of fine-grained, information-carrying type objects + // allows the tokenizer to store the information it has about a + // token in a way that is very cheap for the parser to look up. + + // All token type variables start with an underscore, to make them + // easy to recognize. + + // These are the general types. The `type` property is only used to + // make them recognizeable when debugging. + + var _num = {type: "num"}, _regexp = {type: "regexp"}, _string = {type: "string"}; + var _name = {type: "name"}, _eof = {type: "eof"}; + + // Keyword tokens. The `keyword` property (also used in keyword-like + // operators) indicates that the token originated from an + // identifier-like word, which is used when parsing property names. + // + // The `beforeExpr` property is used to disambiguate between regular + // expressions and divisions. It is set on all token types that can + // be followed by an expression (thus, a slash after them would be a + // regular expression). + // + // `isLoop` marks a keyword as starting a loop, which is important + // to know when parsing a label, in order to allow or disallow + // continue jumps to that label. + + var _break = {keyword: "break"}, _case = {keyword: "case", beforeExpr: true}, _catch = {keyword: "catch"}; + var _continue = {keyword: "continue"}, _debugger = {keyword: "debugger"}, _default = {keyword: "default"}; + var _do = {keyword: "do", isLoop: true}, _else = {keyword: "else", beforeExpr: true}; + var _finally = {keyword: "finally"}, _for = {keyword: "for", isLoop: true}, _function = {keyword: "function"}; + var _if = {keyword: "if"}, _return = {keyword: "return", beforeExpr: true}, _switch = {keyword: "switch"}; + var _throw = {keyword: "throw", beforeExpr: true}, _try = {keyword: "try"}, _var = {keyword: "var"}; + var _let = {keyword: "let"}, _const = {keyword: "const"}; + var _while = {keyword: "while", isLoop: true}, _with = {keyword: "with"}, _new = {keyword: "new", beforeExpr: true}; + var _this = {keyword: "this"}; + var _class = {keyword: "class"}, _extends = {keyword: "extends", beforeExpr: true}; + var _export = {keyword: "export"}, _import = {keyword: "import"}; + var _yield = {keyword: "yield", beforeExpr: true}; + + // The keywords that denote values. + + var _null = {keyword: "null", atomValue: null}, _true = {keyword: "true", atomValue: true}; + var _false = {keyword: "false", atomValue: false}; + + // Some keywords are treated as regular operators. `in` sometimes + // (when parsing `for`) needs to be tested against specifically, so + // we assign a variable name to it for quick comparing. + + var _in = {keyword: "in", binop: 7, beforeExpr: true}; + + // Map keyword names to token types. + + var keywordTypes = {"break": _break, "case": _case, "catch": _catch, + "continue": _continue, "debugger": _debugger, "default": _default, + "do": _do, "else": _else, "finally": _finally, "for": _for, + "function": _function, "if": _if, "return": _return, "switch": _switch, + "throw": _throw, "try": _try, "var": _var, "let": _let, "const": _const, + "while": _while, "with": _with, + "null": _null, "true": _true, "false": _false, "new": _new, "in": _in, + "instanceof": {keyword: "instanceof", binop: 7, beforeExpr: true}, "this": _this, + "typeof": {keyword: "typeof", prefix: true, beforeExpr: true}, + "void": {keyword: "void", prefix: true, beforeExpr: true}, + "delete": {keyword: "delete", prefix: true, beforeExpr: true}, + "class": _class, "extends": _extends, + "export": _export, "import": _import, "yield": _yield}; + + // Punctuation token types. Again, the `type` property is purely for debugging. + + var _bracketL = {type: "[", beforeExpr: true}, _bracketR = {type: "]"}, _braceL = {type: "{", beforeExpr: true}; + var _braceR = {type: "}"}, _parenL = {type: "(", beforeExpr: true}, _parenR = {type: ")"}; + var _comma = {type: ",", beforeExpr: true}, _semi = {type: ";", beforeExpr: true}; + var _colon = {type: ":", beforeExpr: true}, _dot = {type: "."}, _question = {type: "?", beforeExpr: true}; + var _arrow = {type: "=>", beforeExpr: true}, _template = {type: "template"}; + var _ellipsis = {type: "...", beforeExpr: true}; + var _backQuote = {type: "`"}, _dollarBraceL = {type: "${", beforeExpr: true}; + + // Operators. These carry several kinds of properties to help the + // parser use them properly (the presence of these properties is + // what categorizes them as operators). + // + // `binop`, when present, specifies that this operator is a binary + // operator, and will refer to its precedence. + // + // `prefix` and `postfix` mark the operator as a prefix or postfix + // unary operator. `isUpdate` specifies that the node produced by + // the operator should be of type UpdateExpression rather than + // simply UnaryExpression (`++` and `--`). + // + // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as + // binary operators with a very low precedence, that should result + // in AssignmentExpression nodes. + + var _slash = {binop: 10, beforeExpr: true}, _eq = {isAssign: true, beforeExpr: true}; + var _assign = {isAssign: true, beforeExpr: true}; + var _incDec = {postfix: true, prefix: true, isUpdate: true}, _prefix = {prefix: true, beforeExpr: true}; + var _logicalOR = {binop: 1, beforeExpr: true}; + var _logicalAND = {binop: 2, beforeExpr: true}; + var _bitwiseOR = {binop: 3, beforeExpr: true}; + var _bitwiseXOR = {binop: 4, beforeExpr: true}; + var _bitwiseAND = {binop: 5, beforeExpr: true}; + var _equality = {binop: 6, beforeExpr: true}; + var _relational = {binop: 7, beforeExpr: true}; + var _bitShift = {binop: 8, beforeExpr: true}; + var _plusMin = {binop: 9, prefix: true, beforeExpr: true}; + var _modulo = {binop: 10, beforeExpr: true}; + + // '*' may be multiply or have special meaning in ES6 + var _star = {binop: 10, beforeExpr: true}; + + // Provide access to the token types for external users of the + // tokenizer. + + exports.tokTypes = {bracketL: _bracketL, bracketR: _bracketR, braceL: _braceL, braceR: _braceR, + parenL: _parenL, parenR: _parenR, comma: _comma, semi: _semi, colon: _colon, + dot: _dot, ellipsis: _ellipsis, question: _question, slash: _slash, eq: _eq, + name: _name, eof: _eof, num: _num, regexp: _regexp, string: _string, + arrow: _arrow, template: _template, star: _star, assign: _assign, + backQuote: _backQuote, dollarBraceL: _dollarBraceL}; + for (var kw in keywordTypes) exports.tokTypes["_" + kw] = keywordTypes[kw]; + + // This is a trick taken from Esprima. It turns out that, on + // non-Chrome browsers, to check whether a string is in a set, a + // predicate containing a big ugly `switch` statement is faster than + // a regular expression, and on Chrome the two are about on par. + // This function uses `eval` (non-lexical) to produce such a + // predicate from a space-separated string of words. + // + // It starts by sorting the words by length. + + function makePredicate(words) { + words = words.split(" "); + var f = "", cats = []; + out: for (var i = 0; i < words.length; ++i) { + for (var j = 0; j < cats.length; ++j) + if (cats[j][0].length == words[i].length) { + cats[j].push(words[i]); + continue out; + } + cats.push([words[i]]); + } + function compareTo(arr) { + if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; + f += "switch(str){"; + for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; + f += "return true}return false;"; + } + + // When there are more than three length categories, an outer + // switch first dispatches on the lengths, to save on comparisons. + + if (cats.length > 3) { + cats.sort(function(a, b) {return b.length - a.length;}); + f += "switch(str.length){"; + for (var i = 0; i < cats.length; ++i) { + var cat = cats[i]; + f += "case " + cat[0].length + ":"; + compareTo(cat); + } + f += "}"; + + // Otherwise, simply generate a flat `switch` statement. + + } else { + compareTo(words); + } + return new Function("str", f); + } + + // The ECMAScript 3 reserved word list. + + var isReservedWord3 = makePredicate("abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile"); + + // ECMAScript 5 reserved words. + + var isReservedWord5 = makePredicate("class enum extends super const export import"); + + // The additional reserved words in strict mode. + + var isStrictReservedWord = makePredicate("implements interface let package private protected public static yield"); + + // The forbidden variable names in strict mode. + + var isStrictBadIdWord = makePredicate("eval arguments"); + + // And the keywords. + + var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this"; + + var isEcma5AndLessKeyword = makePredicate(ecma5AndLessKeywords); + + var isEcma6Keyword = makePredicate(ecma5AndLessKeywords + " let const class extends export import yield"); + + var isKeyword = isEcma5AndLessKeyword; + + // ## Character categories + + // Big ugly regular expressions that match characters in the + // whitespace, identifier, and identifier-start categories. These + // are only applied when a character is found to actually have a + // code point above 128. + // Generated by `tools/generate-identifier-regex.js`. + + var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/; + var nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0-\u08b2\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua7ad\ua7b0\ua7b1\ua7f7-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab5f\uab64\uab65\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; + var nonASCIIidentifierChars = "\u200c\u200d\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08e4-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c03\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0d01-\u0d03\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d82\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19b0-\u19c0\u19c8\u19c9\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf2-\u1cf4\u1cf8\u1cf9\u1dc0-\u1df5\u1dfc-\u1dff\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua620-\ua629\ua66f\ua674-\ua67d\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua880\ua881\ua8b4-\ua8c4\ua8d0-\ua8d9\ua8e0-\ua8f1\ua900-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2d\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f"; + + var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); + var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); + nonASCIIidentifierStartChars = nonASCIIidentifierChars = null; + + // These are a run-length and offset encoded representation of the + // >0xffff code points that are a valid part of identifiers. The + // offset starts at 0x10000, and each pair of numbers represents an + // offset to the next range, and then a size of the range. They were + // generated by tools/generate-identifier-regex.js + var astralIdentifierStartCodes = [0,11,2,25,2,18,2,1,2,14,3,13,35,122,70,52,268,28,4,48,48,31,17,26,6,37,11,29,3,35,5,7,2,4,43,157,99,39,9,51,157,310,10,21,11,7,153,5,3,0,2,43,2,1,4,0,3,22,11,22,10,30,98,21,11,25,71,55,7,1,65,0,16,3,2,2,2,26,45,28,4,28,36,7,2,27,28,53,11,21,11,18,14,17,111,72,955,52,76,44,33,24,27,35,42,34,4,0,13,47,15,3,22,0,38,17,2,24,133,46,39,7,3,1,3,21,2,6,2,1,2,4,4,0,32,4,287,47,21,1,2,0,185,46,82,47,21,0,60,42,502,63,32,0,449,56,1288,920,104,110,2962,1070,13266,568,8,30,114,29,19,47,17,3,32,20,6,18,881,68,12,0,67,12,16481,1,3071,106,6,12,4,8,8,9,5991,84,2,70,2,1,3,0,3,1,3,3,2,11,2,0,2,6,2,64,2,3,3,7,2,6,2,27,2,3,2,4,2,0,4,6,2,339,3,24,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,30,2,24,2,7,4149,196,1340,3,2,26,2,1,2,0,3,0,2,9,2,3,2,0,2,0,7,0,5,0,2,0,2,0,2,2,2,1,2,0,3,0,2,0,2,0,2,0,2,0,2,1,2,0,3,3,2,6,2,3,2,3,2,0,2,9,2,16,6,2,2,4,2,16,4421,42710,42,4148,12,221,16355,541]; + var astralIdentifierCodes = [509,0,227,0,150,4,294,9,1368,2,2,1,6,3,41,2,5,0,166,1,1306,2,54,14,32,9,16,3,46,10,54,9,7,2,37,13,2,9,52,0,13,2,49,13,16,9,83,11,168,11,6,9,8,2,57,0,2,6,3,1,3,2,10,0,11,1,3,6,4,4,316,19,13,9,214,6,3,8,112,16,16,9,82,12,9,9,535,9,20855,9,135,4,60,6,26,9,1016,45,17,3,19723,1,5319,4,4,5,9,7,3,6,31,3,149,2,1418,49,4305,6,792618,239]; + + // This has a complexity linear to the value of the code. The + // assumption is that looking up astral identifier characters is + // rare. + function isInAstralSet(code, set) { + var pos = 0x10000; + for (var i = 0; i < set.length; i += 2) { + pos += set[i]; + if (pos > code) return false; + pos += set[i + 1]; + if (pos >= code) return true; + } + } + + // Whether a single character denotes a newline. + + var newline = /[\n\r\u2028\u2029]/; + + function isNewLine(code) { + return code === 10 || code === 13 || code === 0x2028 || code == 0x2029; + } + + // Matches a whole line break (where CRLF is considered a single + // line break). Used to count lines. + + var lineBreak = /\r\n|[\n\r\u2028\u2029]/g; + + // Test whether a given character code starts an identifier. + + var isIdentifierStart = exports.isIdentifierStart = function(code, astral) { + if (code < 65) return code === 36; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123) return true; + if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); + if (astral === false) return false; + return isInAstralSet(code, astralIdentifierStartCodes); + }; + + // Test whether a given character is part of an identifier. + + var isIdentifierChar = exports.isIdentifierChar = function(code, astral) { + if (code < 48) return code === 36; + if (code < 58) return true; + if (code < 65) return false; + if (code < 91) return true; + if (code < 97) return code === 95; + if (code < 123) return true; + if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); + if (astral === false) return false; + return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes); + }; + + // ## Tokenizer + + // These are used when `options.locations` is on, for the + // `tokStartLoc` and `tokEndLoc` properties. + + function Position(line, col) { + this.line = line; + this.column = col; + } + + Position.prototype.offset = function(n) { + return new Position(this.line, this.column + n); + }; + + function curPosition() { + return new Position(tokCurLine, tokPos - tokLineStart); + } + + // Reset the token state. Used at the start of a parse. + + function initTokenState(pos) { + if (pos) { + tokPos = pos; + tokLineStart = Math.max(0, input.lastIndexOf("\n", pos)); + tokCurLine = input.slice(0, tokLineStart).split(newline).length; + } else { + tokCurLine = 1; + tokPos = tokLineStart = 0; + } + tokType = _eof; + tokContext = [b_stat]; + tokExprAllowed = true; + strict = false; + if (tokPos === 0 && options.allowHashBang && input.slice(0, 2) === '#!') { + skipLineComment(2); + } + } + + // The algorithm used to determine whether a regexp can appear at a + // given point in the program is loosely based on sweet.js' approach. + // See https://github.com/mozilla/sweet.js/wiki/design + + var b_stat = {token: "{", isExpr: false}, b_expr = {token: "{", isExpr: true}, b_tmpl = {token: "${", isExpr: true}; + var p_stat = {token: "(", isExpr: false}, p_expr = {token: "(", isExpr: true}; + var q_tmpl = {token: "`", isExpr: true}, f_expr = {token: "function", isExpr: true}; + + function curTokContext() { + return tokContext[tokContext.length - 1]; + } + + function braceIsBlock(prevType) { + var parent; + if (prevType === _colon && (parent = curTokContext()).token == "{") + return !parent.isExpr; + if (prevType === _return) + return newline.test(input.slice(lastEnd, tokStart)); + if (prevType === _else || prevType === _semi || prevType === _eof) + return true; + if (prevType == _braceL) + return curTokContext() === b_stat; + return !tokExprAllowed; + } + + // Called at the end of every token. Sets `tokEnd`, `tokVal`, and + // maintains `tokContext` and `tokExprAllowed`, and skips the space + // after the token, so that the next one's `tokStart` will point at + // the right position. + + function finishToken(type, val) { + tokEnd = tokPos; + if (options.locations) tokEndLoc = curPosition(); + var prevType = tokType, preserveSpace = false; + tokType = type; + tokVal = val; + + // Update context info + if (type === _parenR || type === _braceR) { + var out = tokContext.pop(); + if (out === b_tmpl) { + preserveSpace = true; + } else if (out === b_stat && curTokContext() === f_expr) { + tokContext.pop(); + tokExprAllowed = false; + } else { + tokExprAllowed = !(out && out.isExpr); + } + } else if (type === _braceL) { + tokContext.push(braceIsBlock(prevType) ? b_stat : b_expr); + tokExprAllowed = true; + } else if (type === _dollarBraceL) { + tokContext.push(b_tmpl); + tokExprAllowed = true; + } else if (type == _parenL) { + var statementParens = prevType === _if || prevType === _for || prevType === _with || prevType === _while; + tokContext.push(statementParens ? p_stat : p_expr); + tokExprAllowed = true; + } else if (type == _incDec) { + // tokExprAllowed stays unchanged + } else if (type.keyword && prevType == _dot) { + tokExprAllowed = false; + } else if (type == _function) { + if (curTokContext() !== b_stat) { + tokContext.push(f_expr); + } + tokExprAllowed = false; + } else if (type === _backQuote) { + if (curTokContext() === q_tmpl) { + tokContext.pop(); + } else { + tokContext.push(q_tmpl); + preserveSpace = true; + } + tokExprAllowed = false; + } else { + tokExprAllowed = type.beforeExpr; + } + + if (!preserveSpace) skipSpace(); + } + + function skipBlockComment() { + var startLoc = options.onComment && options.locations && curPosition(); + var start = tokPos, end = input.indexOf("*/", tokPos += 2); + if (end === -1) raise(tokPos - 2, "Unterminated comment"); + tokPos = end + 2; + if (options.locations) { + lineBreak.lastIndex = start; + var match; + while ((match = lineBreak.exec(input)) && match.index < tokPos) { + ++tokCurLine; + tokLineStart = match.index + match[0].length; + } + } + if (options.onComment) + options.onComment(true, input.slice(start + 2, end), start, tokPos, + startLoc, options.locations && curPosition()); + } + + function skipLineComment(startSkip) { + var start = tokPos; + var startLoc = options.onComment && options.locations && curPosition(); + var ch = input.charCodeAt(tokPos+=startSkip); + while (tokPos < inputLen && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) { + ++tokPos; + ch = input.charCodeAt(tokPos); + } + if (options.onComment) + options.onComment(false, input.slice(start + startSkip, tokPos), start, tokPos, + startLoc, options.locations && curPosition()); + } + + // Called at the start of the parse and after every token. Skips + // whitespace and comments, and. + + function skipSpace() { + while (tokPos < inputLen) { + var ch = input.charCodeAt(tokPos); + if (ch === 32) { // ' ' + ++tokPos; + } else if (ch === 13) { + ++tokPos; + var next = input.charCodeAt(tokPos); + if (next === 10) { + ++tokPos; + } + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } else if (ch === 10 || ch === 8232 || ch === 8233) { + ++tokPos; + if (options.locations) { + ++tokCurLine; + tokLineStart = tokPos; + } + } else if (ch > 8 && ch < 14) { + ++tokPos; + } else if (ch === 47) { // '/' + var next = input.charCodeAt(tokPos + 1); + if (next === 42) { // '*' + skipBlockComment(); + } else if (next === 47) { // '/' + skipLineComment(2); + } else break; + } else if (ch === 160) { // '\xa0' + ++tokPos; + } else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) { + ++tokPos; + } else { + break; + } + } + } + + // ### Token reading + + // This is the function that is called to fetch the next token. It + // is somewhat obscure, because it works in character codes rather + // than characters, and because operator parsing has been inlined + // into it. + // + // All in the name of speed. + // + function readToken_dot() { + var next = input.charCodeAt(tokPos + 1); + if (next >= 48 && next <= 57) return readNumber(true); + var next2 = input.charCodeAt(tokPos + 2); + if (options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.' + tokPos += 3; + return finishToken(_ellipsis); + } else { + ++tokPos; + return finishToken(_dot); + } + } + + function readToken_slash() { // '/' + var next = input.charCodeAt(tokPos + 1); + if (tokExprAllowed) {++tokPos; return readRegexp();} + if (next === 61) return finishOp(_assign, 2); + return finishOp(_slash, 1); + } + + function readToken_mult_modulo(code) { // '%*' + var next = input.charCodeAt(tokPos + 1); + if (next === 61) return finishOp(_assign, 2); + return finishOp(code === 42 ? _star : _modulo, 1); + } + + function readToken_pipe_amp(code) { // '|&' + var next = input.charCodeAt(tokPos + 1); + if (next === code) return finishOp(code === 124 ? _logicalOR : _logicalAND, 2); + if (next === 61) return finishOp(_assign, 2); + return finishOp(code === 124 ? _bitwiseOR : _bitwiseAND, 1); + } + + function readToken_caret() { // '^' + var next = input.charCodeAt(tokPos + 1); + if (next === 61) return finishOp(_assign, 2); + return finishOp(_bitwiseXOR, 1); + } + + function readToken_plus_min(code) { // '+-' + var next = input.charCodeAt(tokPos + 1); + if (next === code) { + if (next == 45 && input.charCodeAt(tokPos + 2) == 62 && + newline.test(input.slice(lastEnd, tokPos))) { + // A `-->` line comment + skipLineComment(3); + skipSpace(); + return readToken(); + } + return finishOp(_incDec, 2); + } + if (next === 61) return finishOp(_assign, 2); + return finishOp(_plusMin, 1); + } + + function readToken_lt_gt(code) { // '<>' + var next = input.charCodeAt(tokPos + 1); + var size = 1; + if (next === code) { + size = code === 62 && input.charCodeAt(tokPos + 2) === 62 ? 3 : 2; + if (input.charCodeAt(tokPos + size) === 61) return finishOp(_assign, size + 1); + return finishOp(_bitShift, size); + } + if (next == 33 && code == 60 && input.charCodeAt(tokPos + 2) == 45 && + input.charCodeAt(tokPos + 3) == 45) { + // `