Merge pull request #351 from chriseth/autoload

Automatically load imported files in solc.
This commit is contained in:
chriseth 2016-01-19 13:14:25 +01:00
commit d21c4276b3
3 changed files with 88 additions and 32 deletions

View File

@ -38,11 +38,8 @@
#include <libdevcore/SHA3.h>
using namespace std;
namespace dev
{
namespace solidity
{
using namespace dev;
using namespace dev::solidity;
const map<string, string> StandardSources = map<string, string>{
{"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(bytes3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"},
@ -58,8 +55,8 @@ const map<string, string> StandardSources = map<string, string>{
{"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"}
};
CompilerStack::CompilerStack(bool _addStandardSources):
m_parseSuccessful(false)
CompilerStack::CompilerStack(bool _addStandardSources, ReadFileCallback const& _readFile):
m_readFile(_readFile), m_parseSuccessful(false)
{
if (_addStandardSources)
addSources(StandardSources, true); // add them as libraries
@ -104,16 +101,30 @@ bool CompilerStack::parse()
m_errors.clear();
m_parseSuccessful = false;
vector<string> sourcesToParse;
for (auto const& s: m_sources)
sourcesToParse.push_back(s.first);
map<string, SourceUnit const*> sourceUnitsByName;
for (auto& sourcePair: m_sources)
for (size_t i = 0; i < sourcesToParse.size(); ++i)
{
sourcePair.second.scanner->reset();
sourcePair.second.ast = Parser(m_errors).parse(sourcePair.second.scanner);
if (!sourcePair.second.ast)
string const& path = sourcesToParse[i];
Source& source = m_sources[path];
source.scanner->reset();
source.ast = Parser(m_errors).parse(source.scanner);
sourceUnitsByName[path] = source.ast.get();
if (!source.ast)
solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error.");
else
sourcePair.second.ast->annotation().path = sourcePair.first;
sourceUnitsByName[sourcePair.first] = sourcePair.second.ast.get();
{
source.ast->annotation().path = path;
for (auto const& newSource: loadMissingSources(*source.ast, path))
{
string const& newPath = newSource.first;
string const& newContents = newSource.second;
m_sources[newPath].scanner = make_shared<Scanner>(CharStream(newContents), newPath);
sourcesToParse.push_back(newPath);
}
}
}
if (!Error::containsOnlyWarnings(m_errors))
// errors while parsing. sould stop before type checking
@ -367,13 +378,44 @@ tuple<int, int, int, int> CompilerStack::positionFromSourceLocation(SourceLocati
return make_tuple(++startLine, ++startColumn, ++endLine, ++endColumn);
}
StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _path)
{
StringMap newSources;
for (auto const& node: _ast.nodes())
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
{
string path = absolutePath(import->path(), _path);
import->annotation().absolutePath = path;
if (m_sources.count(path) || newSources.count(path))
continue;
string contents;
string errorMessage;
if (!m_readFile)
errorMessage = "File not supplied initially.";
else
tie(contents, errorMessage) = m_readFile(path);
if (!errorMessage.empty())
{
auto err = make_shared<Error>(Error::Type::ParserError);
*err <<
errinfo_sourceLocation(import->location()) <<
errinfo_comment("Source not found: " + errorMessage);
m_errors.push_back(std::move(err));
continue;
}
else
newSources[path] = contents;
}
return newSources;
}
void CompilerStack::resolveImports()
{
// topological sorting (depth first search) of the import graph, cutting potential cycles
vector<Source const*> sourceOrder;
set<Source const*> sourcesSeen;
function<void(string const&, Source const*)> toposort = [&](string const& _sourceName, Source const* _source)
function<void(Source const*)> toposort = [&](Source const* _source)
{
if (sourcesSeen.count(_source))
return;
@ -381,24 +423,18 @@ void CompilerStack::resolveImports()
for (ASTPointer<ASTNode> const& node: _source->ast->nodes())
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
{
string path = absolutePath(import->path(), _sourceName);
import->annotation().absolutePath = path;
if (!m_sources.count(path))
BOOST_THROW_EXCEPTION(
Error(Error::Type::ParserError)
<< errinfo_sourceLocation(import->location())
<< errinfo_comment("Source not found.")
);
import->annotation().sourceUnit = m_sources.at(path).ast.get();
toposort(path, &m_sources[path]);
string const& path = import->annotation().absolutePath;
solAssert(!path.empty(), "");
solAssert(m_sources.count(path), "");
import->annotation().sourceUnit = m_sources[path].ast.get();
toposort(&m_sources[path]);
}
sourceOrder.push_back(_source);
};
for (auto const& sourcePair: m_sources)
if (!sourcePair.second.isLibrary)
toposort(sourcePair.first, &sourcePair.second);
toposort(&sourcePair.second);
swap(m_sourceOrder, sourceOrder);
}
@ -507,6 +543,3 @@ CompilerStack::Source const& CompilerStack::source(string const& _sourceName) co
return it->second;
}
}
}

View File

@ -27,6 +27,7 @@
#include <string>
#include <memory>
#include <vector>
#include <functional>
#include <boost/noncopyable.hpp>
#include <json/json.h>
#include <libdevcore/Common.h>
@ -74,8 +75,14 @@ enum class DocumentationType: uint8_t
class CompilerStack: boost::noncopyable
{
public:
/// Creates a new compiler stack. Adds standard sources if @a _addStandardSources.
explicit CompilerStack(bool _addStandardSources = true);
/// File reading callback, should return a pair of content and error message (exactly one nonempty)
/// for a given path.
using ReadFileCallback = std::function<std::pair<std::string, std::string>(std::string const&)>;
/// Creates a new compiler stack.
/// @param _readFile callback to used to read files for import statements. Should return
/// @param _addStandardSources Adds standard sources if @a _addStandardSources.
explicit CompilerStack(bool _addStandardSources = true, ReadFileCallback const& _readFile = ReadFileCallback());
/// Resets the compiler to a state where the sources are not parsed or even removed.
void reset(bool _keepSources = false, bool _addStandardSources = true);
@ -198,6 +205,10 @@ private:
mutable std::unique_ptr<std::string const> devDocumentation;
};
/// Loads the missing sources from @a _ast (named @a _path) using the callback
/// @a m_readFile and stores the absolute paths of all imports in the AST annotations.
/// @returns the newly loaded sources.
StringMap loadMissingSources(SourceUnit const& _ast, std::string const& _path);
void resolveImports();
/// Checks whether there are libraries with the same name, reports that as an error and
/// @returns false in this case.
@ -215,6 +226,7 @@ private:
Contract const& contract(std::string const& _contractName = "") const;
Source const& source(std::string const& _sourceName = "") const;
ReadFileCallback m_readFile;
bool m_parseSuccessful;
std::map<std::string const, Source> m_sources;
std::shared_ptr<GlobalContext> m_globalContext;

View File

@ -497,7 +497,18 @@ bool CommandLineInterface::processInput()
return link();
}
m_compiler.reset(new CompilerStack(m_args.count(g_argAddStandard) > 0));
function<pair<string,string>(string const&)> fileReader = [this](string const& _path)
{
auto path = boost::filesystem::path(_path);
if (!boost::filesystem::exists(path))
return make_pair(string(), string("File not found."));
else if (!boost::filesystem::is_regular_file(path))
return make_pair(string(), string("Not a valid file."));
else
return make_pair(m_sourceCodes[_path] = dev::contentsString(_path), string());
};
m_compiler.reset(new CompilerStack(m_args.count(g_argAddStandard) > 0, fileReader));
try
{
for (auto const& sourceCode: m_sourceCodes)