Commit 1a35bf3b authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'issue/956'

parents 1b1aae99 18e800a8
......@@ -32,6 +32,8 @@ IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MacroBlockBegin: "^BEGIN_STATE$"
MacroBlockEnd: "^END_STATE$"
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 1000
......
......@@ -654,11 +654,6 @@ message(STATUS "Set release version for all documentation to ${CAF_RELEASE}.")
# -- Setup for building manual and API documentation ---------------------------
# we need the examples and some headers relative to the build/doc/tex directory
file(COPY examples/ DESTINATION examples)
file(COPY libcaf_core/caf/exit_reason.hpp DESTINATION libcaf_core/caf/)
file(COPY libcaf_core/caf/sec.hpp DESTINATION libcaf_core/caf/)
add_subdirectory(doc)
################################################################################
......
cmake_minimum_required(VERSION 2.8.12)
project(doc NONE)
add_custom_target(doc)
# -- list all .tex source files ------------------------------------------------
......@@ -34,20 +30,22 @@ set(sources
tex/Testing.tex
)
# -- process .in files -----------------------------------------------------
# -- create target folders -----------------------------------------------------
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/tex")
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rst")
# -- process .in files ---------------------------------------------------------
configure_file("cmake/Doxyfile.in"
"${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
@ONLY)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/tex")
configure_file("cmake/variables.tex.in"
"${CMAKE_CURRENT_BINARY_DIR}/tex/variables.tex"
@ONLY)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rst")
configure_file("cmake/conf.py.in"
"${CMAKE_CURRENT_BINARY_DIR}/rst/conf.py"
@ONLY)
......@@ -60,6 +58,50 @@ configure_file("cmake/index_header.rst.in"
"${CMAKE_CURRENT_BINARY_DIR}/rst/index_header.rst"
@ONLY)
# -- generate .rst files -------------------------------------------------------
add_executable(caf-generate-rst cmake/caf-generate-rst.cpp)
target_link_libraries(caf-generate-rst
${CAF_EXTRA_LDFLAGS}
${CAF_LIBRARIES}
${PTHREAD_LIBRARIES})
add_custom_target(rst)
add_dependencies(doc rst)
function(convert_to_rst tex_file)
get_filename_component(file_name "${tex_file}" NAME_WE)
set(input "${CMAKE_CURRENT_SOURCE_DIR}/tex/${tex_file}")
set(rst_file "${file_name}.rst")
set(output "${CMAKE_CURRENT_BINARY_DIR}/rst/${rst_file}")
add_custom_command(OUTPUT "${output}"
COMMAND
caf-generate-rst
-o "${output}"
-i "${input}"
-r "${PROJECT_SOURCE_DIR}"
DEPENDS caf-generate-rst "${input}")
add_custom_target("${rst_file}" DEPENDS "${output}")
add_dependencies(rst "${rst_file}")
endfunction()
foreach(filename ${sources})
get_filename_component(filename_no_dir "${filename}" NAME)
convert_to_rst("${filename_no_dir}")
endforeach()
# generate index.rst file from manual.tex
add_custom_target("index.rst"
DEPENDS "tex/manual.tex"
COMMAND "python"
"${CMAKE_SOURCE_DIR}/scripts/make_index_rst.py"
"${CMAKE_CURRENT_BINARY_DIR}/rst/index.rst"
"${CMAKE_SOURCE_DIR}/doc/tex/manual.tex"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rst")
add_dependencies(rst "index.rst")
# -- Doxygen setup -------------------------------------------------------------
find_package(Doxygen)
......@@ -76,23 +118,6 @@ else()
add_dependencies(doc doxygen)
endif()
# -- Pandoc utility macro ------------------------------------------------------
macro(generate_rst texfile)
get_filename_component(rstfile_we "${texfile}" NAME_WE)
set(rstfile "${rstfile_we}.rst")
set(bin_texfile "${CMAKE_CURRENT_BINARY_DIR}/${texfile}")
add_custom_target("${rstfile}"
DEPENDS "${bin_texfile}"
COMMAND ${PANDOC_EXECUTABLE}
"--filter=${CMAKE_SOURCE_DIR}/scripts/pandoc-filter.py"
--wrap=none -f latex
-o "${CMAKE_CURRENT_BINARY_DIR}/rst/${rstfile}"
"${bin_texfile}"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rst")
add_dependencies(rst "${rstfile}")
endmacro()
# -- LaTeX setup ---------------------------------------------------------------
if (CAF_BUILD_TEX_MANUAL)
......@@ -108,35 +133,5 @@ if (CAF_BUILD_TEX_MANUAL)
FORCE_PDF
TARGET_NAME manual)
add_dependencies(doc manual)
find_program(PANDOC_EXECUTABLE pandoc)
if(NOT EXISTS ${PANDOC_EXECUTABLE})
message(STATUS "Pandoc not found, skip generating reFormattedText version of the manual.")
else()
execute_process(COMMAND "python" "-c"
"from pandocfilters import toJSONFilter; print('ok')"
RESULT_VARIABLE has_pandocfilters
OUTPUT_QUIET
ERROR_QUIET)
if(NOT ${has_pandocfilters} EQUAL 0)
message(STATUS "Python with pandocfilters not found, skip generating reFormattedText version of the manual.")
else()
message(STATUS "Add optional target: rst.")
add_custom_target(rst)
add_dependencies(doc rst)
# generate .rst files for individual sections
foreach(texfile ${sources})
generate_rst(${texfile})
endforeach()
# generate index.rst file from manual.tex
add_custom_target("index.rst"
DEPENDS "tex/manual.tex"
COMMAND "python"
"${CMAKE_SOURCE_DIR}/scripts/make_index_rst.py"
"${CMAKE_CURRENT_BINARY_DIR}/rst/index.rst"
"${CMAKE_SOURCE_DIR}/doc/tex/manual.tex"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rst")
add_dependencies(rst "index.rst")
endif()
endif()
endif()
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include <ctype.h>
#include <stack>
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/config.hpp"
#include "caf/detail/parser/chars.hpp"
#include "caf/detail/parser/read_atom.hpp"
#include "caf/detail/parser/read_bool.hpp"
#include "caf/detail/parser/read_number_or_timespan.hpp"
#include "caf/detail/parser/read_string.hpp"
#include "caf/detail/parser/read_uri.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/exec_main.hpp"
#include "caf/pec.hpp"
#include "caf/uri_builder.hpp"
namespace {
void trim(std::string& s) {
auto not_space = [](char c) { return isspace(c) == 0; };
s.erase(s.begin(), find_if(s.begin(), s.end(), not_space));
s.erase(find_if(s.rbegin(), s.rend(), not_space).base(), s.end());
}
template <class>
struct type_name;
template <class>
struct is_inline;
#define DECLARE_NAMED_STRUCT(type, inline_flag) \
struct type; \
template <> \
struct type_name<type> { \
static constexpr const char* value = #type; \
}; \
template <> \
struct is_inline<type> { \
static constexpr bool value = inline_flag; \
}
DECLARE_NAMED_STRUCT(section, false);
DECLARE_NAMED_STRUCT(subsection, false);
DECLARE_NAMED_STRUCT(subsubsection, false);
DECLARE_NAMED_STRUCT(paragraph, false);
DECLARE_NAMED_STRUCT(label, false);
DECLARE_NAMED_STRUCT(see, true);
DECLARE_NAMED_STRUCT(sref, true);
DECLARE_NAMED_STRUCT(ref, true);
DECLARE_NAMED_STRUCT(verbatim, false);
DECLARE_NAMED_STRUCT(lstlisting, false);
DECLARE_NAMED_STRUCT(lstinline, true);
DECLARE_NAMED_STRUCT(text, true);
DECLARE_NAMED_STRUCT(texttt, true);
DECLARE_NAMED_STRUCT(textbf, true);
DECLARE_NAMED_STRUCT(textit, true);
DECLARE_NAMED_STRUCT(href, true);
DECLARE_NAMED_STRUCT(item, false);
DECLARE_NAMED_STRUCT(itemize, false);
DECLARE_NAMED_STRUCT(enumerate, false);
DECLARE_NAMED_STRUCT(tabular, false);
DECLARE_NAMED_STRUCT(cppexample, false);
DECLARE_NAMED_STRUCT(iniexample, false);
DECLARE_NAMED_STRUCT(sourcefile, false);
DECLARE_NAMED_STRUCT(singlefig, false);
DECLARE_NAMED_STRUCT(experimental, true);
using node = caf::variant<section, subsection, subsubsection, paragraph, label,
see, sref, ref, verbatim, lstlisting, lstinline, text,
texttt, textbf, textit, href, item, itemize,
enumerate, tabular, cppexample, iniexample,
sourcefile, singlefig, experimental>;
struct section {
std::string name;
};
struct subsection {
std::string name;
};
struct subsubsection {
std::string name;
};
struct paragraph {
std::string name;
};
struct label {
std::string name;
};
struct see {
std::string link;
};
struct sref {
std::string link;
};
struct ref {
std::string link;
};
struct verbatim {
std::string block;
};
struct lstlisting {
std::string block;
};
struct lstinline {
std::string str;
};
struct text {
std::string str;
};
struct texttt {
std::string str;
};
struct textbf {
std::string str;
};
struct textit {
std::string str;
};
struct href {
std::string url;
std::string str;
};
struct item {
std::vector<node> nodes;
};
struct itemize {
std::vector<item> items;
};
struct enumerate {
std::vector<item> items;
};
struct tabular {
using column_type = item;
using row_type = std::vector<column_type>;
std::vector<row_type> rows;
};
struct cppexample {
std::string lines;
std::string file;
};
struct iniexample {
std::string lines;
std::string file;
};
struct sourcefile {
std::string lines;
std::string file;
};
struct singlefig {
std::string file;
std::string caption;
std::string label;
};
struct experimental {
// nop
};
#define MAKE_NODE_0(type) \
if (name == #type) { \
if (args.size() != 0) \
throw std::runtime_error("expected exactly 0 argument for " #type \
", got: " \
+ caf::deep_to_string(args)); \
return type{}; \
}
#define MAKE_NODE_1(type) \
if (name == #type) { \
if (args.size() != 1) \
throw std::runtime_error("expected exactly 1 argument for " #type \
", got: " \
+ caf::deep_to_string(args)); \
return type{std::move(args[0])}; \
}
#define MAKE_NODE_2(type) \
if (name == #type) { \
if (args.size() != 2) \
throw std::runtime_error("expected exactly 2 argument for " #type \
", got: " \
+ caf::deep_to_string(args)); \
return type{std::move(args[0]), std::move(args[1])}; \
}
#define MAKE_NODE_3(type) \
if (name == #type) { \
if (args.size() != 3) \
throw std::runtime_error("expected exactly 3 argument for " #type \
", got: " \
+ caf::deep_to_string(args)); \
return type{std::move(args[0]), std::move(args[1]), std::move(args[2])}; \
}
#define MAKE_LINES_AND_FILE_NODE(type) \
if (name == #type) { \
if (args.size() == 1) { \
return type{std::string{}, std::move(args[0])}; \
} else if (args.size() == 2) { \
return type{std::move(args[0]), std::move(args[1])}; \
} \
throw std::runtime_error("expected 1 or 2 arguments for " #type); \
}
node make_node(const std::string& name, std::vector<std::string> args) {
MAKE_NODE_1(section)
MAKE_NODE_1(subsection)
MAKE_NODE_1(subsubsection)
MAKE_NODE_1(paragraph)
MAKE_NODE_1(label)
MAKE_NODE_1(see)
MAKE_NODE_1(sref)
MAKE_NODE_1(ref)
MAKE_NODE_1(verbatim)
MAKE_NODE_1(lstlisting)
MAKE_NODE_1(lstinline)
MAKE_NODE_1(texttt)
MAKE_NODE_1(textbf)
MAKE_NODE_1(textit)
MAKE_NODE_2(href)
MAKE_LINES_AND_FILE_NODE(cppexample)
MAKE_LINES_AND_FILE_NODE(iniexample)
MAKE_LINES_AND_FILE_NODE(sourcefile)
MAKE_NODE_3(singlefig)
MAKE_NODE_0(experimental)
if (name == "emph")
return make_node("textit", std::move(args));
throw std::runtime_error("unrecognized command: " + name
+ caf::deep_to_string(args));
}
bool is_ignored_node(const std::string& name,
const std::vector<std::string>& args) {
if (args.size() == 0)
return name == "clearpage" || name == "textwidth";
if (args.size() == 1)
return (name == "begin" || name == "end")
&& (args[0] == "center" || args[0] == "footnotesize");
return false;
}
struct abstract_consumer {
public:
virtual ~abstract_consumer() {
// nop
}
virtual void consume(node x) = 0;
};
template <class Result>
struct list_builder : abstract_consumer {
using result_type = Result;
abstract_consumer* consumer;
Result result;
bool finalized = false;
explicit list_builder(abstract_consumer* ptr) : consumer(ptr) {
// nop
}
void finalize() {
consumer->consume(std::move(result));
finalized = true;
}
void consume(node x) override {
// The command parser might pass whitespaces to this builder after seeing
// the end command.
if (finalized) {
consumer->consume(std::move(x));
return;
}
if (result.items.empty())
throw std::runtime_error("expected \\item as first token for list block");
result.items.back().nodes.emplace_back(std::move(x));
}
void cmd(const std::string& name, std::vector<std::string> args) {
if (name == "end" && args.size() == 1
&& args[0] == type_name<Result>::value) {
finalize();
} else if (name == "item") {
result.items.emplace_back();
} else {
consumer->consume(make_node(name, std::move(args)));
}
}
};
struct tabular_builder : abstract_consumer {
abstract_consumer* consumer;
tabular result;
bool finalized = false;
explicit tabular_builder(abstract_consumer* consumer) : consumer(consumer) {
next_row();
}
void consume(node x) override {
// The command parser might pass whitespaces to this builder after seeing
// the end command.
if (finalized)
consumer->consume(std::move(x));
else
result.rows.back().back().nodes.emplace_back(std::move(x));
}
void next_column() {
result.rows.back().emplace_back();
}
void next_row() {
result.rows.emplace_back();
next_column();
}
void cmd(const std::string& name, std::vector<std::string> args) {
if (name == "hline") {
// drop
} else if (name == "end" && args.size() == 1 && args[0] == "tabular") {
if (result.rows.back().empty()) {
result.rows.pop_back();
if (result.rows.empty())
throw std::runtime_error("empty table");
}
consumer->consume(std::move(result));
finalized = true;
} else {
consume(make_node(name, std::move(args)));
}
}
};
} // namespace
CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
namespace {
using std::string;
using namespace caf;
using namespace caf::detail;
using namespace caf::detail::parser;
template <class State, class Consumer>
void read_tex_comment(State& ps, Consumer&&) {
start();
term_state(init){transition(done, '\n') transition(init)} term_state(done) {
// nop
}
fin();
}
template <class State, class Consumer>
void read_tex_verbatim(State& ps, Consumer&& consumer, const string& cmd_name) {
string verbatim;
string cmd;
string end_of_command = "end{" + cmd_name;
auto flush_cmd = [&] {
verbatim += '\\';
verbatim += cmd;
cmd.clear();
};
auto guard = make_scope_guard([&] {
if (ps.code <= pec::trailing_character) {
std::vector<string> args;
args.emplace_back(std::move(verbatim));
consumer.cmd(cmd_name, std::move(args));
}
});
// clang-format off
start();
state(init) {
transition(read_end_verbatim, "\\");
transition(init, any_char, verbatim += ch)
}
state(read_end_verbatim) {
transition_if(cmd == end_of_command, done, "}")
transition(init, "}", flush_cmd());
transition(read_end_verbatim, "\\", flush_cmd());
transition(read_end_verbatim, any_char, cmd += ch)
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
template <class State, class Consumer>
void read_tex_list(State& ps, Consumer&& consumer, const string& cmd_name);
template <class State, class Consumer>
void read_tex_tabular(State& ps, Consumer&& consumer);
template <class State, class Consumer>
void read_tex_command(State& ps, Consumer&& consumer) {
string cmd;
string spaces;
std::vector<string> args;
const char* stop_chars;
auto add_arg = [&](const char* closing_char) {
spaces.clear();
stop_chars = closing_char;
args.emplace_back();
};
auto is_verbatim_cmd = [&] {
if (args.size() != 1)
return false;
return cmd == "begin" && (args[0] == "verbatim" || args[0] == "lstlisting");
};
auto is_list_cmd = [&] {
if (args.size() != 1)
return false;
return cmd == "begin" && (args[0] == "itemize" || args[0] == "enumerate");
};
auto is_tabular_cmd = [&] {
if (args.size() != 2)
return false;
return cmd == "begin" && args[0] == "tabular";
};
auto guard = make_scope_guard([&] {
if (is_ignored_node(cmd, args))
return;
if (cmd.empty()) {
ps.code = pec::unexpected_eof;
return;
}
if (ps.code <= pec::trailing_character) {
consumer.cmd(cmd, std::move(args));
if (!spaces.empty())
consumer.consume(text{std::move(spaces)});
}
});
// clang-format off
start();
state(init) {
epsilon(read_command)
}
term_state(read_command) {
fsm_epsilon(read_tex_comment(ps, consumer), read_command, '%')
fsm_epsilon_if(is_verbatim_cmd(), read_tex_verbatim(ps, consumer, args[0]), done, any_char)
fsm_epsilon_if(is_list_cmd(), read_tex_list(ps, consumer, args[0]), done, any_char)
fsm_epsilon_if(is_tabular_cmd(), read_tex_tabular(ps, consumer), done, any_char)
transition_if(args.empty() && spaces.empty(), read_command, alphanumeric_chars, cmd += ch)
transition(read_command_arg, "[", add_arg("]"))
transition(read_command_arg, "{", add_arg("}"))
transition(read_command_arg, "`", add_arg("`"))
transition(read_command_arg, "^", add_arg("^"))
transition(read_command, " \t\n", spaces += ch)
}
state(read_command_arg) {
fsm_epsilon(read_tex_comment(ps, consumer), read_command_arg, '%')
transition(read_command, stop_chars)
transition(read_command_arg, any_char, args.back() += ch)
}
term_state(done) {
guard.disable();
}
fin();
// clang-format on
}
/// Reads an .tex formatted input file for the CAF manual.
template <class State, class Consumer>
void read_tex(State& ps, Consumer&& consumer) {
string str;
auto consume_str = [&] {
if (!str.empty()) {
consumer.consume(text{std::move(str)});
str.clear();
}
};
auto guard = make_scope_guard([&] {
if (ps.code <= pec::trailing_character)
consume_str();
});
// clang-format off
start();
term_state(init) {
fsm_transition(read_tex_comment(ps, consumer), init, '%')
transition(start_escaping, "\\")
transition(init, "~", str += ' ')
transition(init, any_char, str += ch)
}
state(start_escaping) {
transition(init, "\\", str += '\\')
transition(init, "%", str += '%')
fsm_epsilon(read_tex_command(ps, consumer), init, any_char, consume_str())
}
fin();
// clang-format on
}
template <class State, class Consumer>
void read_tex_list(State& ps, Consumer& consumer) {
string str;
auto consume_str = [&] {
if (!str.empty()) {
consumer.consume(text{std::move(str)});
str.clear();
}
};
auto before_first_item = [&] { return consumer.result.items.empty(); };
// clang-format off
start();
state(init) {
fsm_transition(read_tex_comment(ps, consumer), init, '%')
transition(start_escaping, "\\")
transition_if(!before_first_item(), init, "~", str += ' ')
transition_if(!before_first_item(), init, any_char, str += ch)
transition_if(before_first_item(), init, " \t\n")
}
state(start_escaping) {
transition_if(!before_first_item(), init, "\\", str += '\\')
transition_if(!before_first_item(), init, "%", str += '%')
fsm_epsilon(read_tex_command(ps, consumer), after_cmd, any_char, consume_str())
}
unstable_state(after_cmd) {
epsilon_if(consumer.finalized, done, any_char)
epsilon(init, any_char)
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
template <class State, class Consumer>
void read_tex_list(State& ps, Consumer&& consumer, const string& cmd_name) {
if (cmd_name == "itemize") {
list_builder<itemize> builder{&consumer};
read_tex_list(ps, builder);
} else if (cmd_name == "enumerate") {
list_builder<enumerate> builder{&consumer};
read_tex_list(ps, builder);
} else {
throw std::logic_error("expected itemize or enumerate");
}
}
template <class State, class Consumer>
void read_tex_tabular(State& ps, Consumer&& consumer) {
tabular_builder builder{&consumer};
string str;
auto consume_str = [&] {
if (!str.empty()) {
builder.consume(text{str});
str.clear();
}
};
auto next_column = [&] {
consume_str();
builder.next_column();
};
auto next_row = [&] {
consume_str();
builder.next_row();
};
// clang-format off
start();
state(init) {
fsm_transition(read_tex_comment(ps, builder), init, '%')
transition(start_escaping, "\\")
transition(init, "&", next_column())
transition(init, "~", str += ' ')
transition(init, any_char, str += ch)
}
state(start_escaping) {
transition(init, "\\", next_row())
transition(init, "%", str += '%')
fsm_epsilon(read_tex_command(ps, builder), after_cmd, any_char, consume_str())
}
unstable_state(after_cmd) {
epsilon_if(builder.finalized, done, any_char)
epsilon(init, any_char)
}
term_state(done) {
// nop
}
fin();
// clang-format on
}
} // namespace
#include "caf/detail/parser/fsm_undef.hpp"
CAF_POP_WARNINGS
namespace {
struct file_iter {
std::istream* f;
char ch;
explicit file_iter(std::istream* f) : f(f) {
f->get(ch);
}
file_iter() : f(nullptr), ch('\0') {
// nop
}
file_iter(const file_iter&) = default;
file_iter& operator=(const file_iter&) = default;
char operator*() const {
return ch;
}
file_iter& operator++() {
f->get(ch);
return *this;
}
};
struct file_sentinel {};
bool operator!=(file_iter iter, file_sentinel) {
return !iter.f->fail();
}
class rst_writer;
class rst_writer_state : public abstract_consumer {
public:
rst_writer_state(rst_writer* parent) : parent_(parent) {
// nop
}
virtual ~rst_writer_state() {
// nop
}
virtual const char* name() const noexcept = 0;
virtual void exit();
rst_writer* parent() const noexcept {
return parent_;
};
std::ostream& out();
protected:
rst_writer* parent_;
};
struct parse_failure : std::runtime_error {
using super = std::runtime_error;
const char* state_name;
parse_failure(const char* state_name, const std::string& what)
: super(what), state_name(state_name) {
// nop
}
template <class... Ts>
[[noreturn]] static void raise(const char* state_name, const Ts&... xs) {
throw parse_failure(state_name,
deep_to_string(std::forward_as_tuple(xs...)));
}
};
template <class Subtype>
class rst_writer_state_base : public rst_writer_state {
public:
using rst_writer_state::rst_writer_state;
void consume(node x) override {
current_node_ = &x;
visit(dref(), x);
current_node_ = nullptr;
}
protected:
template <class T, class... Ts>
void epsilon(const std::unique_ptr<T>& target, Ts&&... xs) {
auto& st = target->parent()->state;
if (st)
st->exit();
st = target.get();
target->entry(std::forward<Ts>(xs)...);
target->consume(std::move(*current_node_));
}
template <class T>
void unexpected(T&) {
auto cstr = type_name<T>::value;
parse_failure::raise(dref().name(), "unexpected command: ", cstr);
}
private:
Subtype& dref() {
return static_cast<Subtype&>(*this);
}
node* current_node_ = nullptr;
};
template <class T, class... Ts>
void transition(const std::unique_ptr<T>& target, Ts&&... xs) {
auto& st = target->parent()->state;
if (st)
st->exit();
st = target.get();
target->entry(std::forward<Ts>(xs)...);
}
#define DECLARE_STATE(name) \
struct name##_state; \
std::unique_ptr<name##_state> name; \
void make_##name()
class rst_writer : public abstract_consumer {
public:
using state_ptr = std::unique_ptr<rst_writer_state>;
rst_writer() : state(nullptr) {
make_await_section();
make_await_section_label();
make_read_body();
transition(await_section);
}
~rst_writer() {
state->exit();
}
void consume(node x) override {
state->consume(std::move(x));
}
void cmd(const std::string& name, std::vector<std::string> args) {
consume(make_node(name, std::move(args)));
}
std::string project_root;
rst_writer_state* state;
std::ofstream out;
DECLARE_STATE(await_section);
DECLARE_STATE(await_section_label);
DECLARE_STATE(read_body);
};
std::ostream& rst_writer_state::out() {
return parent_->out;
}
void rst_writer_state::exit() {
// customization point
}
#define BEGIN_STATE(type) \
struct rst_writer::type##_state \
: rst_writer_state_base<rst_writer::type##_state> { \
using super = rst_writer_state_base<rst_writer::type##_state>; \
type##_state(rst_writer* parent) : super(parent) { \
} \
const char* name() const noexcept override { \
return #type; \
}
#define END_STATE(type) \
} \
; \
void rst_writer::make_##type() { \
type.reset(new type##_state(this)); \
}
BEGIN_STATE(await_section)
void entry() {
// nop
}
void operator()(section& x) {
transition(parent_->await_section_label, x.name, '=');
}
template <class T>
void operator()(T& x) {
unexpected(x);
}
END_STATE(await_section)
struct string_stream {
std::string& result;
};
string_stream& operator<<(string_stream& out, string_view str) {
out.result.insert(out.result.end(), str.begin(), str.end());
return out;
}
string_stream& operator<<(string_stream& out, char c) {
out.result += c;
return out;
}
namespace rst_ops {
template <class Out>
Out& operator<<(Out& out, text& x) {
// Trim all whitespaces on the left and right but one.
if (x.str.empty())
return out;
auto no_space = [](char c) { return c != ' '; };
auto i = std::find_if(x.str.begin(), x.str.end(), no_space);
auto e = std::find_if(x.str.rbegin(), x.str.rend(), no_space).base();
if (i > e)
return out << ' ';
if (i != x.str.begin())
out << ' ';
for (; i != e; ++i)
out << *i;
if (e != x.str.end())
out << ' ';
return out;
}
template <class Out>
Out& operator<<(Out& out, see& x) {
return out << x.link << '_';
}
template <class Out>
Out& operator<<(Out& out, sref& x) {
return out << x.link << '_';
}
template <class Out>
Out& operator<<(Out& out, ref& x) {
return out << x.link << '_';
}
template <class Out>
Out& operator<<(Out& out, lstinline& x) {
return out << "``" << x.str << "``";
}
template <class Out>
Out& operator<<(Out& out, texttt& x) {
return out << "``" << x.str << "``";
}
template <class Out>
Out& operator<<(Out& out, textbf& x) {
return out << "**" << x.str << "**";
}
template <class Out>
Out& operator<<(Out& out, textit& x) {
return out << "*" << x.str << "*";
}
template <class Out>
Out& operator<<(Out& out, href& x) {
return out << "`" << x.str << " <" << x.url << ">`_";
}
template <class Out>
Out& operator<<(Out& out, experimental&) {
return out << "\\ :sup:`experimental`\\ ";
}
} // namespace rst_ops
template <class Out>
struct rst_ops_visitor : abstract_consumer {
Out& out;
rst_ops_visitor(Out& out) : out(out) {
// nop
}
template <class T>
detail::enable_if_t<is_inline<T>::value> operator()(T& x) {
using namespace rst_ops;
out << x;
}
template <class T>
detail::enable_if_t<!is_inline<T>::value> operator()(T&) {
throw std::runtime_error("expected an inline command");
}
void consume(node x) override {
caf::visit(*this, x);
}
template <class T>
void consume(T x) {
(*this)(x);
}
void cmd(const std::string& name, std::vector<std::string> args) {
consume(make_node(name, std::move(args)));
}
};
BEGIN_STATE(await_section_label)
void entry(const std::string& section_name, char highlighting) {
spaces.clear();
this->section_name.clear();
this->highlighting = highlighting;
// TODO: The tokenzier should parse TeX inside section names, too. Remove
// this hack once the sections contains nodes instead of just a
// string.
string_stream str_out{this->section_name};
rst_ops_visitor<string_stream> v{str_out};
using iterator_type = std::string::const_iterator;
parser_state<iterator_type> res{section_name.begin(), section_name.end()};
read_tex(res, v);
}
void operator()(label& x) {
out() << ".. _" << x.name << ":\n\n"
<< section_name << '\n'
<< std::string(section_name.size(), highlighting) << "\n\n";
transition(parent_->read_body);
}
void operator()(text& x) {
// Ignore whitespaces between \section and \label commands.
if (x.str.empty())
return;
if (!spaces.empty()) {
x.str.insert(x.str.begin(), spaces.begin(), spaces.end());
delegate();
return;
}
if (std::all_of(x.str.begin(), x.str.end(), ::isspace))
spaces = std::move(x.str);
else
delegate();
}
template <class T>
void operator()(T&) {
delegate();
}
void delegate() {
out() << section_name << '\n'
<< std::string(section_name.size(), highlighting) << "\n\n";
epsilon(parent_->read_body);
}
std::string section_name;
std::string spaces;
char highlighting;
END_STATE(await_section_label)
BEGIN_STATE(read_body)
void entry() {
// nop
}
template <class T>
detail::enable_if_t<is_inline<T>::value> operator()(T& x) {
using namespace rst_ops;
out() << x;
}
template <class T>
detail::enable_if_t<!is_inline<T>::value> operator()(T& x) {
unexpected(x);
}
void operator()(subsection& x) {
transition(parent_->await_section_label, x.name, '-');
}
void operator()(subsubsection& x) {
transition(parent_->await_section_label, x.name, '~');
}
void operator()(paragraph& x) {
transition(parent_->await_section_label, x.name, '+');
}
void operator()(lstlisting& x) {
print_block(".. code-block:: C++", x.block);
}
void operator()(verbatim& x) {
print_block(".. ::", x.block);
}
void operator()(itemize& x) {
out() << "\n\n";
for (auto& i : x.items) {
out() << "* ";
for (auto& n : i.nodes)
visit(*this, n);
out() << '\n';
}
out() << '\n';
}
void operator()(enumerate& x) {
size_t num = 1;
out() << "\n\n";
for (auto& i : x.items) {
out() << num++ << ". ";
for (auto& n : i.nodes)
visit(*this, n);
out() << '\n';
}
out() << '\n';
}
void operator()(tabular& x) {
if (x.rows.empty() || x.rows[0].empty())
throw std::runtime_error("empty tabular");
// Convert the tabular into a string matrix.
std::vector<std::vector<std::string>> content;
content.reserve(x.rows.size());
auto num_columns = x.rows[0].size();
std::vector<size_t> column_sizes(num_columns);
for (auto& row : x.rows) {
// This hack makes sure we can handle \hline on the last line of a
// tabular. It silently drops anything with different column side, but a
// proper fix is not trivial.
if (row.size() != num_columns)
continue;
content.emplace_back();
auto& content_row = content.back();
content_row.resize(num_columns);
for (size_t col_index = 0; col_index < num_columns; ++col_index) {
auto& cell = content_row[col_index];
string_stream cell_out{cell};
rst_ops_visitor<string_stream> v{cell_out};
for (auto& node : row[col_index].nodes)
visit(v, node);
trim(cell);
column_sizes[col_index] = std::max(column_sizes[col_index],
cell.size());
}
}
// Output the matrix.
auto hline = [&] {
for (auto cs : column_sizes) {
out() << "+-";
for (size_t i = 0; i < cs; ++i)
out() << '-';
}
out() << "-+\n";
};
out() << "\n\n";
hline();
for (auto& row : content) {
for (size_t col_index = 0; col_index < row.size(); ++col_index) {
auto& column = row[col_index];
column.resize(column_sizes[col_index], ' ');
out() << "| " << column;
}
out() << " |\n";
hline();
}
out() << '\n';
}
void operator()(cppexample& x) {
auto path = parent_->project_root;
path += "/examples/";
path += x.file;
path += ".cpp";
std::ifstream in{path};
print_file(".. code-block:: c++", in, x.lines);
}
void operator()(iniexample& x) {
auto path = parent_->project_root;
path += "/examples/";
path += x.file;
path += ".ini";
std::ifstream in{path};
print_file(".. code-block:: ini", in, x.lines);
}
void operator()(sourcefile& x) {
auto path = parent_->project_root;
path += '/';
path += x.file;
std::ifstream in{path};
print_file(".. code-block:: c++", in, x.lines);
}
void operator()(singlefig& x) {
out() << ".. _" << x.label << ":\n\n"
<< ".. image:: " << x.file << ".png" << '\n'
<< " :alt: " << x.caption << "\n\n";
}
void print_block(const char* hdr, std::string& block) {
// Trim leading and trailing newlines.
while (block.size() > 0 && block.front() == '\n')
block.erase(block.begin());
while (block.size() > 0 && block.back() == '\n')
block.pop_back();
out() << "\n" << hdr << "\n\n";
out() << " ";
for (auto ch : block) {
if (ch == '\n')
out() << "\n ";
else
out() << ch;
}
out() << "\n\n";
}
void print_file(const char* hdr, std::ifstream& in, int first_line,
int last_line, int& line_num) {
std::string line;
while (line_num < first_line) {
if (!std::getline(in, line))
throw std::runtime_error("unexpected end of file");
++line_num;
}
out() << "\n" << hdr << "\n\n";
while (line_num < last_line) {
if (!std::getline(in, line))
break;
out() << " " << line << '\n';
++line_num;
}
out() << "\n\n";
}
void print_file(const char* hdr, std::ifstream& in,
const std::string& lines) {
int line_num = 1;
if (lines.empty()) {
print_file(hdr, in, 1, std::numeric_limits<int>::max(), line_num);
return;
}
std::vector<std::string> line_ranges;
split(line_ranges, lines, ",");
for (const auto& lr : line_ranges) {
std::vector<std::string> line_nums;
split(line_nums, lr, "-");
if (line_nums.size() != 2)
throw std::runtime_error("illegal line range");
print_file(hdr, in, std::stoi(line_nums[0]), std::stoi(line_nums[1]),
line_num);
}
}
END_STATE(read_body)
struct config : actor_system_config {
config() {
opt_group{custom_options_, "global"}
.add(input, "input,i", "input .tex file")
.add(output, "output,o", "output .rst file")
.add(project_root, "project-root,r", "project root for C++ examples");
}
std::string input;
std::string output;
std::string project_root;
};
} // namespace
int main(int argc, char** argv) {
// Read CAF configuration.
config cfg;
if (auto err = cfg.parse(argc, argv)) {
std::cerr << "unable to parse CAF config: " << cfg.render(err) << '\n';
return EXIT_FAILURE;
}
if (cfg.cli_helptext_printed)
return EXIT_SUCCESS;
if (cfg.input.empty() || cfg.output.empty()) {
std::cerr << "input or output path missing\n";
return EXIT_FAILURE;
}
rst_writer consumer;
consumer.out.open(cfg.output);
if (!consumer.out) {
std::cerr << "unable to open output file: " << cfg.output << '\n';
return EXIT_FAILURE;
}
consumer.project_root = cfg.project_root;
std::ifstream input{cfg.input};
parser_state<file_iter, file_sentinel> res{file_iter{&input}};
try {
read_tex(res, consumer);
if (res.i != res.e) {
std::cerr << "error in line " << res.line << " on column " << res.column
<< ": " << to_string(res.code) << '\n';
return EXIT_FAILURE;
}
} catch (parse_failure& x) {
std::cerr << "error in line " << res.line << " on column " << res.column
<< " while in state " << x.state_name << ": " << x.what() << '\n';
return EXIT_FAILURE;
} catch (std::exception& x) {
std::cerr << "error in line " << res.line << " on column " << res.column
<< ": " << x.what() << '\n';
return EXIT_FAILURE;
} catch (...) {
std::cerr << "unknown error in line " << res.line << " on column "
<< res.column << '\n';
return EXIT_FAILURE;
}
}
\newcommand{\cafrelease}{@CAF_RELEASE@}
\newcommand{\cafsha}{@CAF_SHA@}
\newcommand{\cafroot}{@PROJECT_SOURCE_DIR@}
......@@ -40,7 +40,7 @@ TCP and returns a broker managing this connection on success. Finally,
on success. There are no convenience functions spawn a UDP-based client or
server.
\subsection{Class \texttt{broker}}
\subsection{Class \lstinline^broker^}
\label{broker-class}
\begin{lstlisting}
......
......@@ -6,66 +6,59 @@ programming in CAF.
\subsection{Defining Message Handlers}
\begin{itemize}
\item C++ evaluates comma-separated expressions from left-to-right, using only
the last element as return type of the whole expression. This means that
message handlers and behaviors must \emph{not} be initialized like this:
\begin{lstlisting}
message_handler wrong = (
[](int i) { /*...*/ },
[](float f) { /*...*/ }
);
\end{lstlisting}
The correct way to initialize message handlers and behaviors is to either
use the constructor or the member function \lstinline^assign^:
\begin{lstlisting}
message_handler ok1{
[](int i) { /*...*/ },
[](float f) { /*...*/ }
};
C++ evaluates comma-separated expressions from left-to-right, using only the
last element as return type of the whole expression. This means that message
handlers and behaviors must \emph{not} be initialized like this:
message_handler ok2;
// some place later
ok2.assign(
[](int i) { /*...*/ },
[](float f) { /*...*/ }
);
\end{lstlisting}
\end{itemize}
\begin{lstlisting}
message_handler wrong = (
[](int i) { /*...*/ },
[](float f) { /*...*/ }
);
\end{lstlisting}
The correct way to initialize message handlers and behaviors is to either
use the constructor or the member function \lstinline^assign^:
\begin{lstlisting}
message_handler ok1{
[](int i) { /*...*/ },
[](float f) { /*...*/ }
};
message_handler ok2;
// some place later
ok2.assign(
[](int i) { /*...*/ },
[](float f) { /*...*/ }
);
\end{lstlisting}
\subsection{Event-Based API}
\begin{itemize}
\item The member function \lstinline^become^ does not block, i.e., always
returns immediately. Thus, lambda expressions should \textit{always} capture
by value. Otherwise, all references on the stack will cause undefined
behavior if the lambda expression is executed.
\end{itemize}
The member function \lstinline^become^ does not block, i.e., always returns
immediately. Thus, lambda expressions should \textit{always} capture by value.
Otherwise, all references on the stack will cause undefined behavior if the
lambda expression is executed.
\subsection{Requests}
\begin{itemize}
\item A handle returned by \lstinline^request^ represents \emph{exactly one}
response message. It is not possible to receive more than one response
message.
\item The handle returned by \lstinline^request^ is bound to the calling actor.
It is not possible to transfer a handle to a response to another actor.
\end{itemize}
A handle returned by \lstinline^request^ represents \emph{exactly one} response
message. It is not possible to receive more than one response message.
\clearpage
The handle returned by \lstinline^request^ is bound to the calling actor. It is
not possible to transfer a handle to a response to another actor.
\clearpage
\subsection{Sharing}
\begin{itemize}
\item It is strongly recommended to \textbf{not} share states between actors.
In particular, no actor shall ever access member variables or member
functions of another actor. Accessing shared memory segments concurrently
can cause undefined behavior that is incredibly hard to find and debug.
However, sharing \textit{data} between actors is fine, as long as the data
is \textit{immutable} and its lifetime is guaranteed to outlive all actors.
The simplest way to meet the lifetime guarantee is by storing the data in
smart pointers such as \lstinline^std::shared_ptr^. Nevertheless, the
recommended way of sharing informations is message passing. Sending the
same message to multiple actors does not result in copying the data several
times.
\end{itemize}
It is strongly recommended to \textbf{not} share states between actors. In
particular, no actor shall ever access member variables or member functions of
another actor. Accessing shared memory segments concurrently can cause undefined
behavior that is incredibly hard to find and debug. However, sharing
\textit{data} between actors is fine, as long as the data is \textit{immutable}
and its lifetime is guaranteed to outlive all actors. The simplest way to meet
the lifetime guarantee is by storing the data in smart pointers such as
\lstinline^std::shared_ptr^. Nevertheless, the recommended way of sharing
informations is message passing. Sending the same message to multiple actors
does not result in copying the data several times.
......@@ -98,7 +98,7 @@ knowing the proper handle type. Pointers must be converted to a handle via
\subsubsection{Spawning}
``Spawning'' an actor means to create and run a new actor.
\emph{Spawning} an actor means to create and run a new actor.
\subsubsection{Monitor}
\label{monitor}
......
......@@ -153,13 +153,15 @@ When passing the \lstinline^cells^ vector to our three different
implementations, we observe three outputs. Our \lstinline^waiting_testee^ actor
will always print:
{\footnotesize\begin{verbatim}
\begin{footnotesize}
\begin{verbatim}
cell #9 -> 16
cell #8 -> 9
cell #7 -> 4
cell #6 -> 1
cell #5 -> 0
\end{verbatim}}
\end{verbatim}
\end{footnotesize}
This is because \lstinline^await^ puts the one-shots handlers onto a stack and
enforces LIFO order by re-ordering incoming response messages.
......@@ -170,13 +172,15 @@ immediately.
Finally, the \lstinline^blocking_testee^ implementation will always print:
{\footnotesize\begin{verbatim}
\begin{footnotesize}
\begin{verbatim}
cell #5 -> 0
cell #6 -> 1
cell #7 -> 4
cell #8 -> 9
cell #9 -> 16
\end{verbatim}}
\end{verbatim}
\end{footnotesize}
Both event-based approaches send all requests, install a series of one-shot
handlers, and then return from the implementing function. In contrast, the
......
......@@ -103,7 +103,7 @@ because the actor objects themselves can get destroyed independently from their
control block. A weak reference is only formed by \lstinline^actor_addr^
\see{actor-address}.
\subsection{Converting Actor References with \texttt{actor\_cast}}
\subsection{Converting Actor References with \lstinline^actor_cast^}
\label{actor-cast}
The function \lstinline^actor_cast^ converts between actor pointers and
......
......@@ -124,20 +124,20 @@ User Manual\\
\newcommand{\cppexample}[2][]{%
\ifthenelse{\isempty{#1}}%
{\lstinputlisting{../../examples/#2.cpp}}%
{\lstinputlisting[language=C++,linerange={#1}]{../../examples/#2.cpp}}%
{\lstinputlisting{\cafroot/examples/#2.cpp}}%
{\lstinputlisting[language=C++,linerange={#1}]{\cafroot/examples/#2.cpp}}%
}
\newcommand{\iniexample}[2][]{%
\ifthenelse{\isempty{#1}}%
{\lstinputlisting[language=ini]{../../examples/#2.ini}}%
{\lstinputlisting[language=ini,linerange={#1}]{../../examples/#2.ini}}%
{\lstinputlisting[language=ini]{\cafroot/examples/#2.ini}}%
{\lstinputlisting[language=ini,linerange={#1}]{\cafroot/examples/#2.ini}}%
}
\newcommand{\sourcefile}[2][]{%
\ifthenelse{\isempty{#1}}%
{\lstinputlisting[language=C++]{../../#2}}%
{\lstinputlisting[language=C++,linerange={#1}]{../../#2}}%
{\lstinputlisting[language=C++]{\cafroot/#2}}%
{\lstinputlisting[language=C++,linerange={#1}]{\cafroot/#2}}%
}
% highlight for INI file syntax
......
......@@ -39,22 +39,48 @@
namespace caf {
namespace detail {
template <class T0, typename T1 = unit_t, typename T2 = unit_t,
typename T3 = unit_t, typename T4 = unit_t, typename T5 = unit_t,
typename T6 = unit_t, typename T7 = unit_t, typename T8 = unit_t,
typename T9 = unit_t, typename T10 = unit_t, typename T11 = unit_t,
typename T12 = unit_t, typename T13 = unit_t, typename T14 = unit_t,
typename T15 = unit_t, typename T16 = unit_t, typename T17 = unit_t,
typename T18 = unit_t, typename T19 = unit_t, typename T20 = unit_t>
template <class T0 = unit_t, class T1 = unit_t, class T2 = unit_t,
class T3 = unit_t, class T4 = unit_t, class T5 = unit_t,
class T6 = unit_t, class T7 = unit_t, class T8 = unit_t,
class T9 = unit_t, class T10 = unit_t, class T11 = unit_t,
class T12 = unit_t, class T13 = unit_t, class T14 = unit_t,
class T15 = unit_t, class T16 = unit_t, class T17 = unit_t,
class T18 = unit_t, class T19 = unit_t, class T20 = unit_t,
class T21 = unit_t, class T22 = unit_t, class T23 = unit_t,
class T24 = unit_t, class T25 = unit_t, class T26 = unit_t,
class T27 = unit_t, class T28 = unit_t, class T29 = unit_t>
struct variant_data {
union {
T0 v0; T1 v1; T2 v2;
T3 v3; T4 v4; T5 v5;
T6 v6; T7 v7; T8 v8;
T9 v9; T10 v10; T11 v11;
T12 v12; T13 v13; T14 v14;
T15 v15; T16 v16; T17 v17;
T18 v18; T19 v19; T20 v20;
T0 v0;
T1 v1;
T2 v2;
T3 v3;
T4 v4;
T5 v5;
T6 v6;
T7 v7;
T8 v8;
T9 v9;
T10 v10;
T11 v11;
T12 v12;
T13 v13;
T14 v14;
T15 v15;
T16 v16;
T17 v17;
T18 v18;
T19 v19;
T20 v20;
T21 v21;
T22 v22;
T23 v23;
T24 v24;
T25 v25;
T26 v26;
T27 v27;
T28 v28;
T29 v29;
};
variant_data() {
......@@ -86,6 +112,15 @@ struct variant_data {
CAF_VARIANT_DATA_GETTER(18)
CAF_VARIANT_DATA_GETTER(19)
CAF_VARIANT_DATA_GETTER(20)
CAF_VARIANT_DATA_GETTER(21)
CAF_VARIANT_DATA_GETTER(22)
CAF_VARIANT_DATA_GETTER(23)
CAF_VARIANT_DATA_GETTER(24)
CAF_VARIANT_DATA_GETTER(25)
CAF_VARIANT_DATA_GETTER(26)
CAF_VARIANT_DATA_GETTER(27)
CAF_VARIANT_DATA_GETTER(28)
CAF_VARIANT_DATA_GETTER(29)
};
struct variant_data_destructor {
......
......@@ -146,7 +146,7 @@ public:
// -- sanity checks ----------------------------------------------------------
static_assert(sizeof...(Ts) <= 20, "Too many template arguments given.");
static_assert(sizeof...(Ts) <= 30, "Too many template arguments given.");
static_assert(sizeof...(Ts) > 0, "No template argument given.");
......@@ -284,6 +284,16 @@ public:
CAF_VARIANT_CASE(17);
CAF_VARIANT_CASE(18);
CAF_VARIANT_CASE(19);
CAF_VARIANT_CASE(20);
CAF_VARIANT_CASE(21);
CAF_VARIANT_CASE(22);
CAF_VARIANT_CASE(23);
CAF_VARIANT_CASE(24);
CAF_VARIANT_CASE(25);
CAF_VARIANT_CASE(26);
CAF_VARIANT_CASE(27);
CAF_VARIANT_CASE(28);
CAF_VARIANT_CASE(29);
}
}
......@@ -481,6 +491,16 @@ inspect(Inspector& f, variant_writer<variant<Ts...>>& x) {
CAF_VARIANT_ASSIGN_CASE(17);
CAF_VARIANT_ASSIGN_CASE(18);
CAF_VARIANT_ASSIGN_CASE(19);
CAF_VARIANT_ASSIGN_CASE(20);
CAF_VARIANT_ASSIGN_CASE(21);
CAF_VARIANT_ASSIGN_CASE(22);
CAF_VARIANT_ASSIGN_CASE(23);
CAF_VARIANT_ASSIGN_CASE(24);
CAF_VARIANT_ASSIGN_CASE(25);
CAF_VARIANT_ASSIGN_CASE(26);
CAF_VARIANT_ASSIGN_CASE(27);
CAF_VARIANT_ASSIGN_CASE(28);
CAF_VARIANT_ASSIGN_CASE(29);
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment