I have a boost::process::child. There are many examples on how to get all its stdout or stderr in a single vector, but in this method you capture all data at once. But how to retrieve lines/characters as soon as they are printed in child process?
The docs are here:
Synchronous IO
Asynchronous IO
Using ipstream
The simplest way:
Live On Coliru
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
int main() {
std::vector<std::string> args {
"-c",
R"--(for a in one two three four; do sleep "$(($RANDOM%2)).$(($RANDOM%10))"; echo "line $a"; done)--" };
bp::ipstream output;
bp::child p("/bin/bash", args, bp::std_out > output);
std::string line;
while (std::getline(output, line)) {
std::cout << "Received: '" << line << "'" << std::endl;
}
}
Prints (e.g.):
At 0.409434s Received: 'line one'
At 0.813645s Received: 'line two'
At 1.2179s Received: 'line three'
At 2.92228s Received: 'line four'
Using async_pipe
This method is much more versatile and lets you do the "hard" cases where deadlock could occur, like when you want to do other things at the same time instead of blocking for input.
#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <boost/asio.hpp>
#include <iostream>
namespace bp = boost::process;
using boost::asio::mutable_buffer;
void read_loop(bp::async_pipe& p, mutable_buffer buf) {
p.async_read_some(buf, [&p,buf](std::error_code ec, size_t n) {
std::cout << "Received " << n << " bytes (" << ec.message() << "): '";
std::cout.write(boost::asio::buffer_cast<char const*>(buf), n) << "'" << std::endl;
if (!ec) read_loop(p, buf);
});
}
int main() {
boost::asio::io_service svc;
std::vector<std::string> args {
"-c",
R"--(for a in one two three four; do sleep "$(($RANDOM%2)).$(($RANDOM%10))"; echo "line $a"; done)--" };
bp::async_pipe output(svc);
bp::child p("/bin/bash", args, bp::std_out > output, svc);
char buf[1024];
read_loop(output, bp::buffer(buf));
svc.run();
}
Related
I have a windows command line program using Boost.Program_Options. One option uses a std::filesystem::path variable.
namespace fs = std::filesystem;
namespace po = boost::program_options;
fs::path optionsFile;
po::options_description desc( "Options" );
desc.add_options()
("help,h", "Help screen")
("options,o", po::value<fs::path>( &optionsFile ), "file with options");
calling the program with -o c:\temp\options.txt or with -o "c:\temp\options.txt" works fine, but calling the program with -o "c:\temp\options 1.txt" fails with this error:
error: the argument( 'c:\temp\options 1.txt' ) for option '--options' is invalid
The content of argv in this case is:
argv[0] = Exepath
argv[1] = -o
argv[2] = c:\temp\options 1.txt
This is the full code:
#include <boost/program_options.hpp>
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
namespace po = boost::program_options;
int wmain( int argc, wchar_t * argv[] )
{
try
{
fs::path optionsFile;
po::options_description desc( "Options" );
desc.add_options()
("help,h", "Help screen")
("options,o", po::value<fs::path>( &optionsFile ), "File containing the command and the arguments");
po::wcommand_line_parser parser{ argc, argv };
parser.options( desc ).allow_unregistered().style(
po::command_line_style::default_style |
po::command_line_style::allow_slash_for_short );
po::wparsed_options parsed_options = parser.run();
po::variables_map vm;
store( parsed_options, vm );
notify( vm );
if( vm.count( "help" ) )
{
std::cout << desc << '\n';
return 0;
}
std::cout << "optionsFile = " << optionsFile << "\n";
}
catch( const std::exception & e )
{
std::cerr << "error: " << e.what() << "\n";
return 1;
}
return 0;
}
How can I handle paths containing whitespace correctly? Is that even possible using std::filesystem::path or do I have to use std::wstring?
Indeed I could reproduce this. Replacing fs::path with std::string fixed it.
Here's a side-by-side reproducer:
Live On Coliru
#include <boost/program_options.hpp>
#include <filesystem>
#include <iostream>
namespace po = boost::program_options;
template <typename Path> static constexpr auto Type = "[unknown]";
template <> constexpr auto Type<std::string> = "std::string";
template <> constexpr auto Type<std::filesystem::path> = "fs::path";
template <typename Path>
bool do_test(int argc, char const* argv[]) try {
Path optionsFile;
po::options_description desc("Options");
desc.add_options() //
("help,h", "Help screen") //
("options,o", po::value<Path>(&optionsFile),
"File containing the command and the arguments");
po::command_line_parser parser{argc, argv};
parser.options(desc).allow_unregistered().style(
po::command_line_style::default_style |
po::command_line_style::allow_slash_for_short);
auto parsed_options = parser.run();
po::variables_map vm;
store(parsed_options, vm);
notify(vm);
if (vm.count("help")) {
std::cout << desc << '\n';
return true;
}
std::cout << "Using " << Type<Path> << "\toptionsFile = " << optionsFile << "\n";
return true;
} catch (const std::exception& e) {
std::cout << "Using " << Type<Path> << "\terror: " << e.what() << "\n";
return false;
}
int main() {
for (auto args : {
std::vector{"Exepath", "-o", "c:\\temp\\options1.txt"},
std::vector{"Exepath", "-o", "c:\\temp\\options 1.txt"},
})
{
std::cout << "\n -- Input: ";
for (auto& arg : args) {
std::cout << " " << std::quoted(arg);
}
std::cout << "\n";
int argc = args.size();
args.push_back(nullptr);
do_test<std::string>(argc, args.data());
do_test<std::filesystem::path>(argc, args.data());
}
}
Prints
-- Input: "Exepath" "-o" "c:\\temp\\options1.txt"
Using std::string optionsFile = c:\temp\options1.txt
Using fs::path optionsFile = "c:\\temp\\options1.txt"
-- Input: "Exepath" "-o" "c:\\temp\\options 1.txt"
Using std::string optionsFile = c:\temp\options 1.txt
Using fs::path error: the argument ('c:\temp\options 1.txt') for option '--options' is invalid
The reason most likely is that extraction from the command line argument defaults to using operator>> on a stringstream¹. If that has skipws set (as all C++ istreams do by default), then whitespace stops the "parse" and the argument is rejected because it is not fully consumed.
However, modifying the code to include a validate overload that fires for paths, adding std::noskipws didn't help!
template <class CharT>
void validate(boost::any& v, std::vector<std::basic_string<CharT>> const& s,
std::filesystem::path* p, int)
{
assert(s.size() == 1);
std::basic_stringstream<CharT> ss;
for (auto& el : s)
ss << el;
path converted;
ss >> std::noskipws >> converted;
if (!ss.eof())
throw std::runtime_error("Invalid path format");
v = std::move(converted);
}
Apparently, operator>> for fs::path doesn't obey noskipws. A look at the docs confirms:
Performs stream input or output on the path p. std::quoted is used so that spaces do not cause truncation when later read by stream input operator.
This gives us the workaround:
Workaround
template <class CharT>
void validate(boost::any& v, std::vector<std::basic_string<CharT>> const& s,
std::filesystem::path* p, int)
{
assert(s.size() == 1);
std::basic_stringstream<CharT> ss;
for (auto& el : s)
ss << std::quoted(el);
path converted;
ss >> std::noskipws >> converted;
if (ss.peek(); !ss.eof())
throw std::runtime_error("excess path characters");
v = std::move(converted);
}
Here we balance the std::quoted quoting/escaping as required.
Live Demo
Proof Of Concept:
Live On Coliru
#include <boost/program_options.hpp>
#include <filesystem>
#include <iostream>
namespace std::filesystem {
template <class CharT>
void validate(boost::any& v, std::vector<std::basic_string<CharT>> const& s,
std::filesystem::path* p, int)
{
assert(s.size() == 1);
std::basic_stringstream<CharT> ss;
for (auto& el : s)
ss << std::quoted(el);
path converted;
ss >> std::noskipws >> converted;
if (ss.peek(); !ss.eof())
throw std::runtime_error("excess path characters");
v = std::move(converted);
}
}
namespace po = boost::program_options;
template <typename Path> static constexpr auto Type = "[unknown]";
template <> constexpr auto Type<std::string> = "std::string";
template <> constexpr auto Type<std::filesystem::path> = "fs::path";
template <typename Path>
bool do_test(int argc, char const* argv[]) try {
Path optionsFile;
po::options_description desc("Options");
desc.add_options() //
("help,h", "Help screen") //
("options,o", po::value<Path>(&optionsFile),
"File containing the command and the arguments");
po::command_line_parser parser{argc, argv};
parser.options(desc).allow_unregistered().style(
po::command_line_style::default_style |
po::command_line_style::allow_slash_for_short);
auto parsed_options = parser.run();
po::variables_map vm;
store(parsed_options, vm);
notify(vm);
if (vm.count("help")) {
std::cout << desc << '\n';
return true;
}
std::cout << "Using " << Type<Path> << "\toptionsFile = " << optionsFile << "\n";
return true;
} catch (const std::exception& e) {
std::cout << "Using " << Type<Path> << "\terror: " << e.what() << "\n";
return false;
}
int main() {
for (auto args : {
std::vector{"Exepath", "-o", "c:\\temp\\options1.txt"},
std::vector{"Exepath", "-o", "c:\\temp\\options 1.txt"},
})
{
std::cout << "\n -- Input: ";
for (auto& arg : args) {
std::cout << " " << std::quoted(arg);
}
std::cout << "\n";
int argc = args.size();
args.push_back(nullptr);
do_test<std::string>(argc, args.data());
do_test<std::filesystem::path>(argc, args.data());
}
}
Now prints
-- Input: "Exepath" "-o" "c:\\temp\\options1.txt"
Using std::string optionsFile = c:\temp\options1.txt
Using fs::path optionsFile = "c:\\temp\\options1.txt"
-- Input: "Exepath" "-o" "c:\\temp\\options 1.txt"
Using std::string optionsFile = c:\temp\options 1.txt
Using fs::path optionsFile = "c:\\temp\\options 1.txt"
¹ this actually happens inside boost::lexical_cast which comes from Boost Conversion
I am trying to get the exit code of a child process (using boost::process and boost::asio) when that child process is killed due to a segmentation violation or divide be zero or any other kill signal. The exit code and error code always return with 0 and success.
I am running this on CentOS 7 using g++ 4.8.5 and boost 1.66
If I run the same code with a child process that simply returns a non-zero exit code it successfully returns that exit code.
#include <iostream>
#include <boost/process.hpp>
#include <boost/asio/io_service.hpp>
namespace bp = boost::process;
using namespace std;
int main (int argc, char** argv)
{
string exe = "./crashes";
vector<string> data;
boost::asio::io_service ios;
int exit_code;
error_code ec;
future<string> ostr;
bp::child c(exe,
(bp::std_out & bp::std_err) > ostr,
ios,
bp::on_exit=[&exit_code, &ec](int exit, const error_code& ecin)
{exit_code = exit; ec = ecin;});
ios.run();
cout << "Exit Code = " << exit_code << endl;
cout << "Error Code = " << ec.message() << endl;
cout << "child stdin & stderr:\n";
cout << ostr.get() << endl;
return exit_code;
}
and the crashes code
int main (int argc, char** argv)
{
int* y = 0;
int c = *y;
}
The results show a 0 exit code and Success error_code
Exit Code = 0
Error Code = Success
child stdin & stderr:
running the crashes executable alone returns an exit code of 139
bash-4.2$ ./crashes
Segmentation fault (core dumped)
bash-4.2$ echo $?
139
The details of process termination and exit codes are platform dependent.
Boost process papers over the differences in the default interface: your on_exit handler is called with the result of boost::process::detail::posix::eval_exit_status() of the exit status, which means:
inline int eval_exit_status(int code)
{
if (WIFEXITED(code))
{
return WEXITSTATUS(code);
}
else if (WIFSIGNALED(code))
{
return WTERMSIG(code);
}
else
{
return code;
}
}
So, you get "exit-code 11" meaning segfault... If you want to actually know, you can look at native_exit_code()
bp::on_exit = [&result, &c](int /*ignored*/, const std::error_code &ec) {
auto exit_status = c.native_exit_code();
result.exit_code = boost::make_optional(WIFEXITED(exit_status), WEXITSTATUS(exit_status));
result.signal = boost::make_optional(WIFSIGNALED(exit_status), WTERMSIG(exit_status));
result.ec = ec;
}
Now this assumes some changes to the result variables. Full listing:
Listing
#include <boost/asio/io_service.hpp>
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
int main(int argc, char**) {
std::string exe = argc>1? "./ltua" : "./crashes";
boost::asio::io_service ios;
struct {
boost::optional<int> exit_code;
boost::optional<int> signal;
std::error_code ec{};
} result;
std::future<std::string> ostr;
bp::group g;
bp::child c(exe, g, (bp::std_out & bp::std_err) > ostr, ios,
bp::on_exit = [&result, &c](int /*ignored*/, const std::error_code &ec) {
auto exit_status = c.native_exit_code();
result.exit_code = boost::make_optional(WIFEXITED(exit_status), WEXITSTATUS(exit_status));
result.signal = boost::make_optional(WIFSIGNALED(exit_status), WTERMSIG(exit_status));
result.ec = ec;
});
//g.wait();
ios.run();
if (result.exit_code) {
std::cout << "Exited with " << *result.exit_code << std::endl;
}
if (result.signal) {
std::cout << "Signaled with sginal #" << *result.signal << ", aka " << ::strsignal(*result.signal) << std::endl;
}
std::cout << "Error Code = " << result.ec.message() << std::endl;
std::cout << "child stdin & stderr:\n";
std::cout << ostr.get() << std::endl;
return result.exit_code? *result.exit_code : 255;
}
Output
When run with ltua.cpp:
#include <iostream>
int main() {
std::cout << "so long" << std::end;
std::cerr << "and thanks" << std::end;
std::cout << "for all" << std::end;
std::cerr << "the fish" << std::end;
return 42;
}
Prints
Exited with 42
Error Code = Success
child stdin & stderr:
so long
and thanks
for all
the fish
And with crashes.cpp:
int main() {
int *y = 0;
int c = *y;
}
Prints
Signaled with sginal #11, aka Segmentation fault
Error Code = Success
child stdin & stderr:
I have tried to use boost::childprocess with an async_pipe as shown in the code example below, while expecting since there is a wait method, that the call to run would not wait for the called executable to finish before continuing to the line where I call wait(). My aim is namely to start the same executable multiple times in order to test in GTest an instance counting method (implemented based on boost managed shared memory segment).
But here fore I need the call to io_service::run(), to not wait for the called executable to finish as it does right now. Can someone tell me where I am using it wrong please? Or if this is the wrong way to unit test my function? I have been trying to find the solution for quite some time!
Here is a sample of how I call one instance of the executable:
int CallChildProcess_Style9() {
std::string strCmdLine = "E:\\file.exe --Debug MainStartUps_Off --Lock 3";
boost::asio::io_service m_oIOS;
std::vector<char> m_oAsyncBuffer_Out;
bp::async_pipe m_oAsyncPipe_Out(m_oIOS);
std::error_code build_ec;
size_t nReadSize(0);
boost::scoped_ptr<boost::process::child> m_pChildProcess(nullptr);
m_pChildProcess.reset(new bp::child(strCmdLine.data(), bp::std_out > m_oAsyncPipe_Out, build_ec));
m_oAsyncBuffer_Out.resize(1024*8);
boost::asio::async_read(m_oAsyncPipe_Out, boost::asio::buffer(m_oAsyncBuffer_Out),
[&](const boost::system::error_code &ec, std::size_t size) { nReadSize = size; });
size_t iii = m_oIOS.run();
m_pChildProcess->wait();
m_oAsyncBuffer_Out.resize(nReadSize);
std::string strBuf(m_oAsyncBuffer_Out.begin(), m_oAsyncBuffer_Out.begin() + nReadSize);
int result = m_pChildProcess->exit_code();
m_oAsyncPipe_Out.close();
m_oIOS.reset();
return result;
}
Using io_service
To be using async_pipe, you need to supply the io_service instance to the parameter keywords of bp::child:
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <boost/process/async.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
namespace bp = boost::process;
int CallChildProcess_Style9() {
std::string strCmdLine = "/bin/cat";
boost::asio::io_service m_oIOS;
std::vector<char> m_oAsyncBuffer_Out;
bp::async_pipe m_oAsyncPipe_Out(m_oIOS);
std::error_code build_ec;
size_t nReadSize(0);
boost::scoped_ptr<boost::process::child> m_pChildProcess(nullptr);
std::vector<std::string> const args = { "/home/sehe/Projects/stackoverflow/test.cpp" };
m_pChildProcess.reset(new bp::child(strCmdLine, args, bp::std_out > m_oAsyncPipe_Out, build_ec, m_oIOS));
std::cout << "Launched: " << build_ec.message() << std::endl;
m_oAsyncBuffer_Out.resize(1024 * 8);
boost::asio::async_read(m_oAsyncPipe_Out, boost::asio::buffer(m_oAsyncBuffer_Out),
[&](const boost::system::error_code &ec, std::size_t size) {
std::cout << "read completion handler: size = " << size << " (" << ec.message() << ")" << std::endl;
nReadSize = size;
});
std::cout << "read started" << std::endl;
size_t iii = m_oIOS.run();
std::cout << "io_service stopped" << std::endl;
std::cout << "initiate child::wait" << std::endl;
m_pChildProcess->wait();
std::cout << "wait completed" << std::endl;
std::string const strBuf(m_oAsyncBuffer_Out.data(), nReadSize);
int result = m_pChildProcess->exit_code();
m_oAsyncPipe_Out.close();
m_oIOS.reset();
return result;
}
int main() {
CallChildProcess_Style9();
}
Prints
http://coliru.stacked-crooked.com/a/8a9bc6bed3dd5e0a
Launched: Success
read started
read completion handler: size = 1589 (End of file)
io_service stopped
initiate child::wait
wait completed
Hanging Up The Child
Even with that fixed, async_pipe::async_read only reads until the buffer is full or EOF is reached. If the child process outputs more than the buffer size (8k in your sample) then it will get stuck and never finish.
E.g.: replacing the command like this:
std::string strCmdLine = "/usr/bin/yes";
Results in
Live On Coliru
Launched: Success
read started
read completion handler: size = 8192 (Success)
io_service stopped
initiate child::wait
At which it will hang till infinity. This is not because yes has infinite output. Any command having large output will hang (e.g. /bin/cat /etc/dictionaries-common/words hangs in the same way). You can prove this by looking at the strace output:
$ sudo strace -p $(pgrep yes)
strace: Process 21056 attached
write(1, "/home/sehe/Projects/stackoverflo"..., 8170
The easiest way to "fix" this would be to close the output sink after you filled up your output buffer:
boost::asio::async_read(m_oAsyncPipe_Out, boost::asio::buffer(m_oAsyncBuffer_Out),
[&](const boost::system::error_code &ec, std::size_t size) {
std::cout << "read completion handler: size = " << size << " (" << ec.message() << ")" << std::endl;
nReadSize = size;
m_oAsyncPipe_Out.close();
});
This requires you to anticipate that the child exited before you call wait() so wait() might fail:
Live On Coliru
Launched: Success
read started
read completion handler: size = 8192 (Success)
io_service stopped
initiate child::wait
wait completed (Success)
Taking A Step Back: What Do You Need?
It looks, though, that you might be complicating. If you're happy limiting the output to 8k, and all you need is to have multiple copies, why bother with async io?
Any child is already asynchronous, and you can just pass the buffer:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
using Args = std::vector<std::string>;
using Buffer8k = std::array<char, 8192>;
int main() {
auto first_out = std::make_unique<Buffer8k>(),
second_out = std::make_unique<Buffer8k>();
*first_out = {};
*second_out = {};
boost::asio::io_service svc;
bp::child first("/bin/echo", Args{"-n", "first"}, bp::std_out > boost::asio::buffer(*first_out), svc);
bp::child second("/bin/echo", Args{"-n", "second"}, bp::std_out >boost::asio::buffer(*second_out), svc);
std::cout << "Launched" << std::endl;
svc.run();
first.wait();
second.wait();
std::string const strFirst(first_out->data()); // uses NUL-termination (assumes text output)
std::string const strSecond(second_out->data()); // uses NUL-termination (assumes text output)
std::cout << strFirst << "\n";
std::cout << strSecond << "\n";
return first.exit_code();
}
Prints
Launched
first
second
More Examples
Because I can't really be sure about what you need, look at other examples that I wrote to actually show live async IO, where you might need to respond to particular output of one process.
Boost::process output blank lines
Read child process stdout in a separate thread with BOOST process
How to retrieve program output as soon as it printed?
I am trying to use the letter character class from unicode i.e. \p{L} with Boost Spirit but I have no luck so far. Below is an example where I am trying to use (on line 30) the \p{L} character class. When I replace line 30 with line 29 it works but that is not the intended use as I need any letter from Unicode in my example.
My use case is for UTF8 only. At the end of they day what I am trying to do here is substract a unicode range from all unicode letters when using boost-spirit lexer.
PS
Of course, my example is trimmed down and may not make a lot of sense as a use case but I hope you get the idea.
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <iostream>
#include <fstream>
#include <chrono>
#include <vector>
using namespace boost;
using namespace boost::spirit;
using namespace std;
using namespace std::chrono;
std::vector<pair<string, string> > getTokenMacros() {
std::vector<pair<string, string> > tokenDefinitionsVector;
tokenDefinitionsVector.emplace_back("JAPANESE_HIRAGANA", "[\u3041-\u3096]");
tokenDefinitionsVector.emplace_back("JAPANESE_HIRAGANA1",
"[\u3099-\u309E]");
tokenDefinitionsVector.emplace_back("ASIAN_NWS", "{JAPANESE_HIRAGANA}|"
"{JAPANESE_HIRAGANA1}");
tokenDefinitionsVector.emplace_back("ASIAN_NWS_WORD", "{ASIAN_NWS}*");
//tokenDefinitionsVector.emplace_back("NON_ASIAN_LETTER", "[A-Za-z0-9]");
tokenDefinitionsVector.emplace_back("NON_ASIAN_LETTER", "[\\p{L}-[{ASIAN_NWS}]]");
tokenDefinitionsVector.emplace_back("WORD", "{NON_ASIAN_LETTER}+");
tokenDefinitionsVector.emplace_back("ANY", ".");
return tokenDefinitionsVector;
}
;
struct distance_func {
template<typename Iterator1, typename Iterator2>
struct result: boost::iterator_difference<Iterator1> {
};
template<typename Iterator1, typename Iterator2>
typename result<Iterator1, Iterator2>::type operator()(Iterator1& begin,
Iterator2& end) const {
return distance(begin, end);
}
};
boost::phoenix::function<distance_func> const distance_fctor = distance_func();
template<typename Lexer>
struct word_count_tokens: lex::lexer<Lexer> {
word_count_tokens() :
asianNwsWord("{ASIAN_NWS_WORD}", lex::min_token_id + 110), word(
"{WORD}", lex::min_token_id + 170), any("{ANY}",
lex::min_token_id + 3000) {
using lex::_start;
using lex::_end;
using boost::phoenix::ref;
std::vector<pair<string, string> > tokenMacros(getTokenMacros());
for (auto start = tokenMacros.begin(), end = tokenMacros.end();
start != end; start++) {
this->self.add_pattern(start->first, start->second);
}
this->self = asianNwsWord | word | any;
}
lex::token_def<> asianNwsWord, word, any;
};
int main(int argc, char* argv[]) {
typedef lex::lexertl::token<string::iterator> token_type;
typedef lex::lexertl::actor_lexer<token_type> lexer_type;
word_count_tokens<lexer_type> word_count_lexer;
// read in the file int memory
ifstream sampleFile("/home/dan/Documents/wikiSample.txt");
string str = "abc efg ぁあ";
string::iterator first = str.begin();
string::iterator last = str.end();
lexer_type::iterator_type iter = word_count_lexer.begin(first, last);
lexer_type::iterator_type end = word_count_lexer.end();
typedef boost::iterator_range<string::iterator> iterator_range;
vector<iterator_range> parsed_tokens;
while (iter != end && token_is_valid(*iter)) {
cout << (iter->id() - lex::min_token_id) << " " << iter->value()
<< endl;
const iterator_range range = get<iterator_range>(iter->value());
parsed_tokens.push_back(range);
++iter;
}
if (iter != end) {
string rest(first, last);
cout << endl << "!!!!!!!!!" << endl << "Lexical analysis failed\n"
<< "stopped at: \"" << rest << "\"" << endl;
cout << "#" << (int) rest.at(0) << "#" << endl;
}
return 0;
}
I am trying to escape a string in quotation marks using boost::spirit::karma. This works fine if it's just a string. However, for a string in a boost::variant in a std::vector, it does not. Just printing the string does work however, I do not quite understand why.
Line (1) works fine, but doesn't do what I want. Line (2) should do it, but doesn't.
#include <iostream>
#include <string>
#include <boost/variant.hpp>
#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;
typedef std::vector<boost::variant<int, std::string>> ParameterList;
typedef boost::variant<int, std::string, ParameterList> Parameter;
main()
{
using karma::int_;
using boost::spirit::ascii::string;
using karma::eol;
using karma::lit;
std::string generated;
std::back_insert_iterator<std::string> sink(generated);
// (1)
karma::rule<std::back_insert_iterator<std::string>, ParameterList()> parameterListRule = (int_ | string) % lit(", "); // This works!
// (2)
//karma::rule<std::back_insert_iterator<std::string>, ParameterList()> parameterListRule = (int_ | (lit('"') << string << lit('"'))) % lit(", "); // This does not work
karma::rule<std::back_insert_iterator<std::string>, Parameter()> parameterRule = (int_ | (lit('"') << string << lit('"')) | parameterListRule) << eol; // This does work, even though it also escapes the string in a pair of quotation marks
karma::generate(sink, parameterRule, 1); // Works
karma::generate(sink, parameterRule, "foo"); // Works
karma::generate(sink, parameterRule, Parameter(ParameterList{1, "foo"})); // Only works using rule (1), not with (2)
std::cout << generated;
}
Edited In case recursion was not the goal, here's an edited version that solves the issue and the quote escaping: Live on Coliru (or just source here)
Hmm. It looks like you might have been after a recursive attribute/rule:
typedef boost::make_recursive_variant<int, std::string, std::vector<boost::recursive_variant_> >::type Parameter;
Just in that case, here's a simple approach to generating that:
gen = int_ | string | gen % ", ";
Now, your title suggests that strings containing double-quotes should escape these. I suggest
str = '"' << *('\\' << char_('"') | char_) << '"';
gen = int_ | str | gen % ", ";
Now the following test cases
for (Parameter p : Parameters {
1,
"foo",
Parameters { 1, "foo" },
Parameters { 1, "escape: \"foo\"", Parameters { "2", "bar" } }
})
{
std::cout << karma::format(gen, p) << '\n';
}
result in:
1
"foo"
1, "foo"
1, "escape: \"foo\"", "2", "bar"
If recursion is really a feature, you'd want to see the grouping of nested Parameter lists:
gen = int_ | str | '{' << gen % ", " << '}';
Now prints
1
"foo"
{1, "foo"}
{1, "escape: \"foo\"", {"2", "bar"}}
Full sample program:
#include <boost/variant.hpp>
#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;
typedef boost::make_recursive_variant<int, std::string, std::vector<boost::recursive_variant_> >::type Parameter;
typedef std::vector<Parameter> Parameters;
int main()
{
typedef boost::spirit::ostream_iterator It;
karma::rule<It, Parameter()> gen;
karma::rule<It, std::string()> str;
str = '"' << *('\\' << karma::char_('"') | karma::char_) << '"';
gen = (karma::int_ | str | '{' << gen % ", " << '}');
for (Parameter p : Parameters {
1,
"foo",
Parameters { 1, "foo" },
Parameters { 1, "escape: \"foo\"", Parameters { "2", "bar" } }
})
{
std::cout << karma::format(gen, p) << '\n';
}
}
If you iterate your data types, you should iterate your rules.
#include <iostream>
#include <string>
#include <boost/variant.hpp>
#include <boost/spirit/include/karma.hpp>
namespace karma = boost::spirit::karma;
typedef boost::variant<int, std::string> Item;
typedef std::vector<Item> ParameterList;
typedef boost::variant<int, std::string, ParameterList> Parameter;
int main()
{
using karma::int_;
using boost::spirit::ascii::string;
using karma::eol;
using karma::lit;
std::string generated;
std::back_insert_iterator<std::string> sink(generated);
karma::rule<std::back_insert_iterator<std::string>, Item()> itemRule =
int_ | (lit('"') << string << lit('"'));
karma::rule<std::back_insert_iterator<std::string>, ParameterList()>
parameterListRule = itemRule % lit(", ");
karma::rule<std::back_insert_iterator<std::string>, Parameter()>
parameterRule = (int_ | (lit('"') << string << lit('"')) | parameterListRule) << eol;
karma::generate(sink, parameterRule, 1);
karma::generate(sink, parameterRule, "foo");
karma::generate(sink, parameterRule, Parameter(ParameterList {1, "foo"}));
std::cout << generated;
return 0;
}
#include <iostream>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/karma_right_alignment.hpp>
using namespace boost;
void foo(char* buffer, uint32_t lhOid) {
boost::spirit::karma::generate(buffer, boost::spirit::right_align(20)[boost::spirit::karma::int_], lhOid);
*buffer = '\0';
}
int main() {
char arr[21];
foo(arr, 1234);
std::cout.write(arr, 21) << std::endl;
return 0;
}