How can I prevent boost log to duplicate message - c++

I have code where I log different categories of information. Each piece of code has a specific tag that has been made for them. (e.g : database.message, database.odbc, event.cevent ...)
Moreover, I've made a function that reads a json file and their value which corresponds to their according severity filter
{
"database" :
{
"message": "INFO",
"odbc": "DEBUG",
},
"event" :
{
"cevent" : "INFO",
},
}
My problem is, I want to set a "basic filter", (for instance only log messages that are "INFO" or above) for all items whose tags were not set in this file.
Right now, I'm adding filters this way:
logging::core::get()->add_global_attribute("Tag", attrs::constant<std::string>(""));
logging::add_common_attributes();
std::vector<std::string> tags; // Suppose it's already filled with the tags and values from the json
...
for (const auto& tag :tags)
{
boost::shared_ptr<text_sink> sink(new text_sink(backend)); // same as the doc
auto level = logging::trivial::info; // just an example for more clarity
sink->set_filter(expr::attr<std::string>("Tag") == tag && expr::attr<logging::trivial::severity_level>("Severity") >= level);
logging::core::get()->add_sink(sink);
}
This piece of code works and correctly reads and sets filter according to the json file.
So to add this "basic filter", I also added this once every filter has been set:
boost::shared_ptr<text_sink> basic_sink(new text_sink(backend));
auto filter = logging::trivial::severity >= logging::trivial::info;
for (const auto& tag : tags)
{
filter = filter && expr::attr<std::string>("Tag") != tag;
}
basic_sink->set_filter(filter);
logging::core::get()->add_sink(basic_sink);
But it duplicates messages that are defined in the json, when I thought this would filter out tags stored. Do you have any ideas on how to avoid such duplication or do I have to implement such a sink as mentionned in this post

You weren't duplicating messages. You were adding duplicate sinks.
As someone else posted, you want to combine into one filter instead of duplicating your sinks.
However, since the filter expression is a compile-time static template expression that describes a deferred invocation, you need a deferred function to work with it.
I'd use boost::phoenix::function to make it simple:
boost::phoenix::function matching = [tags](logging::value_ref<std::string> actual) {
return tags.contains(actual.get());
};
Now you can use the single filter expression:
auto filter = matching(_tag) && (logging::trivial::severity >= level);
C++ 11
The above assumed C++17, but you can spell it out for older beasts:
struct match_impl {
std::set<std::string> target_tags;
using result_type = bool;
result_type operator()(logging::value_ref<std::string> actual_tag) const {
return 0 < target_tags.count(actual_tag.get());
}
};
boost::phoenix::function<match_impl> matching{match_impl{std::move(tags)}};
If Boost Log supports C++03 I know how to get that done too, but hopefully that's not required.
Full Demo
Live On Coliru
#include <boost/log/sinks/text_file_backend.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>
#include <boost/phoenix.hpp>
#include <set>
namespace logging = boost::log;
namespace sinks = logging::sinks;
namespace attrs = logging::attributes;
void init_logging(logging::trivial::severity_level level, std::set<std::string> tags) {
auto core = logging::core::get();
using text_sink = sinks::synchronous_sink<sinks::text_ostream_backend>;
core->add_global_attribute("Tag", attrs::constant<std::string>(""));
logging::add_common_attributes();
auto backend = boost::make_shared<sinks::text_ostream_backend>();
auto sink = boost::make_shared<text_sink>(backend);
sink->locked_backend()->add_stream(
boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter{}));
#if __cplusplus < 201703L
struct match_impl {
std::set<std::string> target_tags;
using result_type = bool;
result_type operator()(logging::value_ref<std::string> actual_tag) const {
return 0 < target_tags.count(actual_tag.get());
}
};
boost::phoenix::function<match_impl> matching{match_impl{std::move(tags)}};
#else
boost::phoenix::function matching = [tags](logging::value_ref<std::string> actual) {
return tags.contains(actual.get());
};
#endif
auto _tag = boost::log::expressions::attr<std::string>("Tag");
auto filter = matching(_tag) && (logging::trivial::severity >= level);
sink->set_filter(filter);
core->add_sink(sink);
}
int main() {
init_logging(logging::trivial::severity_level::error, {"foo", "bar", "qux"});
for (std::string tag : {"foo", "bogus", "bar"}) {
BOOST_LOG_SCOPED_THREAD_ATTR("Tag", attrs::constant<std::string>(tag));
BOOST_LOG_TRIVIAL(debug) << "debug tagged with " << tag;
BOOST_LOG_TRIVIAL(error) << "error tagged with " << tag;
BOOST_LOG_TRIVIAL(fatal) << "fatal tagged with " << tag;
BOOST_LOG_TRIVIAL(info) << "info tagged with " << tag;
BOOST_LOG_TRIVIAL(trace) << "trace tagged with " << tag;
BOOST_LOG_TRIVIAL(warning) << "warning tagged with " << tag;
}
}
Prints the expected
error tagged with foo
fatal tagged with foo
error tagged with bar
fatal tagged with bar

Related

Error code to string? Struct to json with error_message

Im developing a program in C++ that returns info from a DLL to be used in a webpage.
The DLL returns a big struct with information but only need some fields that i plan to return as a json using https://github.com/nlohmann/json and then to char*.
Here is an example of the struct and the meaning of the values of each field (acording to the documentation pdf)
struct myStruct {
BYTE StatusCode;
BYTE ErrorCode;
DWORD WarningCode[2];
otherStruct SystemInfo[16];
...
}
StatusCode:
0x00 = No Error
0x01 = Error
0x02 = Ready
...
0x05 = Power Off
WarningCode
0x00 0x00 = No warning
0x02 0x01 = Warning Alert
... etc
Here is how i access the fields of the struct:
GetInfoStatus(&myStatusStruct);
jInfo["error_code"] = myStatusStruct.ErrorCode;
jInfo["status_code"] = myStatusStruct.StatusCode;
jInfo["warning_code"] = myStatusStruct.WarningCode2;
jInfo["is_available_warning_code"] = myStatusStruct.AvailableWarningCode2;
std::string info = jInfo.dump();
return info.c_str();
// My current return char* "json"
// {"available_warning_code":1,"error_code":255,"status_code":4}
But i would like to have something like this
{"available_warning_code": [0x01, "warning_alert"], "error_code": [0x01, "error_system_fail"], "status_code": [0x04, "low_battery"]}
Or similar so i can return also an error code to a "string" or "error_message" that indicates the meaning (a traslation) so my backend/frontend (NodeJS) later can detect "low_battery" and do something about it, instead of having to match 0x04 to a table to understand a 0x04 (that is different from other 0x04 in other key)
Ive checked this solution https://stackoverflow.com/a/208003/4620644 but still dont understand if is the best for my case and how to implement it. I have like 20 error codes, 10 warning codes, 15 status codes.
You could create a std:pair and use that in the json. Somewhere, though, you are going to have to type out all the error messages.
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include "json.h"
using namespace nlohmann;
std::pair<int, std::string> make_error(int error)
{
// Use a vector if error codes are sequential
// Otherwise, maybe a switch
std::vector<std::string> error_msgs = {
"No Error", "Error", "Ready"
};
if (error >= 0 && error < error_msgs.size()) {
return std::make_pair(error, error_msgs[error]);
}
else {
return std::make_pair(error, "Unknown");
}
}
int main()
{
json jInfo;
jInfo["error_code"] = make_error(2);
std::cout << jInfo.dump();
return 0;
}
This outputs:
{"error_code":[2,"Ready"]}
You'll have to do this for the other fields as well.
To get error string
class CodeMap {
map<pair<int, int>, string> m_warningCodes {
{make_pair(0,0), "No warning"},
{make_pair(2,1), "Warning Alert"}
};
map<int, string> m_statusCode{
{0, "No Error"},
{1, "Error"},
{2, "Ready"},
{5, "Power Off"},
};
public:
std::string GetWarningCode(int code[]){
return m_warningCodes[make_pair(code[0], code[1])];
}
std::string GetStatusCode(int code){
return m_statusCode[code];
}
};
Hexadecimal in json do not comply to RFC 7159
https://github.com/nlohmann/json/issues/249
Approach 1: Hex as string
To get hex from int type
//with c++ 20 std::format can be used instead below function
std::string GetHex(int i) {
std::stringstream stream;
stream << "0x" <<std::hex << i;
return stream.str();
}
Assign key value pair to json field
CodeMap m; //To get message string
jInfo["error_code"] = make_pair(GetHex(myStatusStruct.ErrorCode), "error_system_fail");
jInfo["status_code"] = make_pair(GetHex(myStatusStruct.StatusCode), m.GetStatusCode(myStatusStruct.StatusCode));
output:
"error_code": ["0x01", "error_system_fail"], "status_code": ["0x04", "low_battery"]
Approach 2: Hex as integer
Assign key value pair to json field
CodeMap m; //To get message string
jInfo["error_code"] = make_pair(myStatusStruct.ErrorCode, "error_system_fail");
jInfo["status_code"] = make_pair(myStatusStruct.StatusCode, m.GetStatusCode(myStatusStruct.StatusCode));
output:
"error_code": [1, "error_system_fail"], "status_code": [4, "low_battery"]

How to capture the value parsed by a boost::spirit::x3 parser to be used within the body of a semantic action?

I have a parser for string literals, and I'd like to attach a semantic action to the parser that will manipulate the parsed value. It seems that boost::spirit::x3::_val() returns a reference to the parsed value when given the context, but for some reason the parsed string always enters the body of the semantic action as just an empty string, which obviously makes it difficult to read from it. It is the right string though, I've made sure by checking the addresses. Anyone know how I could have a reference to the parsed value within the semantic action attached to the parser? This here is the parser I currently use:
x3::lexeme[quote > *("\\\"" >> x3::attr('\"') | ~x3::char_(quote)) > quote]
And I'd like to add the semantic action to the end of it. Thank you in advance!
EDIT: it seems that whenever I attach any semantic action in general to the parser, the value is nullified. I suppose the question now is how could I access the value before that happens? I just need to be able to manipulate the parsed string before it is given to the AST.
In X3, semantic actions are much simpler. They're unary callables that take just the context.
Then you use free functions to extract information from the context:
x3::_val(ctx) is like qi::_val
x3::_attr(ctx) is like qi::_0 (or qi::_1 for simple parsers)
x3::_pass(ctx) is like qi::_pass
So, to get your semantic action, you could do:
auto qstring
= x3::rule<struct rule_type, std::string> {"qstring"}
= x3::lexeme[quote > *("\\" >> x3::char_(quote) | ~x3::char_(quote)) > quote]
;
Now to make a very odd string rule that reverses the text (after de-escaping) and requires the number of characters to be an odd-number:
auto odd_reverse = [](auto& ctx) {
auto& attr = x3::_attr(ctx);
auto& val = x3::_val(ctx);
x3::traits::move_to(attr, val);
std::reverse(val.begin(), val.end());
x3::_pass(ctx) = val.size() % 2 == 0;
};
auto odd_string
= x3::rule<struct odd_type, std::string> {"odd_string"}
= qstring [ odd_reverse ]
;
DEMO
Live On Coliru
#include <boost/spirit/home/x3.hpp>
#include <iostream>
#include <iomanip>
int main() {
namespace x3 = boost::spirit::x3;
auto constexpr quote = '"';
auto qstring
= x3::rule<struct rule_type, std::string> {"qstring"}
= x3::lexeme[quote > *("\\" >> x3::char_(quote) | ~x3::char_(quote)) > quote]
;
auto odd_reverse = [](auto& ctx) {
auto& attr = x3::_attr(ctx);
auto& val = x3::_val(ctx);
x3::traits::move_to(attr, val);
std::reverse(val.begin(), val.end());
x3::_pass(ctx) = val.size() % 2 == 0;
};
auto odd_string
= x3::rule<struct odd_type, std::string> {"odd_string"}
= qstring [ odd_reverse ]
;
for (std::string const input : {
R"("test \"hello\" world")",
R"("test \"hello\" world!")",
}) {
std::string output;
auto f = begin(input), l = end(input);
if (x3::phrase_parse(f, l, odd_string, x3::blank, output)) {
std::cout << "[" << output << "]\n";
} else {
std::cout << "Failed\n";
}
if (f != l) {
std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << "\n";
}
}
}
Printing
[dlrow "olleh" tset]
Failed
Remaining unparsed: "\"test \\\"hello\\\" world!\""
UPDATE
To the added question:
EDIT: it seems that whenever I attach any semantic action in general
to the parser, the value is nullified. I suppose the question now is
how could I access the value before that happens? I just need to be
able to manipulate the parsed string before it is given to the AST.
Yes, if you attach an action, automatic attribute propagation is inhibited. This is the same in Qi, where you could assign rules with %= instead of = to force automatic attribute propagation.
To get the same effect in X3, use the third template argument to x3::rule: x3::rule<X, T, true> to indicate you want automatic propagation.
Really, try not to fight the system. In practice, the automatic transformation system is way more sophisticated than I am willing to re-discover on my own, so I usually post-process the whole AST or at most apply some minor tweaks in an action. See also Boost Spirit: "Semantic actions are evil"?

Error handling design: Bind error messages to error codes

I have a simple structures to handle errors
enum class Error
{
None = 0,
FirstError,
SecondError,
ThirdError,
...
}
struct ErrorCategory
{
static const std::string& Message(Error aError)
{
static const std::string messages[] =
{
"first error message",
"second error message",
"third error message",
...
}
const auto index = static_cast<size_t>(aError);
assert(index < std::size(messages) && "Invalid error");
return messages[index];
}
};
Also, there are some class and methods to work with those structures.
Though it works, but the amount of errors is growing, so it becomes hard to navigate between error messages and error codes in enum. I want to declare error code and message for it in one place to make it easy to find error code. Is there a way to do it without making my code huge (such as enum + switch cases for messages) so it would be easy to maintain and without using macro?
You can use std::map<Error, std::string> like:
enum class Error : uint8_t {
None = 0,
FirstError = 1,
SecondError = 2,
ThirdError = 3
};
std::map<Error, std::string> error_messages = {
{ Error::None, "unknown error message" },
{ Error::FirstError, "first error message" },
{ Error::SecondError, "second error message" },
{ Error::ThirdError, "third error message" }
};
and then use it afterwards like:
std::cerr << error_messages[Error::None] << std::endl;
std::cerr << error_messages[Error::FirstError] << std::endl;
std::cerr << error_messages[Error::SecondError] << std::endl;
std::cerr << error_messages[Error::ThirdError] << std::endl;
Demo
This type of operation is exactly how most localized UIs work. You map some common structured ID to a user-facing string.
Because of this, there are a huge amount of platform-specific ways of doing this which aim to do exactly what you want. For example on Windows:
(Windows) FormatMessage and Message Compiler
(Windows / .NET) .resx files and ResourceManager

How to convert any value to an object and add members with boost::property_tree json

I have a program that modifies a JSON document if necessary. The program has to add a child to another value whether or not it's an already an object. The program should behave like so:
If the object with key "x" does not exist, create object with key "x" and add value y as a child.
If the object with key "x" DOES exist, set value y as a child.
If the key "x" exists and is ANY OTHER type, delete it, create an object with the key "x" and then add value y as a child.
I see ways to test if property tree values exist or whether they are specified types, but none to test if it's an object or not an object.
Here's a simple program I made illustrating what I mean:
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <sstream>
#include <iostream>
const char *json = "{"
"\"object\" : { \"mighty\" : \"wind\" },"
"\"boolean\" : true"
"}";
void printTree( std::string name, boost::property_tree::ptree tree )
{
std::cout << "Pass '" << name << "'" << std::endl;
try
{
std::stringstream ss;
boost::property_tree::write_json( ss, tree );
std::cout << ss.str() << std::endl;
}
catch( std::exception &e )
{
std::cout << "Could not make create json: " << e.what() << std::endl;
}
}
int main( int argc, char *argv[] )
{
boost::property_tree::ptree tree;
// Load it
std::istringstream ss_json( json );
boost::property_tree::read_json( ss_json, tree );
// Add a value to an object that doesn't exist
tree.put( "none.value", "hello!" );
// Print to see
printTree( "Nonexistent value test", tree );
// Add a value to the object
tree.put( "object.value", "bello!" );
// Print this one
printTree( "Adding value test", tree );
// Convert boolean to an object and add a value
tree.put( "boolean.value", "mello!" );
// Print it
printTree( "Converting value test", tree );
}
The output will be:
Pass 'Nonexistent value test'
{
"object": {
"mighty": "wind"
},
"boolean": "true",
"none": {
"value": "hello!"
}
}
Pass 'Adding value test'
{
"object": {
"mighty": "wind",
"value": "bello!"
},
"boolean": "true",
"none": {
"value": "hello!"
}
}
Pass 'Converting value test'
Could not make create json: <unspecified file>: ptree contains data that cannot be represented in JSON format
You can see in the output, the last step fails to convert to JSON (doesn't throw when I try to set it).
How can I achieve scenario 3 in my list above?
If the key "x" exists and is ANY OTHER type, delete it, create an object with the key "x" and then add value y as a child. Also, they don't observe any of the JSON data types.
Your plan is pretty doomed. Property Tree is not a JSON library. Property Trees can have data and child nodes at the same node. E.g.
ptree p;
auto& x = p.put_child("x", {});
x.put_value("hello");
write_json(std::cout, p);
Prints
{
"x": "hello"
}
But adding
/*auto& a = */ p.put_child("x.a", {});
write_json(std::cout, p);
Fails with Live On Coliru
terminate called after throwing an instance of 'boost::wrapexcept<boost::property_tree::json_parser::json_parser_error>'
what(): <unspecified file>: ptree contains data that cannot be represented in JSON format
A workaround would be to remove any value prior to or when adding properties:
x.put_value("");
auto& a = p.put_child("x.a", {});
a.add("prop1", 123);
a.add("prop2", "one two three");
a.add("b.prop1", "nesting");
write_json(std::cout, p);
Would print Live On Coliru
Finer notes
It might seem more efficient to check the presence of a value before clearing it:
if (x.get_value_optional<std::string>()) {
x.put_value("");
}
But due the the stringly typed nature of Property Tree storage there's no difference as the condition will just always be true for std::string. (Similarly there's no way to retrieve a value by reference.)
Note ALSO that when setting the n.prop1 nested property, you MAY have to also check that b has no value if you don't control the source data, because otherwise it would fail again.
Assuming that your object graph structure is reasonably predictable (or even static), I'd suggest getting it over with ahead of time:
for (auto key : { "x", "x.a", "x.a.b" }) {
if (auto child = p.get_child_optional(key)) {
std::cout << "clearing " << key << std::endl;
child->put_value("");
}
}
Which can be generalized with a helper:
clear_values("x.a.b", p);
Which could be implemented as
void clear_values(ptree::path_type path, ptree& p) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (child) {
child->put_value("");
clear_values(path, *child);
}
}
Bonus
In fact with such a helper it might become opportune to also create the expected hierarchy on the fly:
void clear_values(ptree::path_type path, ptree& p, bool create = false) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (!child && create) {
child = p.put_child(head, {});
}
if (child) {
child->put_value("");
clear_values(path, *child, create);
}
}
Now it would even work well without any pre-existing data:
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
void clear_values(ptree::path_type path, ptree& p, bool create = false) {
if (path.empty())
return;
auto head = path.reduce();
auto child = p.get_child_optional(head);
if (!child && create) {
child = p.put_child(head, {});
}
if (child) {
child->put_value("");
clear_values(path, *child, create);
}
}
int main() {
ptree p;
clear_values("x.a.b", p, true);
auto& a = p.get_child("x.a");
a.add("prop1", 123);
a.add("prop2", "one two three");
a.add("b.prop1", "nesting");
write_json(std::cout, p);
}
Prints
{
"x": {
"a": {
"b": {
"prop1": "nesting"
},
"prop1": "123",
"prop2": "one two three"
}
}
}

Clang Tool: rewrite ObjCMessageExpr

I want to rewrite all messages in my code,
I need replace only selectors, but I need be able to replace nested expressions
f. e. :
[super foo:[someInstance someMessage:#""] foo2:[someInstance someMessage2]];
I tried do it with clang::Rewriter replaceText and just generate new string,
but there is a problem: It would not be work if I change selectors length, because I replace nested messages with those old positions.
So, I assumed that I need to use clang::Rewriter ReplaceStmt(originalStatement, newStatement);
I am using RecursiveASTVisitor to visit all messages, and I want to copy those messages objects, and replace selectors:
How can I do that?
I tried use ObjCMessageExpr::Create but there is so meny args, I don't know how to get ASTContext &Context and ArrayRef<SourceLocation> SeLocs and Expr *Receiver parameters from the original message.
What is the proper way to replace selectors in nested messages using clang tool (clang tooling interface)?
Update:
Should I use ReplaceStmtWithStmt callback and ASTMatchFinder ?
Update:
I am using following function to rewrite text in file:
void ReplaceText(SourceLocation start, unsigned originalLength, StringRef string) {
m_rewriter.ReplaceText(start, originalLength, string);
m_rewriter.overwriteChangedFiles();
}
And I want to replace all messageExpr in code with new selector f.e:
how it was:
[object someMessage:[object2 someMessage:obj3 calculate:obj4]];
how it should be:
[object newSelector:[object2 newSelector:obj3 newSelector:obj4]];
I am using ReqoursiveASTVisitor:
bool VisitStmt(Stmt *statement) {
if (ObjCMessageExpr *messageExpr = dyn_cast<ObjCMessageExpr>(statement)) {
ReplaceMessage(*messageExpr)
}
return true;
}
I created method for generating new message expr string:
string StringFromObjCMessageExpr(ObjCMessageExpr& messageExpression) {
std::ostringstream stringStream;
const string selectorString = messageExpression.getSelector().getAsString();
cout << selectorString << endl;
vector<string> methodParts;
split(selectorString, ParametersDelimiter, methodParts);
stringStream << "[" ;
const string receiver = GetStringFromLocations(m_compiler, messageExpression.getReceiverRange().getBegin(), messageExpression.getSelectorStartLoc());
stringStream << receiver;
clang::ObjCMessageExpr::arg_iterator argIterator = messageExpression.arg_begin();
for (vector<string>::const_iterator partsIterator = methodParts.begin();
partsIterator != methodParts.end();
++partsIterator) {
stringStream << "newSelector";
if (messageExpression.getNumArgs() != 0) {
const clang::Stmt *argument = *argIterator;
stringStream << ":" << GetStatementString(*argument) << " ";
++argIterator;
}
}
stringStream << "]";
return stringStream.str();
}
void ReplaceMessage(ObjCMessageExpr& messageExpression) {
SourceLocation locStart = messageExpression.getLocStart();
SourceLocation locEnd = messageExpression.getLocEnd();
string newExpr = StringFromObjCMessageExpr(messageExpression);
const int exprStringLegth = m_rewriter.getRangeSize(SourceRange(locStart, locEnd));
ReplaceText(locStart, exprStringLegth, newExpr);
}
The problem occurs when I try to replace nested messages, like that:
[simpleClass doSomeActionWithString:string3 andAnotherString:string4];
[simpleClass doSomeActionWithString:str andAnotherString:str2];
[simpleClass doSomeActionWithString:#"" andAnotherString:#"asdasdsad"];
[simpleClass setSimpleClassZAZAZAZAZAZAZAZA:[simpleClass getSimpleClassZAZAZAZAZAZAZAZA]];
the result is:
[simpleClass newSelector:string3 newSelector:string4 ];
[simpleClass newSelector:str newSelector:str2 ];
[simpleClass newSelector:#"" newSelector:#"asdasdsad" ];
[simpleClass newSelector:[simpleClass getSimp[simpleClass newSelector]];
because messageExpression has "old" value of getLocStart(); and getLocEnd(); How can I fix it?
You can rewrite selector name by replacing only continuous parts of selector name. For example, replace only underlined parts
[object someMessage:[object2 someMessage:obj3 calculate:obj4]];
^~~~~~~~~~~ ^~~~~~~~~~~ ^~~~~~~~~
To achieve this you require only
number of selector parts - ObjCMessageExpr::getNumSelectorLocs()
their locations - ObjCMessageExpr::getSelectorLoc(index)
their lengths - ObjCMessageExpr::getSelector().getNameForSlot(index).size().
Overall, you can rewrite ObjCMessageExpr with the following RecursiveASTVisitor:
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Rewrite/Core/Rewriter.h"
namespace clang_tooling
{
using clang::SourceLocation;
class RewritingVisitor : public clang::ASTConsumer,
public clang::RecursiveASTVisitor<RewritingVisitor>
{
public:
// You can obtain SourceManager and LangOptions from CompilerInstance when
// you are creating visitor (which is also ASTConsumer) in
// clang::ASTFrontendAction::CreateASTConsumer.
RewritingVisitor(clang::SourceManager &sourceManager,
const clang::LangOptions &langOptions)
: _sourceManager(sourceManager), _rewriter(sourceManager, langOptions)
{}
virtual void HandleTranslationUnit(clang::ASTContext &context)
{
TraverseDecl(context.getTranslationUnitDecl());
_rewriter.overwriteChangedFiles();
}
bool VisitObjCMessageExpr(clang::ObjCMessageExpr *messageExpr)
{
if (_sourceManager.isInMainFile(messageExpr->getLocStart()))
{
clang::Selector selector = messageExpr->getSelector();
for (unsigned i = 0, end = messageExpr->getNumSelectorLocs();
i < end; ++i)
{
SourceLocation selectorLoc = messageExpr->getSelectorLoc(i);
_rewriter.ReplaceText(selectorLoc,
selector.getNameForSlot(i).size(),
"newSelector");
}
}
return Base::VisitObjCMessageExpr(messageExpr);
}
private:
typedef clang::RecursiveASTVisitor<RewritingVisitor> Base;
clang::SourceManager &_sourceManager;
clang::Rewriter _rewriter;
};
} // end namespace clang_tooling