From c55584d3e2da49674993972a129ef478ba6b4914 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 1 Jul 2016 10:14:50 +0200 Subject: Source location as part of AST. --- libsolidity/ast/ASTJsonConverter.cpp | 121 ++++++++++++++++++-------------- libsolidity/ast/ASTJsonConverter.h | 18 +++-- libsolidity/interface/CompilerStack.cpp | 111 +++++++++++++++++++++++++++++ libsolidity/interface/CompilerStack.h | 18 ++++- solc/CommandLineInterface.cpp | 24 ++++++- solc/jsonCompiler.cpp | 15 ++-- test/libsolidity/ASTJSON.cpp | 69 ++++++++++++++++++ 7 files changed, 312 insertions(+), 64 deletions(-) create mode 100644 test/libsolidity/ASTJSON.cpp diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 89d0bf35..6b459da4 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -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> _list, - bool _hasChildren = false) +void ASTJsonConverter::addJsonNode( + ASTNode const& _node, + string const& _nodeName, + initializer_list> _list, + bool _hasChildren = false +) { Json::Value node; + node["id"] = reinterpret_cast(&_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 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(_node.isPublic())), make_pair("const", boost::lexical_cast(_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(_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(_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)) }); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 91ee72e1..2e3046f1 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -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 const& _sourceIndices = std::map() + ); /// 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> _list, - bool _hasChildren); + void addJsonNode( + ASTNode const& _node, + std::string const& _nodeName, + std::initializer_list> _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 m_jsonNodePtrs; - std::string m_source; ASTNode const* m_ast; + std::map const& m_sourceIndices; }; } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 4776a4ce..f7982872 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -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 CompilerStack::sourceNames() const +{ + vector names; + for (auto const& s: m_sources) + names.push_back(s.first); + return names; +} + +map CompilerStack::sourceIndices() const +{ + map 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 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; +} diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 9d2aace4..a4b8447f 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -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 sourceNames() const; + /// @returns a mapping assigning each source name its index inside the vector returned + /// by sourceNames(). + std::map 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; std::shared_ptr 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 solidityInterface; mutable std::unique_ptr userDocumentation; mutable std::unique_ptr devDocumentation; + mutable std::unique_ptr sourceMapping; + mutable std::unique_ptr 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; diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 09c7c8e8..ac8db160 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -86,6 +86,8 @@ static set 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(); } diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index bc1305c5..8fc42d73 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -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); diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp new file mode 100644 index 00000000..6d914391 --- /dev/null +++ b/test/libsolidity/ASTJSON.cpp @@ -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 . +*/ +/** + * @author Christian + * @date 2016 + * Tests for the json ast output. + */ + +#include +#include +#include +#include +#include + +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 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 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 -- cgit