#ifndef TSTRINGS_H_INCLUDED_ #define TSTRINGS_H_INCLUDED_ #define KBLIB_DEF_MACROS 1 #include "kblib/algorithm.h" #include "kblib/variant.h" #include "asyncpp/generator.h" #include "gsl/span" #include #include #include #include #include #include #include #include #include #include "containers.h" #include "error.h" #include "floating_array.h" #include "random.h" // TODO(killerbee): Switch to lazy evaluation // move to grammar file header // translate old-style doubled-character escapes into backslash escapes inline auto compat_filter(std::string_view in) -> std::string { std::string ret; bool esc = false; for (auto [c, i] : kblib::enumerate(in)) { if (esc) { if (c == in[i - 1]) { ret.push_back('\\'); ret.push_back(c); } else { ret.push_back(in[i - 1]); ret.push_back(c); } } if (kblib::contains("<>{}", c)) { esc = true; } else { ret.push_back(c); } } return ret; } // remove backslash-escape sequences [[nodiscard]] inline auto unescape(std::string_view in) -> std::string { bool b_esc = false; bool d_esc = false; std::string ret; char prev{}; for (auto& c : in) { if (d_esc) { if (c == prev) { ret.push_back(c); } else { ret.push_back(prev); ret.push_back(c); } d_esc = false; } else if (b_esc) { ret.push_back(c); b_esc = false; } else { if (c == '\\') { b_esc = true; } else if (c == '<' or c == '>') { d_esc = true; } else { ret.push_back(c); } } prev = c; } return ret; } auto argsDeclared(std::string_view name) -> std::pair; struct node; using arg_t = std::variant, double, const node*, wordgen_error /*, std::unique_ptr*/>; using argslist = std::vector; [[nodiscard]] auto stringize(const arg_t& arg) -> std::string; [[nodiscard]] auto stringize(arg_t&& arg) -> std::string; class transformer { public: [[nodiscard]] virtual arg_t operator()(argslist) const = 0; virtual ~transformer() noexcept = default; }; class Echo final : public transformer { public: [[nodiscard]] arg_t operator()(argslist v) const override; }; class template_machine; class Evaluator final : public transformer { public: Evaluator(template_machine* owner) : owner(owner) {} [[nodiscard]] arg_t operator()(argslist v) const override; private: template_machine* owner; }; namespace helpers { template using _t_h = std::is_convertible()(std::declval())), arg_t>; template > struct is_transformer : // can't find is_invocable // std::is_invocable public std::false_type {}; template struct is_transformer>> : public _t_h {}; template constexpr bool is_transformer_v = is_transformer::value; }; // namespace helpers template , int> = 0> class dynamic_transformer final : public transformer { public: dynamic_transformer(Callable _c) : c(std::move(_c)) {} [[nodiscard]] arg_t operator()(argslist v) const override { return c(std::move(v)); } private: Callable c; }; template [[nodiscard]] std::unique_ptr>> make_transformer(Callable&& c) { return std::make_unique>>( std::forward(c)); } enum class op : unsigned char { null = 0, // monostate text, // string push_func, // transformer* call_func, // monostate argument, // int special_argument, // string ellipsis, // string }; using op_data = std::variant; struct tstr_op { op type; op_data data; }; using tstr_ops = boost::container::small_vector; struct token { op type = op::null; std::string o_val; }; class datafile; struct counters { int depthLimit{}; int expansionsLimit{}; int* expansions{}; int depth{}; }; inline auto incr(counters& c) -> counters { ++(*c.expansions); return {c.depthLimit, c.expansionsLimit, c.expansions, c.depth + 1}; } using ArgsType = floating_array>>; auto copy(const ArgsType& special, const std::vector& vals) -> ArgsType; [[nodiscard]] auto default_transformers(template_machine*, datafile*, RandomGenerator&) -> tmap>; [[nodiscard]] auto default_names() -> std::vector>; class tnode; struct t_func { using func_t = std::function>) ->arg_t>; [[nodiscard]] auto eval(const template_machine& tm, const ArgsType& n_args, gsl::span> t_args) const -> arg_t { return func(tm, n_args, t_args); } std::string name; func_t func; }; [[nodiscard]] auto default_new_transformers(template_machine*, datafile*, RandomGenerator&) -> tmap; enum class expr_e { ellipsis, // monostate argument, // int text, // string func, // const t_func* special_argument, // string }; using tnode_v = std::variant; class tnode { public: tnode(const tnode_v& d, tnode* p) : data(d) , parent(p) {} tnode(tnode_v&& d, tnode* p) : data(std::move(d)) , parent(p) {} tnode_v data; tnode* parent; std::vector> children; }; auto parse_tstring(const template_machine& bm, std::string_view) -> tnode; auto eval(const template_machine& tm, const tnode& expr, const ArgsType& args) -> arg_t; inline auto eval(const template_machine& tm, const tnode& expr, const std::vector& args, const counters& c, int argc) -> arg_t; template auto r_visit(const tnode& expr, F&& visitor) -> void { kblib::visit_indexed(expr.data, visitor); for (auto& ch : expr.children) { r_visit(ch, visitor); } } auto print_texpr(const tnode& expr, std::ostream& os) -> std::ostream&; // One per data file class template_machine { private: tmap> transformers; tmap new_transformers; std::vector> arg_names; public: template_machine(datafile* owner, RandomGenerator& rng) : transformers(default_transformers(this, owner, rng)) , new_transformers(default_new_transformers(this, owner, rng)) , arg_names(default_names()) { transformers["."] = std::make_unique(); } // a must map to a contiguous sequence of negative numbers ending at -1 // template_machine(decltype(transformers) t, decltype(arg_names) a) // : transformers(std::move(t)) // , arg_names(std::move(a)) { // transformers["."] = std::make_unique(); // } [[nodiscard]] auto compile(const std::string&) const -> tstr_ops; [[nodiscard]] auto to_bytes_test(std::string_view) const -> asyncpp::generator; [[nodiscard]] std::string eval(const tstr_ops&, const std::vector&, const counters&, int argc) const; std::map variables; // Given the name of a special argument, return its index in an ArgsType /* Special argument documentation: * * #p: escaped | (pipe) character * #lt: escaped < (less than) character * #gt: escaped > (greater than) character * #b: escaped \ (backslash) character * * #d: current expansion depth * #D: maximum expansion depth * * #e: current expansion count * #E: maximum expansion count * * #c: number of normal arguments passed to this node * #C: number of positional arguments declared by this node * * #a: a list containing the full series of declared arguments * ...: a list containing all extra (variadic) arguments * #A: the concatenation of #a and ... * */ [[nodiscard]] auto special_argument(std::string_view) const -> int; [[nodiscard]] auto min_argument() const -> int; [[nodiscard]] auto get_args(const std::vector& p_args, const counters& c, int argc) const -> ArgsType; // Returns the transformer with the given name [[nodiscard]] auto lookup(const std::string& name) const noexcept(false) -> const transformer& { return *transformers.at(name); } // Returns the transformer with the given name [[nodiscard]] auto lookup_new(const std::string& name) const noexcept(false) -> const t_func* { return &new_transformers.at(name); } // Returns a string which is a valid name for t // O(transformers.size()), so call it sparingly // Note that the reverse mapping is not unique, and the returned string is // unspecified in the ambiguous case. That is: given: std::string fun; // reverse_lookup(lookup(fun)) == fun // is unspecified // While: // given: const transformer *t; // &lookup(reverse_lookup(*t)) == t // is guaranteed [[nodiscard]] auto reverse_lookup(const transformer& t) const noexcept(false) -> const std::string&; // register a new transformer by name // returns false if a transformer with that name already exists auto set(std::string key, std::unique_ptr val) -> bool { return transformers.emplace(std::move(key), std::move(val)).second; } // re-assign a name to a new transformer (key must already be associated with // a transformer) auto reset(const std::string& key, std::unique_ptr val) -> void { using namespace std::literals; // . is an invariant function assert(key != "."sv); auto it = transformers.find(key); assert(it != transformers.end()); // reset does not register new transformers it->second = std::move(val); } // clears a key auto unset(const std::string& key) -> void { using namespace std::literals; assert(key != "."sv); auto it = transformers.find(key); assert(it != transformers.end()); // key must be registered already transformers.erase(it); } private: friend auto token_to_bytecode(token in, const template_machine& b) -> tstr_op; }; inline auto eval(const template_machine& tm, const tnode& expr, const std::vector& args, const counters& c, int argc) -> arg_t { return eval(tm, expr, tm.get_args(args, c, argc)); } [[nodiscard]] auto to_string(const tstr_op& op, const template_machine& b) -> std::string; [[nodiscard]] auto to_string(const op_data& op, const template_machine& b) -> std::string; [[nodiscard]] auto serialize(const tstr_op& op, const template_machine& b) -> std::string; #endif // TSTRINGS_H_INCLUDED_