Merge pull request #699 from chriseth/sourceLocation

WIP: Source location as part of AST.
This commit is contained in:
chriseth 2016-07-20 19:46:31 +02:00 committed by GitHub
commit 077c8ad83d
8 changed files with 365 additions and 64 deletions

View File

@ -95,6 +95,59 @@ is simplified to code which can also be compiled from
even though the instructions contained a jump in the beginning.
.. index:: source mappings
***************
Source Mappings
***************
As part of the AST output, the compiler provides the range of the source
code that is represented by the respective node in the AST. This can be
used for various purposes ranging from static analysis tools that report
errors based on the AST and debugging tools that highlight local variables
and their uses.
Furthermore, the compiler can also generate a mapping from the bytecode
to the range in the source code that generated the instruction. This is again
important for static analysis tools that operate on bytecode level and
for displaying the current position in the source code inside a debugger
or for breakpoint handling.
Both kinds of source mappings use integer indentifiers to refer to source files.
These are regular array indices into a list of source files usually called
``"sourceList"``, which is part of the combined-json and the output of
the json / npm compiler.
The source mappings inside the AST use the following
notation:
``s:l:f``
Where ``s`` is the byte-offset to the start of the range in the source file,
``l`` is the length of the source range in bytes and ``f`` is the source
index mentioned above.
The encoding in the source mapping for the bytecode is more complicated:
It is a list of ``s:l:f:j`` separated by ``;``. Each of these
elements corresponds to an instruction, i.e. you cannot use the byte offset
but have to use the instruction offset or PC (program counter).
The fields ``s``, ``l`` and ``f`` are as above and ``j`` can be either
``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a
function, returns from a function or is a regular jump as part of e.g. a loop.
In order to compress these source mappings especially for bytecode, the
following rules are used:
- If a field is empty, the value of the preceding element is used.
- If a ``:`` is missing, all following fields are considered empty.
This means the following source mappings represent the same information:
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
``1:2:1;:9;2::2;;``
.. index:: ! commandline compiler, compiler;commandline, ! solc, ! linker
.. _commandline-compiler:

View File

@ -42,12 +42,17 @@ void ASTJsonConverter::addKeyValue(Json::Value& _obj, string const& _key, string
_obj[_key] = _val;
}
void ASTJsonConverter::addJsonNode(string const& _nodeName,
initializer_list<pair<string const, string const>> _list,
bool _hasChildren = false)
void ASTJsonConverter::addJsonNode(
ASTNode const& _node,
string const& _nodeName,
initializer_list<pair<string const, string const>> _list,
bool _hasChildren = false
)
{
Json::Value node;
node["id"] = reinterpret_cast<Json::UInt64>(&_node);
node["src"] = sourceLocationToString(_node.location());
node["name"] = _nodeName;
if (_list.size() != 0)
{
@ -68,7 +73,21 @@ void ASTJsonConverter::addJsonNode(string const& _nodeName,
}
}
ASTJsonConverter::ASTJsonConverter(ASTNode const& _ast): m_ast(&_ast)
string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) const
{
int sourceIndex{-1};
if (_location.sourceName && m_sourceIndices.count(*_location.sourceName))
sourceIndex = m_sourceIndices.at(*_location.sourceName);
int length = -1;
if (_location.start >= 0 && _location.end >= 0)
length = _location.end - _location.start;
return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + std::to_string(sourceIndex);
}
ASTJsonConverter::ASTJsonConverter(
ASTNode const& _ast,
map<string, unsigned> const& _sourceIndices
): m_ast(&_ast), m_sourceIndices(_sourceIndices)
{
Json::Value children(Json::arrayValue);
@ -91,31 +110,31 @@ Json::Value const& ASTJsonConverter::json()
bool ASTJsonConverter::visit(ImportDirective const& _node)
{
addJsonNode("Import", { make_pair("file", _node.path())});
addJsonNode(_node, "Import", { make_pair("file", _node.path())});
return true;
}
bool ASTJsonConverter::visit(ContractDefinition const& _node)
{
addJsonNode("Contract", { make_pair("name", _node.name()) }, true);
addJsonNode(_node, "Contract", { make_pair("name", _node.name()) }, true);
return true;
}
bool ASTJsonConverter::visit(StructDefinition const& _node)
{
addJsonNode("Struct", { make_pair("name", _node.name()) }, true);
addJsonNode(_node, "Struct", { make_pair("name", _node.name()) }, true);
return true;
}
bool ASTJsonConverter::visit(ParameterList const&)
bool ASTJsonConverter::visit(ParameterList const& _node)
{
addJsonNode("ParameterList", {}, true);
addJsonNode(_node, "ParameterList", {}, true);
return true;
}
bool ASTJsonConverter::visit(FunctionDefinition const& _node)
{
addJsonNode("Function",
addJsonNode(_node, "Function",
{ make_pair("name", _node.name()),
make_pair("public", boost::lexical_cast<std::string>(_node.isPublic())),
make_pair("const", boost::lexical_cast<std::string>(_node.isDeclaredConst())) },
@ -125,7 +144,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
bool ASTJsonConverter::visit(VariableDeclaration const& _node)
{
addJsonNode("VariableDeclaration", {
addJsonNode(_node, "VariableDeclaration", {
make_pair("name", _node.name()),
make_pair("name", _node.name()),
}, true);
@ -139,114 +158,114 @@ bool ASTJsonConverter::visit(TypeName const&)
bool ASTJsonConverter::visit(ElementaryTypeName const& _node)
{
addJsonNode("ElementaryTypeName", { make_pair("name", _node.typeName().toString()) });
addJsonNode(_node, "ElementaryTypeName", { make_pair("name", _node.typeName().toString()) });
return true;
}
bool ASTJsonConverter::visit(UserDefinedTypeName const& _node)
{
addJsonNode("UserDefinedTypeName", {
addJsonNode(_node, "UserDefinedTypeName", {
make_pair("name", boost::algorithm::join(_node.namePath(), "."))
});
return true;
}
bool ASTJsonConverter::visit(Mapping const&)
bool ASTJsonConverter::visit(Mapping const& _node)
{
addJsonNode("Mapping", {}, true);
addJsonNode(_node, "Mapping", {}, true);
return true;
}
bool ASTJsonConverter::visit(InlineAssembly const&)
bool ASTJsonConverter::visit(InlineAssembly const& _node)
{
addJsonNode("InlineAssembly", {}, true);
addJsonNode(_node, "InlineAssembly", {}, true);
return true;
}
bool ASTJsonConverter::visit(Block const&)
bool ASTJsonConverter::visit(Block const& _node)
{
addJsonNode("Block", {}, true);
addJsonNode(_node, "Block", {}, true);
return true;
}
bool ASTJsonConverter::visit(IfStatement const&)
bool ASTJsonConverter::visit(IfStatement const& _node)
{
addJsonNode("IfStatement", {}, true);
addJsonNode(_node, "IfStatement", {}, true);
return true;
}
bool ASTJsonConverter::visit(WhileStatement const&)
bool ASTJsonConverter::visit(WhileStatement const& _node)
{
addJsonNode("WhileStatement", {}, true);
addJsonNode(_node, "WhileStatement", {}, true);
return true;
}
bool ASTJsonConverter::visit(ForStatement const&)
bool ASTJsonConverter::visit(ForStatement const& _node)
{
addJsonNode("ForStatement", {}, true);
addJsonNode(_node, "ForStatement", {}, true);
return true;
}
bool ASTJsonConverter::visit(Continue const&)
bool ASTJsonConverter::visit(Continue const& _node)
{
addJsonNode("Continue", {});
addJsonNode(_node, "Continue", {});
return true;
}
bool ASTJsonConverter::visit(Break const&)
bool ASTJsonConverter::visit(Break const& _node)
{
addJsonNode("Break", {});
addJsonNode(_node, "Break", {});
return true;
}
bool ASTJsonConverter::visit(Return const&)
bool ASTJsonConverter::visit(Return const& _node)
{
addJsonNode("Return", {}, true);;
addJsonNode(_node, "Return", {}, true);;
return true;
}
bool ASTJsonConverter::visit(Throw const&)
bool ASTJsonConverter::visit(Throw const& _node)
{
addJsonNode("Throw", {}, true);;
addJsonNode(_node, "Throw", {}, true);;
return true;
}
bool ASTJsonConverter::visit(VariableDeclarationStatement const&)
bool ASTJsonConverter::visit(VariableDeclarationStatement const& _node)
{
addJsonNode("VariableDefinition", {}, true);
addJsonNode(_node, "VariableDefinition", {}, true);
return true;
}
bool ASTJsonConverter::visit(ExpressionStatement const&)
bool ASTJsonConverter::visit(ExpressionStatement const& _node)
{
addJsonNode("ExpressionStatement", {}, true);
addJsonNode(_node, "ExpressionStatement", {}, true);
return true;
}
bool ASTJsonConverter::visit(Conditional const&)
bool ASTJsonConverter::visit(Conditional const& _node)
{
addJsonNode("Conditional", {}, true);
addJsonNode(_node, "Conditional", {}, true);
return true;
}
bool ASTJsonConverter::visit(Assignment const& _node)
{
addJsonNode("Assignment",
addJsonNode(_node, "Assignment",
{ make_pair("operator", Token::toString(_node.assignmentOperator())),
make_pair("type", type(_node)) },
true);
return true;
}
bool ASTJsonConverter::visit(TupleExpression const&)
bool ASTJsonConverter::visit(TupleExpression const& _node)
{
addJsonNode("TupleExpression",{}, true);
addJsonNode(_node, "TupleExpression",{}, true);
return true;
}
bool ASTJsonConverter::visit(UnaryOperation const& _node)
{
addJsonNode("UnaryOperation",
addJsonNode(_node, "UnaryOperation",
{ make_pair("prefix", boost::lexical_cast<std::string>(_node.isPrefixOperation())),
make_pair("operator", Token::toString(_node.getOperator())),
make_pair("type", type(_node)) },
@ -256,7 +275,7 @@ bool ASTJsonConverter::visit(UnaryOperation const& _node)
bool ASTJsonConverter::visit(BinaryOperation const& _node)
{
addJsonNode("BinaryOperation", {
addJsonNode(_node, "BinaryOperation", {
make_pair("operator", Token::toString(_node.getOperator())),
make_pair("type", type(_node))
}, true);
@ -265,7 +284,7 @@ bool ASTJsonConverter::visit(BinaryOperation const& _node)
bool ASTJsonConverter::visit(FunctionCall const& _node)
{
addJsonNode("FunctionCall", {
addJsonNode(_node, "FunctionCall", {
make_pair("type_conversion", boost::lexical_cast<std::string>(_node.annotation().isTypeConversion)),
make_pair("type", type(_node))
}, true);
@ -274,13 +293,13 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
bool ASTJsonConverter::visit(NewExpression const& _node)
{
addJsonNode("NewExpression", { make_pair("type", type(_node)) }, true);
addJsonNode(_node, "NewExpression", { make_pair("type", type(_node)) }, true);
return true;
}
bool ASTJsonConverter::visit(MemberAccess const& _node)
{
addJsonNode("MemberAccess",
addJsonNode(_node, "MemberAccess",
{ make_pair("member_name", _node.memberName()),
make_pair("type", type(_node)) },
true);
@ -289,20 +308,20 @@ bool ASTJsonConverter::visit(MemberAccess const& _node)
bool ASTJsonConverter::visit(IndexAccess const& _node)
{
addJsonNode("IndexAccess", { make_pair("type", type(_node)) }, true);
addJsonNode(_node, "IndexAccess", { make_pair("type", type(_node)) }, true);
return true;
}
bool ASTJsonConverter::visit(Identifier const& _node)
{
addJsonNode("Identifier",
addJsonNode(_node, "Identifier",
{ make_pair("value", _node.name()), make_pair("type", type(_node)) });
return true;
}
bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node)
{
addJsonNode("ElementaryTypenameExpression",
addJsonNode(_node, "ElementaryTypenameExpression",
{ make_pair("value", _node.typeName().toString()), make_pair("type", type(_node)) });
return true;
}
@ -310,7 +329,7 @@ bool ASTJsonConverter::visit(ElementaryTypeNameExpression const& _node)
bool ASTJsonConverter::visit(Literal const& _node)
{
char const* tokenString = Token::toString(_node.token());
addJsonNode("Literal",
addJsonNode(_node, "Literal",
{ make_pair("string", (tokenString) ? tokenString : "null"),
make_pair("value", _node.value()),
make_pair("type", type(_node)) });

View File

@ -42,7 +42,11 @@ class ASTJsonConverter: public ASTConstVisitor
{
public:
/// Create a converter to JSON for the given abstract syntax tree.
explicit ASTJsonConverter(ASTNode const& _ast);
/// @a _sourceIndices is used to abbreviate source names in source locations.
explicit ASTJsonConverter(
ASTNode const& _ast,
std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>()
);
/// Output the json representation of the AST to _stream.
void print(std::ostream& _stream);
Json::Value const& json();
@ -118,9 +122,13 @@ public:
private:
void process();
void addKeyValue(Json::Value& _obj, std::string const& _key, std::string const& _val);
void addJsonNode(std::string const& _nodeName,
std::initializer_list<std::pair<std::string const, std::string const>> _list,
bool _hasChildren);
void addJsonNode(
ASTNode const& _node,
std::string const& _nodeName,
std::initializer_list<std::pair<std::string const, std::string const>> _list,
bool _hasChildren
);
std::string sourceLocationToString(SourceLocation const& _location) const;
std::string type(Expression const& _expression);
std::string type(VariableDeclaration const& _varDecl);
inline void goUp()
@ -132,8 +140,8 @@ private:
bool processed = false;
Json::Value m_astJson;
std::stack<Json::Value*> m_jsonNodePtrs;
std::string m_source;
ASTNode const* m_ast;
std::map<std::string, unsigned> const& m_sourceIndices;
};
}

View File

@ -279,6 +279,28 @@ eth::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _con
return currentContract.compiler ? &contract(_contractName).compiler->runtimeAssemblyItems() : nullptr;
}
string const* CompilerStack::sourceMapping(string const& _contractName) const
{
Contract const& c = contract(_contractName);
if (!c.sourceMapping)
{
if (auto items = assemblyItems(_contractName))
c.sourceMapping.reset(new string(computeSourceMapping(*items)));
}
return c.sourceMapping.get();
}
string const* CompilerStack::runtimeSourceMapping(string const& _contractName) const
{
Contract const& c = contract(_contractName);
if (!c.runtimeSourceMapping)
{
if (auto items = runtimeAssemblyItems(_contractName))
c.runtimeSourceMapping.reset(new string(computeSourceMapping(*items)));
}
return c.runtimeSourceMapping.get();
}
eth::LinkerObject const& CompilerStack::object(string const& _contractName) const
{
return contract(_contractName).object;
@ -315,6 +337,22 @@ Json::Value CompilerStack::streamAssembly(ostream& _outStream, string const& _co
}
}
vector<string> CompilerStack::sourceNames() const
{
vector<string> names;
for (auto const& s: m_sources)
names.push_back(s.first);
return names;
}
map<string, unsigned> CompilerStack::sourceIndices() const
{
map<string, unsigned> indices;
for (auto const& s: m_sources)
indices[s.first] = indices.size();
return indices;
}
string const& CompilerStack::interface(string const& _contractName) const
{
return metadata(_contractName, DocumentationType::ABIInterface);
@ -604,3 +642,76 @@ CompilerStack::Source const& CompilerStack::source(string const& _sourceName) co
return it->second;
}
string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) const
{
string ret;
map<string, unsigned> sourceIndicesMap = sourceIndices();
int prevStart = -1;
int prevLength = -1;
int prevSourceIndex = -1;
char prevJump = 0;
for (auto const& item: _items)
{
if (!ret.empty())
ret += ";";
SourceLocation const& location = item.location();
int length = location.start != -1 && location.end != -1 ? location.end - location.start : -1;
int sourceIndex =
location.sourceName && sourceIndicesMap.count(*location.sourceName) ?
sourceIndicesMap.at(*location.sourceName) :
-1;
char jump = '-';
if (item.getJumpType() == eth::AssemblyItem::JumpType::IntoFunction)
jump = 'i';
else if (item.getJumpType() == eth::AssemblyItem::JumpType::OutOfFunction)
jump = 'o';
unsigned components = 4;
if (jump == prevJump)
{
components--;
if (sourceIndex == prevSourceIndex)
{
components--;
if (length == prevLength)
{
components--;
if (location.start == prevStart)
components--;
}
}
}
if (components-- > 0)
{
if (location.start != prevStart)
ret += std::to_string(location.start);
if (components-- > 0)
{
ret += ':';
if (length != prevLength)
ret += std::to_string(length);
if (components-- > 0)
{
ret += ':';
if (sourceIndex != prevSourceIndex)
ret += std::to_string(sourceIndex);
if (components-- > 0)
{
ret += ':';
if (jump != prevJump)
ret += jump;
}
}
}
}
prevStart = location.start;
prevLength = length;
prevSourceIndex = sourceIndex;
prevJump = jump;
}
return ret;
}

View File

@ -142,6 +142,12 @@ public:
eth::AssemblyItems const* assemblyItems(std::string const& _contractName = "") const;
/// @returns runtime contract assembly items
eth::AssemblyItems const* runtimeAssemblyItems(std::string const& _contractName = "") const;
/// @returns the string that provides a mapping between bytecode and sourcecode or a nullptr
/// if the contract does not (yet) have bytecode.
std::string const* sourceMapping(std::string const& _contractName = "") const;
/// @returns the string that provides a mapping between runtime bytecode and sourcecode.
/// if the contract does not (yet) have bytecode.
std::string const* runtimeSourceMapping(std::string const& _contractName = "") const;
/// @returns hash of the runtime bytecode for the contract, i.e. the code that is
/// returned by the constructor or the zero-h256 if the contract still needs to be linked or
/// does not have runtime code.
@ -153,6 +159,11 @@ public:
/// Prerequisite: Successful compilation.
Json::Value streamAssembly(std::ostream& _outStream, std::string const& _contractName = "", StringMap _sourceCodes = StringMap(), bool _inJsonFormat = false) const;
/// @returns the list of sources (paths) used
std::vector<std::string> sourceNames() const;
/// @returns a mapping assigning each source name its index inside the vector returned
/// by sourceNames().
std::map<std::string, unsigned> sourceIndices() const;
/// @returns a string representing the contract interface in JSON.
/// Prerequisite: Successful call to parse or compile.
std::string const& interface(std::string const& _contractName = "") const;
@ -196,9 +207,8 @@ private:
{
std::shared_ptr<Scanner> scanner;
std::shared_ptr<SourceUnit> ast;
std::string interface;
bool isLibrary = false;
void reset() { scanner.reset(); ast.reset(); interface.clear(); }
void reset() { scanner.reset(); ast.reset(); }
};
struct Contract
@ -212,6 +222,8 @@ private:
mutable std::unique_ptr<std::string const> solidityInterface;
mutable std::unique_ptr<std::string const> userDocumentation;
mutable std::unique_ptr<std::string const> devDocumentation;
mutable std::unique_ptr<std::string const> sourceMapping;
mutable std::unique_ptr<std::string const> runtimeSourceMapping;
};
/// Loads the missing sources from @a _ast (named @a _path) using the callback
@ -236,6 +248,8 @@ private:
Contract const& contract(std::string const& _contractName = "") const;
Source const& source(std::string const& _sourceName = "") const;
std::string computeSourceMapping(eth::AssemblyItems const& _items) const;
struct Remapping
{
std::string context;

View File

@ -86,6 +86,8 @@ static set<string> const g_combinedJsonArgs{
"bin",
"bin-runtime",
"clone-bin",
"srcmap",
"srcmap-runtime",
"opcodes",
"abi",
"interface",
@ -658,6 +660,16 @@ void CommandLineInterface::handleCombinedJSON()
ostringstream unused;
contractData["asm"] = m_compiler->streamAssembly(unused, contractName, m_sourceCodes, true);
}
if (requests.count("srcmap"))
{
auto map = m_compiler->sourceMapping(contractName);
contractData["srcmap"] = map ? *map : "";
}
if (requests.count("srcmap-runtime"))
{
auto map = m_compiler->runtimeSourceMapping(contractName);
contractData["srcmap"] = map ? *map : "";
}
if (requests.count("devdoc"))
contractData["devdoc"] = m_compiler->metadata(contractName, DocumentationType::NatspecDev);
if (requests.count("userdoc"))
@ -665,12 +677,22 @@ void CommandLineInterface::handleCombinedJSON()
output["contracts"][contractName] = contractData;
}
bool needsSourceList = requests.count("ast") || requests.count("srcmap") || requests.count("srcmap-runtime");
if (needsSourceList)
{
// Indices into this array are used to abbreviate source names in source locations.
output["sourceList"] = Json::Value(Json::arrayValue);
for (auto const& source: m_compiler->sourceNames())
output["sourceList"].append(source);
}
if (requests.count("ast"))
{
output["sources"] = Json::Value(Json::objectValue);
for (auto const& sourceCode: m_sourceCodes)
{
ASTJsonConverter converter(m_compiler->ast(sourceCode.first));
ASTJsonConverter converter(m_compiler->ast(sourceCode.first), m_compiler->sourceIndices());
output["sources"][sourceCode.first] = Json::Value(Json::objectValue);
output["sources"][sourceCode.first]["AST"] = converter.json();
}

View File

@ -214,6 +214,10 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback
contractData["opcodes"] = solidity::disassemble(compiler.object(contractName).bytecode);
contractData["functionHashes"] = functionHashes(compiler.contractDefinition(contractName));
contractData["gasEstimates"] = estimateGas(compiler, contractName);
auto sourceMap = compiler.sourceMapping(contractName);
contractData["srcmap"] = sourceMap ? *sourceMap : "";
auto runtimeSourceMap = compiler.sourceMapping(contractName);
contractData["srcmap-runtime"] = runtimeSourceMap ? *runtimeSourceMap : "";
ostringstream unused;
contractData["assembly"] = compiler.streamAssembly(unused, contractName, _sources, true);
output["contracts"][contractName] = contractData;
@ -235,12 +239,13 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback
output["formal"]["errors"] = errors;
}
// Indices into this array are used to abbreviate source names in source locations.
output["sourceList"] = Json::Value(Json::arrayValue);
for (auto const& source: compiler.sourceNames())
output["sourceList"].append(source);
output["sources"] = Json::Value(Json::objectValue);
for (auto const& source: _sources)
{
output["sources"][source.first] = Json::Value(Json::objectValue);
output["sources"][source.first]["AST"] = ASTJsonConverter(compiler.ast(source.first)).json();
}
for (auto const& source: compiler.sourceNames())
output["sources"][source]["AST"] = ASTJsonConverter(compiler.ast(source), compiler.sourceIndices()).json();
}
return Json::FastWriter().write(output);

View File

@ -0,0 +1,69 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2016
* Tests for the json ast output.
*/
#include <string>
#include <boost/test/unit_test.hpp>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/ast/ASTJsonConverter.h>
using namespace std;
namespace dev
{
namespace solidity
{
namespace test
{
BOOST_AUTO_TEST_SUITE(SolidityASTJSON)
BOOST_AUTO_TEST_CASE(smoke_test)
{
CompilerStack c;
c.addSource("a", "contract C {}");
c.parse();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
BOOST_CHECK_EQUAL(astJson["name"], "root");
}
BOOST_AUTO_TEST_CASE(source_location)
{
CompilerStack c;
c.addSource("a", "contract C { function f() { var x = 2; x++; } }");
c.parse();
map<string, unsigned> sourceIndices;
sourceIndices["a"] = 1;
Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json();
BOOST_CHECK_EQUAL(astJson["name"], "root");
BOOST_CHECK_EQUAL(astJson["children"][0]["name"], "Contract");
BOOST_CHECK_EQUAL(astJson["children"][0]["children"][0]["name"], "Function");
BOOST_CHECK_EQUAL(astJson["children"][0]["children"][0]["src"], "13:32:1");
}
BOOST_AUTO_TEST_SUITE_END()
}
}
} // end namespaces