yavl-cpp
is a tool to auto-generate C/C++ code containing struct declarations, data bindings (methods to convert YAML to generated structs and vice versa) and validators from a specification written in a subset of YAML ("YAVL").
In principle, yavl-cpp
is similar to Google Protobuf: You specify data structures in an abstract language and generate C++ code from that.
Some key differences are:
- Structure definitions in
yavl-cpp
are done via YAML documents. yavl-cpp
does not perform any kind of serialization.- Most of the validation is done either during parsing (by
yaml-cpp
) or in compilation (byg++
).yavl-compile
doesn't verify any of the types you specified. All types thatyaml-cpp
and the compiler understand, are valid.
The full YAVL language specification can be found in the Wiki. Here is an example:
Types:
String: std::string
EnumType:
- first_choice
- second_choice
TopType:
my_vec: std::vector<std::string>
my_enum: EnumType
my_int: int
my_str: String
Here is an example of a YAML document that matches the type TopType
that is declared in this spec:
my_vec: ["monkey", "bread", "tree"]
my_enum: first_choice
my_int: 5
my_str: foo
The YAVL compiler can be built and run like this:
$ git clone https://github.com/felsenhower/yavl-cpp
$ cd yavl-cpp
$ make
$ ./yavl-compile examples/simple_spec.yaml simple.h
The resulting C++ header file simple.h
will contain the following declarations (among other things):
typedef std::string String;
enum EnumType {
first_choice,
second_choice
};
struct TopType {
std::vector<std::string> my_vec;
EnumType my_enum;
int my_int;
String my_str;
};
Full simple.h
(click to expand!)
#pragma once
#include <yaml-cpp/yaml.h>
#include "yavl-cpp/runtime.h"
#include <vector>
#include <string>
typedef std::string String;
enum EnumType {
first_choice,
second_choice
};
inline void operator>>(const YAML::Node &input, EnumType &output) {
std::string tmp;
input >> tmp;
if (tmp == "first_choice") {
output = first_choice;
} else if (tmp == "second_choice") {
output = second_choice;
} else {
throw YAVL::BadConversionException(input, "EnumType");
}
}
inline YAML::Emitter& operator<<(YAML::Emitter &output, const EnumType &input) {
if (input == first_choice) {
output << "first_choice";
} else if (input == second_choice) {
output << "second_choice";
}
return output;
}
struct TopType {
std::vector<std::string> my_vec;
EnumType my_enum;
int my_int;
String my_str;
};
inline void operator>>(const YAML::Node &input, TopType &output) {
const std::set<std::string> keys = {
"my_vec",
"my_enum",
"my_int",
"my_str"
};
for (const auto &key : keys) {
if (!input[key]) {
throw YAVL::MissingKeyException("TopType", key);
}
}
for (const auto &it : input) {
const std::string key = it.first.as<std::string>();
if (!keys.contains(key)) {
throw YAVL::SuperfluousKeyException("TopType", key);
}
}
input["my_vec"] >> output.my_vec;
input["my_enum"] >> output.my_enum;
input["my_int"] >> output.my_int;
input["my_str"] >> output.my_str;
}
inline YAML::Emitter& operator<<(YAML::Emitter &output, const TopType &input) {
output << YAML::BeginMap;
output << YAML::Key << "my_vec";
output << YAML::Value << input.my_vec;
output << YAML::Key << "my_enum";
output << YAML::Value << input.my_enum;
output << YAML::Key << "my_int";
output << YAML::Value << input.my_int;
output << YAML::Key << "my_str";
output << YAML::Value << input.my_str;
output << YAML::EndMap;
return output;
}
inline std::vector<std::string> get_types() {
return {
"String",
"EnumType",
"TopType"
};
}
inline std::tuple<bool, std::optional<std::string>> validate_simple(const YAML::Node &node, const std::string type_name) {
if (type_name == "String") {
return validate<String>(node);
} else if (type_name == "EnumType") {
return validate<EnumType>(node);
} else if (type_name == "TopType") {
return validate<TopType>(node);
}
return std::make_tuple(false, std::nullopt);
}
By default, yavl-compile
creates declarations, readers, writers, and validators. Except for the declarations, this functionality depends on yaml-cpp
. See the Wiki for the complete Command Line Interface description.
When including this header into your C/C++ project, don't forget to add a -I/path/to/yavl-cpp/include/
to your CFLAGS
and to add yaml-cpp
:
$ g++ -Iinclude $(pkg-config --libs yaml-cpp) simple.cpp
If you wish to create header files that are valid C code, simply don't emit writers, readers, and validators, like in the example above, and don't use any types that aren't available in C (like std::string
, std::vector
, ...).
yavl-compile
does not check the correctness of the types you specified in your YAML file. Any mistakes will only be uncovered later when you include the header file into your project.
Using the example above, you can also validate YAML documents:
const auto &[ok, error_message] = validate<TopType>(doc);
// or
const auto &[ok, error_message] = validate(doc, "TopType");
ok
is a bool
which is true
if doc
is a valid YAML representation of the Top
type.
error_message
is an std::optional<std::string>
that holds the error message if ok == false
. Otherwise, error_message
will be std::nullopt
.
You can use the script validate.sh
to validate a YAML document against a YAVL specification:
$ ./validate.sh examples/simple_sample_correct.yaml examples/simple_spec.yaml TopType
Compiling spec...
Compiling shared object...
Validating...
Validation successful!
โ ๏ธ Attention: This is potentially dangerous!validate.sh
will compile your spec to a header, useg++
to create a dynamic library, andyavl-validate-sample
will execute binary code from this library without any checks. Never execute this script in a working environment you don't trust 100%! This is purely for demonstration purposes.
yaml-cpp-0.7.0
make
g++
pkg-config
bash
python3
pyyaml
The original yavl-cpp
was written by Ranganathan Sankaralingam. It has been auto-converted using git-svn
from Google Code Archive. The original branches trunk
and yatc
are archived.
The master
branch is based on the yatc
branch. Since then, the project was developed in quite a different direction (aside from the name and the general idea, nothing of the original code is left) by me (Ruben Felgenhauer).
The new YAVL syntax is inspired by the yavl-cpp
fork by Pieter Lexis.
This code uses some C++ classes by Thibaut Goetghebuer-Planchon.