Closed no-more-secrets closed 5 years ago
It's a very powerful, but somewhat convoluted process to use a macro, with all due respect to @vstakhov for even offering the feature. However, that may just be my primarily C++ background and experience talking. You can totally fail when using macros if you don't have a passing understanding of how libucl works internally. Here's some messy C++ example code to get you started:
Compile and run:
$ g++ -g -I../libucl/include/ -L../libucl/build/ -lucl parser.cc -o parser
$ ./parser test.ucl
.version
checks that the version matches and fails to process if it is a mismatch..uuid
processes a uuid value in two different places in the ucl input file.Failing input ucl file (wrong version):
.version 2
foo .uuid "abcdef01-2345-6789-abcd-ef0123456789" {
thing1 = true;
thing2 = 4;
thing3 = "blue";
}
bar {
.uuid "98765432-10fe-dcba-9876-543210fedcba";
fish1 = "red";
fish2 = "blue";
}
Successful input ucl file:
.version 1
foo .uuid "abcdef01-2345-6789-abcd-ef0123456789" {
thing1 = true;
thing2 = 4;
thing3 = "blue";
}
bar {
.uuid "98765432-10fe-dcba-9876-543210fedcba";
fish1 = "red";
fish2 = "blue";
}
Successful output:
Resulting JSON Root Dump:
{
"version": 1,
"foo": {
"uuid": "abcdef01-2345-6789-abcd-ef0123456789",
"thing1": true,
"thing2": 4,
"thing3": "blue"
},
"bar": {
"uuid": "98765432-10fe-dcba-9876-543210fedcba",
"fish1": "red",
"fish2": "blue"
}
}
Unpack:
version = 1;
foo {
uuid = "abcdef01-2345-6789-abcd-ef0123456789";
thing1 = true;
thing2 = 4;
thing3 = "blue";
}
bar {
uuid = "98765432-10fe-dcba-9876-543210fedcba";
fish1 = "red";
fish2 = "blue";
}
If you end up with macros that can be nested, I'd recommend storing the macro name and line number in a stack while calling ucl_parser_insert_chunk
so you can unroll the macros if you error out for some reason.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdio.h>
#include <string>
#include <vector>
#include <ucl++.h>
// #define WINDOWS
#ifdef WINDOWS
#include <direct.h>
#define GetCurrentDir _getcwd
#else
#include <unistd.h>
#define GetCurrentDir getcwd
#endif
#define UUID_LENGTH 36
#define VERSION_KEY "version"
#define UUID_KEY "uuid"
// Current document Version
const constexpr int kCurrentVersion = 1;
void TrimWhitespace(std::string &str, bool newlines = true) {
if (str.empty()) return;
std::string whitespace;
if (newlines)
whitespace = " \t\n";
else
whitespace = " \t";
str.erase(str.find_last_not_of(whitespace) + 1);
str.erase(0, str.find_first_not_of(whitespace));
}
std::string GetCurrentDirectory( void ) {
char buff[FILENAME_MAX];
GetCurrentDir( buff, FILENAME_MAX );
std::string current_working_dir(buff);
return current_working_dir;
}
void ConsumeUclObject(struct ucl_parser *parser, std::string &result,
uint64_t initial_depth = 1) {
uint64_t depth = initial_depth;
unsigned char next = ucl_parser_chunk_peek(parser);
if (next == '{')
depth++;
else if (next == '}')
depth--;
while (depth > 0 && next != 0) {
result += *reinterpret_cast<char *>(&next);
ucl_parser_chunk_skip(parser);
next = ucl_parser_chunk_peek(parser);
if (next == '{')
depth++;
else if (next == '}')
depth--;
}
}
void uuid_handler_format_error(std::ostream &error) {
error << "Required format was not parsed correctly after ." << UUID_KEY
<< " macro.\n"
<< "\t." << UUID_KEY << " <36 character UUID value>;\n"
<< "\t<foo/bar> ." << UUID_KEY
<< " <36 character UUID value> {" << std::endl;
}
void version_handler_format_error(std::ostream &error) {
error << "Required format was not parsed correctly after ." << VERSION_KEY
<< " macro.\n"
<< "\t." << VERSION_KEY << " <version number>;\n"
<< std::endl;
}
class Parser {
bool bError_;
static bool uuid_handler(const unsigned char *data, size_t len,
const ucl_object_t *args, void *ud) {
ucl::Ucl::macro_userdata_s *userdata =
reinterpret_cast<ucl::Ucl::macro_userdata_s *>(ud);
if (userdata == nullptr) return false;
struct ucl_parser *parser = userdata->parser;
if (parser == nullptr) return false;
Parser *detail_parser = reinterpret_cast<Parser *>(userdata->userdata);
if (detail_parser == nullptr) return false;
std::string uuid(reinterpret_cast<const char *>(data), len);
bool open_brace = (uuid.find_last_of("{") != std::string::npos);
size_t closer = uuid.find_last_of("{;");
if (closer != std::string::npos) {
uuid = uuid.substr(0, closer);
}
TrimWhitespace(uuid);
if (uuid.length() != UUID_LENGTH) {
detail_parser->bError_ = true;
uuid_handler_format_error(std::cerr);
return false;
}
// Add Chunks
std::string chunks;
chunks += UUID_KEY " = \"" + uuid.substr(0, UUID_LENGTH) + "\";\n";
if (ucl_parser_chunk_peek(parser) == '{' || open_brace) {
if (!open_brace) ucl_parser_chunk_skip(parser);
ConsumeUclObject(parser, chunks);
}
bool chunk_success = ucl_parser_insert_chunk(
parser, reinterpret_cast<const unsigned char *>(chunks.c_str()),
chunks.length());
if (!chunk_success || ucl_parser_get_error_code(parser) != 0) {
detail_parser->bError_ = true;
uuid_handler_format_error(std::cerr);
return false;
}
return true;
}
static bool version_handler(const unsigned char *data, size_t len,
const ucl_object_t *args, void *ud) {
ucl::Ucl::macro_userdata_s *userdata =
reinterpret_cast<ucl::Ucl::macro_userdata_s *>(ud);
if (userdata == nullptr) return false;
struct ucl_parser *parser = userdata->parser;
if (parser == nullptr) return false;
Parser *detail_parser = reinterpret_cast<Parser *>(userdata->userdata);
if (detail_parser == nullptr) return false;
std::string version(reinterpret_cast<const char *>(data), len);
if (version.find_last_of("{") != std::string::npos) {
detail_parser->bError_ = true;
version_handler_format_error(std::cerr);
return false;
}
size_t closer = version.find_last_of(";");
if (closer != std::string::npos) {
version = version.substr(0, closer);
}
TrimWhitespace(version);
if (stol(version) != kCurrentVersion) {
detail_parser->bError_ = true;
version_handler_format_error(std::cerr);
return false;
}
// Add Chunks
std::string chunks;
chunks += VERSION_KEY " = " + version + ";\n";
bool chunk_success = ucl_parser_insert_chunk(
parser, reinterpret_cast<const unsigned char *>(chunks.c_str()),
chunks.length());
if (!chunk_success || ucl_parser_get_error_code(parser) != 0) {
detail_parser->bError_ = true;
version_handler_format_error(std::cerr);
return false;
}
return true;
}
public:
int
parse(std::istream &stream, const std::string &filename) {
std::cout << "Parser::parse() filename = " << filename << std::endl;
// Add macro handlers to parser
ucl::Ucl::macro_handler_s handler;
std::vector<std::tuple<std::string /*name*/, ucl::Ucl::macro_handler_s,
void * /*userdata*/> >
macros;
handler.handler = uuid_handler;
macros.push_back(std::make_tuple(std::string(UUID_KEY), handler, this));
handler.handler = version_handler;
macros.push_back(std::make_tuple(std::string(VERSION_KEY), handler, this));
// Parse ucl data
std::string chunks(std::istreambuf_iterator<char>(stream), {});
std::string error;
bError_ = false;
ucl::Ucl uclRoot =
ucl::Ucl::parse(chunks, macros, error, UCL_DUPLICATE_MERGE);
if (!error.empty()) {
std::cerr << "Parse error:\n" << error << std::endl;
return 1;
}
if (bError_) return 1;
std::cout << "Resulting JSON Root Dump:\n"
<< uclRoot.dump(UCL_EMIT_JSON) << std::endl;
std::cout << "\nUnpack:\n"
<< uclRoot.dump(UCL_EMIT_CONFIG) << std::endl;
// TODO: Use your uclRoot here
return 0;
}
};
int main(int argc, char *argv[]) {
if (argc != 2) {
std::cerr << "usage: parser inputfile\n";
return 1;
}
std::string infile = argv[1];
std::ifstream input;
input.open(infile);
if (!input) {
std::cerr << "Failed to open requested input file: '" << infile << "'!" << std::endl;
return 1;
}
return Parser().parse(input, infile);
}
Thanks for that; I guess what I'm looking for is just like a 2-3 (english) sentences explaining what a UCL macro is and what they do. For example, are they the kind of macros that I can define and use within the UCL file, or does defining a macro require adding C/C++ code to augment the parser? If the latter then I doubt I would use them.
Second option.
Thank you
It is not clear from the documentation or examples how to use macros or how they work:
1) Can a user define a macro in a UCL file? 2) How do macros take arguments? Sometimes there are keyword arguments passed in parenthesis, and then there is a string after the macro name. What is the syntax for macros? 3) How do they work?
more info please, Thanks