Allow remappings to change depending on the context.

This commit is contained in:
chriseth
2016-06-08 18:16:46 +02:00
parent 63b6305689
commit 3150ab2bcf
8 changed files with 185 additions and 94 deletions
+66 -13
View File
@@ -63,6 +63,24 @@ CompilerStack::CompilerStack(bool _addStandardSources, ReadFileCallback const& _
addSources(StandardSources, true); // add them as libraries
}
void CompilerStack::setRemappings(vector<string> const& _remappings)
{
vector<Remapping> remappings;
for (auto const& remapping: _remappings)
{
auto eq = find(remapping.begin(), remapping.end(), '=');
if (eq == remapping.end())
continue; // ignore
auto colon = find(remapping.begin(), eq, ':');
Remapping r;
r.context = colon == eq ? string() : string(remapping.begin(), colon);
r.prefix = colon == eq ? string(remapping.begin(), eq) : string(colon + 1, eq);
r.target = string(eq + 1, remapping.end());
remappings.push_back(r);
}
swap(m_remappings, remappings);
}
void CompilerStack::reset(bool _keepSources, bool _addStandardSources)
{
m_parseSuccessful = false;
@@ -384,37 +402,72 @@ 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 CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath)
{
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))
string importPath = absolutePath(import->path(), _sourcePath);
// The current value of `path` is the absolute path as seen from this source file.
// We first have to apply remappings before we can store the actual absolute path
// as seen globally.
importPath = applyRemapping(importPath, _sourcePath);
import->annotation().absolutePath = importPath;
if (m_sources.count(importPath) || newSources.count(importPath))
continue;
string contents;
string errorMessage;
if (!m_readFile)
errorMessage = "File not supplied initially.";
ReadFileResult result{false, string("File not supplied initially.")};
if (m_readFile)
result = m_readFile(importPath);
if (result.success)
newSources[importPath] = result.contentsOrErrorMesage;
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);
errinfo_comment("Source \"" + importPath + "\" not found: " + result.contentsOrErrorMesage);
m_errors.push_back(std::move(err));
continue;
}
else
newSources[path] = contents;
}
return newSources;
}
string CompilerStack::applyRemapping(string const& _path, string const& _context)
{
// Try to find the longest prefix match in all remappings that are active in the current context.
auto isPrefixOf = [](string const& _a, string const& _b)
{
if (_a.length() > _b.length())
return false;
return std::equal(_a.begin(), _a.end(), _b.begin());
};
size_t longestPrefix = 0;
string longestPrefixTarget;
for (auto const& redir: m_remappings)
{
// Skip if we already have a closer match.
if (longestPrefix > 0 && redir.prefix.length() <= longestPrefix)
continue;
// Skip if redir.context is not a prefix of _context
if (!isPrefixOf(redir.context, _context))
continue;
// Skip if the prefix does not match.
if (!isPrefixOf(redir.prefix, _path))
continue;
longestPrefix = redir.prefix.length();
longestPrefixTarget = redir.target;
}
string path = longestPrefixTarget;
path.append(_path.begin() + longestPrefix, _path.end());
return path;
}
void CompilerStack::resolveImports()
{
// topological sorting (depth first search) of the import graph, cutting potential cycles
+22 -3
View File
@@ -75,15 +75,23 @@ enum class DocumentationType: uint8_t
class CompilerStack: boost::noncopyable
{
public:
/// 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&)>;
struct ReadFileResult
{
bool success;
std::string contentsOrErrorMesage;
};
/// File reading callback.
using ReadFileCallback = std::function<ReadFileResult(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());
/// Sets path remappings in the format "context:prefix=target"
void setRemappings(std::vector<std::string> const& _remappings);
/// Resets the compiler to a state where the sources are not parsed or even removed.
void reset(bool _keepSources = false, bool _addStandardSources = true);
@@ -209,6 +217,7 @@ private:
/// @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);
std::string applyRemapping(std::string const& _path, std::string const& _context);
void resolveImports();
/// Checks whether there are libraries with the same name, reports that as an error and
/// @returns false in this case.
@@ -226,7 +235,17 @@ private:
Contract const& contract(std::string const& _contractName = "") const;
Source const& source(std::string const& _sourceName = "") const;
struct Remapping
{
std::string context;
std::string prefix;
std::string target;
};
ReadFileCallback m_readFile;
/// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum
/// "context:prefix=target"
std::vector<Remapping> m_remappings;
bool m_parseSuccessful;
std::map<std::string const, Source> m_sources;
std::shared_ptr<GlobalContext> m_globalContext;