From 3d17d3ec8c5644db5ac2d2b50e12cf4a11b9ead3 Mon Sep 17 00:00:00 2001 From: rexy712 Date: Wed, 22 Jun 2022 13:42:53 -0700 Subject: [PATCH] Add rexy::format and rexy::print functions --- include/rexy/detail/format/arg_store.hpp | 105 +++ include/rexy/detail/format/arg_store.tpp | 89 +++ include/rexy/detail/format/basic_types.hpp | 100 +++ include/rexy/detail/format/basic_types.tpp | 55 ++ .../rexy/detail/format/context_handler.hpp | 97 +++ .../rexy/detail/format/context_handler.tpp | 162 +++++ include/rexy/detail/format/format_args.hpp | 130 ++++ include/rexy/detail/format/format_args.tpp | 236 +++++++ include/rexy/detail/format/format_context.hpp | 77 +++ include/rexy/detail/format/format_context.tpp | 70 ++ include/rexy/detail/format/format_error.hpp | 47 ++ include/rexy/detail/format/format_string.hpp | 40 ++ include/rexy/detail/format/format_string.tpp | 43 ++ include/rexy/detail/format/formatter.hpp | 173 +++++ include/rexy/detail/format/formatter.tpp | 647 ++++++++++++++++++ include/rexy/detail/format/internal_types.hpp | 83 +++ include/rexy/detail/format/named_args.hpp | 117 ++++ include/rexy/detail/format/named_args.tpp | 57 ++ include/rexy/detail/format/output_buffer.hpp | 131 ++++ include/rexy/detail/format/output_buffer.tpp | 156 +++++ include/rexy/detail/format/parse.hpp | 116 ++++ include/rexy/detail/format/parse.tpp | 355 ++++++++++ include/rexy/detail/format/parse_context.hpp | 61 ++ include/rexy/detail/format/parse_context.tpp | 75 ++ include/rexy/detail/format/specs_handler.hpp | 129 ++++ include/rexy/detail/format/specs_handler.tpp | 389 +++++++++++ include/rexy/detail/format/standard_types.hpp | 38 + include/rexy/detail/format/storage.hpp | 71 ++ include/rexy/detail/format/storage.tpp | 111 +++ include/rexy/detail/format/traits.hpp | 126 ++++ include/rexy/detail/format/utf_iterator.hpp | 91 +++ include/rexy/format.hpp | 147 ++++ include/rexy/format.tpp | 338 +++++++++ tests/CMakeLists.txt | 3 + tests/format.cpp | 111 +++ 35 files changed, 4776 insertions(+) create mode 100644 include/rexy/detail/format/arg_store.hpp create mode 100644 include/rexy/detail/format/arg_store.tpp create mode 100644 include/rexy/detail/format/basic_types.hpp create mode 100644 include/rexy/detail/format/basic_types.tpp create mode 100644 include/rexy/detail/format/context_handler.hpp create mode 100644 include/rexy/detail/format/context_handler.tpp create mode 100644 include/rexy/detail/format/format_args.hpp create mode 100644 include/rexy/detail/format/format_args.tpp create mode 100644 include/rexy/detail/format/format_context.hpp create mode 100644 include/rexy/detail/format/format_context.tpp create mode 100644 include/rexy/detail/format/format_error.hpp create mode 100644 include/rexy/detail/format/format_string.hpp create mode 100644 include/rexy/detail/format/format_string.tpp create mode 100644 include/rexy/detail/format/formatter.hpp create mode 100644 include/rexy/detail/format/formatter.tpp create mode 100644 include/rexy/detail/format/internal_types.hpp create mode 100644 include/rexy/detail/format/named_args.hpp create mode 100644 include/rexy/detail/format/named_args.tpp create mode 100644 include/rexy/detail/format/output_buffer.hpp create mode 100644 include/rexy/detail/format/output_buffer.tpp create mode 100644 include/rexy/detail/format/parse.hpp create mode 100644 include/rexy/detail/format/parse.tpp create mode 100644 include/rexy/detail/format/parse_context.hpp create mode 100644 include/rexy/detail/format/parse_context.tpp create mode 100644 include/rexy/detail/format/specs_handler.hpp create mode 100644 include/rexy/detail/format/specs_handler.tpp create mode 100644 include/rexy/detail/format/standard_types.hpp create mode 100644 include/rexy/detail/format/storage.hpp create mode 100644 include/rexy/detail/format/storage.tpp create mode 100644 include/rexy/detail/format/traits.hpp create mode 100644 include/rexy/detail/format/utf_iterator.hpp create mode 100644 include/rexy/format.hpp create mode 100644 include/rexy/format.tpp create mode 100644 tests/format.cpp diff --git a/include/rexy/detail/format/arg_store.hpp b/include/rexy/detail/format/arg_store.hpp new file mode 100644 index 0000000..f6ac780 --- /dev/null +++ b/include/rexy/detail/format/arg_store.hpp @@ -0,0 +1,105 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_ARG_STORE_HPP +#define REXY_DETAIL_FORMAT_ARG_STORE_HPP + +#include //size_t +#include "basic_types.hpp" //arg_info +#include "storage.hpp" //stored_type +#include "named_args.hpp" //count_named_args + +#include "../../string_view.hpp" + +namespace rexy::fmt::detail{ + + //Implementation of argument storage, erasing the need of passing around a parameter pack. Used in + //constructor of basic_format_args + template + class basic_format_arg_store + { + public: + using char_type = typename Context::char_type; + public: + static constexpr std::size_t num_args = sizeof...(Args); + static constexpr std::size_t num_named_args = count_named_args_v; + + struct format_data + { + friend class basic_format_arg_store; + private: + unsigned char m_data[ + (sizeof(arg_info) * num_args) + + (sizeof(basic_string_view) * num_named_args) + + (sizeof(stored_type_t) + ...) + ] = {}; + + arg_info* info(void){ + return reinterpret_cast(raw_info()); + } + constexpr unsigned char* data(void){ + return raw_info() + (sizeof(arg_info) * num_args); + } + + constexpr unsigned char* raw_info(void){ + return m_data; + } + constexpr const unsigned char* raw_info(void)const{ + return m_data; + } + + public: + const arg_info* info(void)const{ + return reinterpret_cast(raw_info()); + } + const unsigned char* data(void)const{ + return raw_info() + (sizeof(arg_info) * num_args); + } + + const unsigned char* raw(void)const{ + return m_data; + } + + }packed_data; + + basic_format_arg_store(Args&&... args); + + private: + template + void store_arg(std::size_t argnum, Arg&& arg); + template + void store_arg(std::size_t argnum, Arg&& arg); + + template + void store_args(SArgs&&... args); + }; + + //Specialized for empty parameter pack + template + class basic_format_arg_store + { + public: + static constexpr std::size_t num_args = 0; + }; + + + + +} + +#endif diff --git a/include/rexy/detail/format/arg_store.tpp b/include/rexy/detail/format/arg_store.tpp new file mode 100644 index 0000000..dc43262 --- /dev/null +++ b/include/rexy/detail/format/arg_store.tpp @@ -0,0 +1,89 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_ARG_STORE_TPP +#define REXY_DETAIL_FORMAT_ARG_STORE_TPP + +#include "arg_store.hpp" + +#include "storage.hpp" +#include "basic_types.hpp" + +#include "../../utility.hpp" //memcpy + +#include //forward +#include //addressof +#include //size_t + +namespace rexy::fmt::detail{ + + ///////////////////////basic_format_arg_store//////////////////////// + template + basic_format_arg_store::basic_format_arg_store(Args&&... args){ + store_args(std::forward(args)...); + } + template + template + void basic_format_arg_store::store_arg(std::size_t argnum, Arg&& arg){ + using stored_type = stored_type_t; + arg_info& ai = packed_data.info()[argnum]; + ai.type = map_to_storage_enum_v; + + //convert to one of the storable types + stored_type st{std::forward(arg)}; + + //save to the array of data + rexy::memcpy(packed_data.data()+ai.offset, std::addressof(st), sizeof(st)); + + //setup next entry's offset + if(argnum+1 < num_args){ + packed_data.info()[argnum+1].offset = ai.offset + sizeof(st); + } + } + template + template + void basic_format_arg_store::store_arg(std::size_t argnum, Arg&& arg){ + using stored_type = stored_type_t; + arg_info& ai = packed_data.info()[argnum]; + ai.type = map_to_storage_enum_v; + ai.named = true; + const std::size_t name_size = sizeof(format_string_view); + + //convert to one of the storable types + stored_type st{std::forward(arg).value}; + format_string_view name{arg.name.c_str(), arg.name.length()}; + + //save to the array of data + rexy::memcpy(packed_data.data()+ai.offset, std::addressof(name), name_size); + rexy::memcpy(packed_data.data()+ai.offset+name_size, std::addressof(st), sizeof(st)); + + //setup next entry's offset + if(argnum+1 < num_args){ + packed_data.info()[argnum+1].offset = ai.offset + sizeof(st) + name_size; + } + } + template + template + void basic_format_arg_store::store_args(SArgs&&... args){ + std::size_t argnum = 0; + (store_arg(argnum++, std::forward(args)), ...); + } + +} + +#endif diff --git a/include/rexy/detail/format/basic_types.hpp b/include/rexy/detail/format/basic_types.hpp new file mode 100644 index 0000000..b6fe713 --- /dev/null +++ b/include/rexy/detail/format/basic_types.hpp @@ -0,0 +1,100 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_BASIC_TYPES_HPP +#define REXY_DETAIL_FORMAT_BASIC_TYPES_HPP + +#include //size_t + +#include "storage.hpp" + +namespace rexy::fmt::detail{ + + static constexpr std::size_t invalid_arg_index = std::size_t{-1}; + + + template + struct format_string_view{ + const Char* name; + std::size_t length; + }; + + struct arg_info{ + storage_type type = storage_type::none_t; + std::size_t offset = 0; + bool named = false; + }; + + enum class alignment : int{ + none = 0, + left = '<', + right = '>', + center = '^' + }; + enum class presentation{ + default_t, + int_t, + char_t, + float_t, + string_t, + ptr_t + }; + + struct format_specs{ + int width = 0; + int precision = 0; + int type = 0; + alignment align = alignment::none; + presentation present = presentation::default_t; + int align_char = ' '; + int sign = 0; + bool locale = false; + bool alt_form = false; + bool zero_fill = false; + }; + enum class dyn_type{ + none = 0, + index, + string + }; + template + struct dynamic_format_specs : public format_specs{ + union dyn_val{ + std::size_t index = invalid_arg_index; + format_string_view name; + }dyn_width, dyn_precision; + + dyn_type width_type = dyn_type::index; + dyn_type precision_type = dyn_type::index; + }; + template + constexpr void normalize_dynamic_format_specs(FormatCtx& ctx, dynamic_format_specs& specs); + +} + +namespace rexy{ + + template + struct format_to_n_result{ + OutIt out; + std::size_t size; + }; + +} + +#endif diff --git a/include/rexy/detail/format/basic_types.tpp b/include/rexy/detail/format/basic_types.tpp new file mode 100644 index 0000000..961330b --- /dev/null +++ b/include/rexy/detail/format/basic_types.tpp @@ -0,0 +1,55 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_BASIC_TYPES_TPP +#define REXY_DETAIL_FORMAT_BASIC_TYPES_TPP + +#include "basic_types.hpp" + +#include "format_args.hpp" + +#include //size_t + +namespace rexy::fmt::detail{ + + template + constexpr void normalize_dynamic_format_specs(FormatCtx& ctx, dynamic_format_specs& specs){ + std::size_t index = 0; + if(specs.width_type == dyn_type::index){ + index = specs.dyn_width.index; + }else{ + const auto& name = specs.dyn_width.name; + index = ctx.arg_index(name.name, name.name + name.length); + } + specs.width = ((index != invalid_arg_index) + ? visit_format_arg(parse::dynamic_integer_retriever{}, ctx.arg(index)) + : specs.width); + if(specs.precision_type == dyn_type::index){ + index = specs.dyn_precision.index; + }else{ + const auto& name = specs.dyn_precision.name; + index = ctx.arg_index(name.name, name.name + name.length); + } + specs.precision = ((index != invalid_arg_index) + ? visit_format_arg(parse::dynamic_integer_retriever{}, ctx.arg(index)) + : specs.precision); + } + +} + +#endif diff --git a/include/rexy/detail/format/context_handler.hpp b/include/rexy/detail/format/context_handler.hpp new file mode 100644 index 0000000..2069b00 --- /dev/null +++ b/include/rexy/detail/format/context_handler.hpp @@ -0,0 +1,97 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_CONTEXT_HANDLER_HPP +#define REXY_DETAIL_FORMAT_CONTEXT_HANDLER_HPP + +#include //size_t + +#include "standard_types.hpp" +#include "internal_types.hpp" +#include "format_context.hpp" +#include "parse_context.hpp" +#include "format_args.hpp" + +#include "../../string_view.hpp" + +#include //locale + +namespace rexy::fmt::detail{ + + //Holds the format and parse contexts and performs parse+format calls for replacement fields + template + struct format_handler + { + public: + using char_type = Char; + using fmt_ctx_t = fmt_context_t; + using parse_ctx_t = parse_context_t; + using out_it_t = output_iterator_t; + using fmt_it_t = typename parse_ctx_t::iterator; + using handle_t = typename basic_format_arg::handle; + template + using fmter_t = typename fmt_ctx_t::template formatter_type; + + public: + fmt_ctx_t fmt_ctx; + parse_ctx_t parse_ctx; + + public: + format_handler(out_it_t output, basic_string_view fmt, basic_format_args args); + format_handler(out_it_t output, basic_string_view fmt, basic_format_args args, const std::locale& l); + constexpr fmt_it_t do_raw(fmt_it_t start, fmt_it_t fin); + constexpr fmt_it_t do_format_spec(fmt_it_t start, fmt_it_t fin, std::size_t id); + constexpr fmt_it_t do_empty_format(fmt_it_t start, fmt_it_t last, std::size_t id); + + constexpr std::size_t on_arg_id(void); + constexpr std::size_t on_arg_id(std::size_t i); + constexpr std::size_t on_arg_id(fmt_it_t start, fmt_it_t last); + }; + template + format_handler(output_iterator_t, basic_string_view, basic_format_args>) -> format_handler; + + //Constant-evaluated version of format_handler. Does not perform formatting but will complete a parsing + //pass during compile + template + class format_checker + { + public: + using char_type = Char; + using parse_ctx_t = parse_context_t; + using fmt_it_t = typename parse_ctx_t::iterator; + + parse_ctx_t parse_ctx; + + consteval format_checker(void) = default; + consteval format_checker(basic_string_view fmt, std::size_t numargs = 0); + constexpr fmt_it_t do_raw(fmt_it_t, fmt_it_t last); + constexpr fmt_it_t do_format_spec(fmt_it_t start, fmt_it_t last, std::size_t id); + constexpr void do_empty_format(fmt_it_t, fmt_it_t, std::size_t); + + constexpr std::size_t on_arg_id(void); + constexpr std::size_t on_arg_id(std::size_t i); + constexpr std::size_t on_arg_id(fmt_it_t start, fmt_it_t last); + + private: + template + constexpr fmt_it_t do_format_spec_impl(fmt_it_t start, fmt_it_t last, std::size_t id); + }; + +} + +#endif diff --git a/include/rexy/detail/format/context_handler.tpp b/include/rexy/detail/format/context_handler.tpp new file mode 100644 index 0000000..52dcdd0 --- /dev/null +++ b/include/rexy/detail/format/context_handler.tpp @@ -0,0 +1,162 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_CONTEXT_HANDLER_TPP +#define REXY_DETAIL_FORMAT_CONTEXT_HANDLER_TPP + +#include "context_handler.hpp" + +#include "parse_context.hpp" +#include "format_context.hpp" +#include "formatter.hpp" +#include "format_args.hpp" +#include "named_args.hpp" + +#include "format_error.hpp" + +#include "../../string_view.hpp" + +#include //locale +#include //size_t +#include //remove_cvref + +namespace rexy::fmt::detail{ + + ///////////////////////////////format_handler////////////////////////////////// + template + format_handler::format_handler(out_it_t output, basic_string_view fmt, basic_format_args args): + fmt_ctx(args, output), + parse_ctx(fmt){} + template + format_handler::format_handler(out_it_t output, basic_string_view fmt, basic_format_args args, const std::locale& l): + fmt_ctx(args, output, l), + parse_ctx(fmt){} + + template + constexpr auto format_handler::do_raw(fmt_it_t start, fmt_it_t fin) -> fmt_it_t{ + for(auto it = start;it != fin;++it){ + fmt_ctx.out() = *it; + } + return fin; + } + template + constexpr auto format_handler::do_format_spec(fmt_it_t start, fmt_it_t fin, std::size_t id) -> fmt_it_t{ + parse_ctx.advance_to(start); + const auto arg = fmt_ctx.arg(id); + format_specs specs; + visit_format_arg(format::arg_formatter{fmt_ctx, parse_ctx, specs}, arg); + return parse_ctx.begin(); + } + + template + constexpr auto format_handler::do_empty_format(fmt_it_t start, fmt_it_t last, std::size_t id) -> fmt_it_t{ + parse_ctx.advance_to(start); + const auto arg = fmt_ctx.arg(id); + visit_format_arg(format::empty_formatter{fmt_ctx, parse_ctx}, arg); + return parse_ctx.begin(); + } + + template + constexpr std::size_t format_handler::on_arg_id(void){ + return parse_ctx.next_arg_id(); + } + template + constexpr std::size_t format_handler::on_arg_id(std::size_t i){ + parse_ctx.check_arg_id(i); + return i; + } + template + constexpr std::size_t format_handler::on_arg_id(fmt_it_t start, fmt_it_t last){ + const auto id = fmt_ctx.arg_index(start, last); + parse_ctx.check_arg_id(id); + return id; + } + + + ///////////////////////////////format_checker////////////////////////////////// + template + consteval format_checker::format_checker(basic_string_view fmt, std::size_t numargs): + parse_ctx(fmt, numargs){} + template + constexpr auto format_checker::do_raw(fmt_it_t, fmt_it_t last) -> fmt_it_t{ + return last; + } + template + constexpr auto format_checker::do_format_spec(fmt_it_t start, fmt_it_t last, std::size_t id) -> fmt_it_t{ + parse_ctx.advance_to(start); + if constexpr(sizeof...(Args)){ + return do_format_spec_impl<0,Args...>(start, last, id); + }else{ + REXY_THROW_FORMAT_ERROR("Missing argument"); + return start; + } + } + template + constexpr void format_checker::do_empty_format(fmt_it_t, fmt_it_t, std::size_t){/*nothing to parse and the checker doesn't format so just return*/} + template + template + constexpr auto format_checker::do_format_spec_impl(fmt_it_t start, fmt_it_t last, std::size_t id) -> fmt_it_t{ + if(I == id){ + using fmt_ctx_t = fmt_context_t; + using base_type = std::remove_cvref_t; + using stored_type = stored_type_t; + if constexpr(Handle){ + using fmter_t = typename fmt_ctx_t::template formatter_type; + fmter_t fmter{}; + return fmter.parse(parse_ctx); + }else{ + dynamic_format_specs specs{}; + format_specs_checker> handler{ + cx_format_specs_handler{parse_ctx, specs}, + detail::map_to_storage_enum_v + }; + return parse::perform_standard_parse(start, last, handler); + } + }else{ + if constexpr(sizeof...(FArgs) > 0){ + return do_format_spec_impl(start, last, id); + } + } + REXY_THROW_FORMAT_ERROR("Missing argument"); + } + + template + constexpr std::size_t format_checker::on_arg_id(void){ + return parse_ctx.next_arg_id(); + } + template + constexpr std::size_t format_checker::on_arg_id(std::size_t i){ + if(i > sizeof...(Args)){ + REXY_THROW_FORMAT_ERROR("Arg index out of bounds"); + } + parse_ctx.check_arg_id(i); + return i; + } + template + constexpr std::size_t format_checker::on_arg_id(fmt_it_t start, fmt_it_t last){ + const std::size_t id = find_static_named_arg_id(start, last); + if(id == invalid_arg_index){ + REXY_THROW_FORMAT_ERROR("No such named arg"); + } + return on_arg_id(id); + } + + +} + +#endif diff --git a/include/rexy/detail/format/format_args.hpp b/include/rexy/detail/format/format_args.hpp new file mode 100644 index 0000000..f30e4af --- /dev/null +++ b/include/rexy/detail/format/format_args.hpp @@ -0,0 +1,130 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMAT_ARGS_HPP +#define REXY_DETAIL_FORMAT_FORMAT_ARGS_HPP + +#include //size_t +#include //monostate + +#include "storage.hpp" //storage_type +#include "basic_types.hpp" //arg_info +#include "arg_store.hpp" +#include "traits.hpp" + +#include "../../string_view.hpp" + +namespace rexy::fmt{ + + //Type erasure of single argument. + //Context is an instantiation of 'basic_format_context' + template + class basic_format_arg + { + public: + using char_type = typename Context::char_type; + + public: + //Handles call to formatter for a user defined type + //type erase the user defined values + class handle + { + public: + using format_ptr_t = void(handle::*)(basic_format_parse_context&, Context&)const; + public: + const void* m_data; + format_ptr_t m_format_impl; + public: + template + explicit handle(T&& t); + template + void format_impl(basic_format_parse_context& parse_ctx, Context& format_ctx)const; + void format(basic_format_parse_context& parse_ctx, Context& format_ctx)const; + }; + + union{ + std::monostate none_v = {}; + bool bool_v; + char_type char_v; + int int_v; + unsigned int uint_v; + long long long_long_v; + unsigned long long ulong_long_v; + float float_v; + double double_v; + long double long_double_v; + const char_type* char_ptr_v; + basic_string_view string_v; + const void* ptr_v; + handle custom_v; + }; + detail::storage_type active_member = detail::storage_type::none_t; + + //initialize std::variant equivalent to std::monostate + basic_format_arg(void)noexcept{} + + explicit basic_format_arg(bool b)noexcept; + explicit basic_format_arg(char_type b)noexcept; + explicit basic_format_arg(int b)noexcept; + explicit basic_format_arg(unsigned int b)noexcept; + explicit basic_format_arg(long long b)noexcept; + explicit basic_format_arg(unsigned long long b)noexcept; + explicit basic_format_arg(float b)noexcept; + explicit basic_format_arg(double b)noexcept; + explicit basic_format_arg(long double b)noexcept; + explicit basic_format_arg(const char_type* b)noexcept; + explicit basic_format_arg(basic_string_view b)noexcept; + explicit basic_format_arg(const void* b)noexcept; + explicit basic_format_arg(const handle b)noexcept; + + detail::storage_type type(void)const; + + explicit operator bool(void)const noexcept; + }; + + //Holds list of arguments, erasing concrete types. + //Context is an instantiation of 'basic_format_context' + template + class basic_format_args + { + public: + using char_type = detail::extract_char_type_from_context_t; + private: + //TODO less pointers + const detail::arg_info* m_info = nullptr; + const unsigned char* m_data = nullptr; + const std::size_t m_num_args = 0; + + public: + basic_format_args(void)noexcept = default; + + constexpr basic_format_args(const detail::basic_format_arg_store& store)noexcept; + template + basic_format_args(const detail::basic_format_arg_store& store)noexcept; + + basic_format_arg get(std::size_t i)const noexcept; + basic_format_arg get(const char_type* first, const char_type* last)const; + std::size_t get_index(const char_type* first, const char_type* last)const; + }; + + //Visitor for basic_format_arg + template + decltype(auto) visit_format_arg(Fun&& fun, const basic_format_arg& arg); +} + +#endif diff --git a/include/rexy/detail/format/format_args.tpp b/include/rexy/detail/format/format_args.tpp new file mode 100644 index 0000000..9e99d9b --- /dev/null +++ b/include/rexy/detail/format/format_args.tpp @@ -0,0 +1,236 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMAT_ARGS_TPP +#define REXY_DETAIL_FORMAT_FORMAT_ARGS_TPP + +#include "format_args.hpp" + +#include "basic_types.hpp" //format_string_view +#include "standard_types.hpp" +#include "parse_context.hpp" +#include "format_context.hpp" +#include "storage.hpp" +#include "arg_store.hpp" +#include "format_error.hpp" + +#include "../../string_view.hpp" + +#include //forward +#include //remove_cvref, add_lvalue_reference, add_pointer, add_const, conditional +#include //size_t +#include //addressof + +namespace rexy::fmt{ + + /////////////////////////////basic_format_arg::handle////////////////////////////// + template + template + basic_format_arg::handle::handle(T&& t): + m_data(std::addressof(t)), + m_format_impl(&handle::format_impl>){} + template + template + void basic_format_arg::handle::format_impl(basic_format_parse_context& parse_ctx, Context& format_ctx)const{ + using value_type = std::remove_cvref_t; + using formatter_t = typename Context::template formatter_type; + using qualified_type = std::conditional_t, + std::add_const_t, + value_type>; + using reference_type = std::add_lvalue_reference_t; + using pointer_type = std::add_pointer_t; + using const_pointer_type = std::add_pointer_t>; + + reference_type ref = *const_cast(reinterpret_cast(m_data)); + + formatter_t fmt; + parse_ctx.advance_to(fmt.parse(parse_ctx)); + format_ctx.advance_to(fmt.format(ref, format_ctx)); + } + + template + void basic_format_arg::handle::format(basic_format_parse_context& parse_ctx, Context& format_ctx)const{ + //Pretty epic gamer moment here + (this->*m_format_impl)(parse_ctx, format_ctx); + } + + ///////////////////////////////basic_format_arg//////////////////////////////// + template + basic_format_arg::basic_format_arg(bool b)noexcept: + bool_v(b), + active_member(detail::storage_type::bool_t){} + template + basic_format_arg::basic_format_arg(char_type b)noexcept: + char_v(b), + active_member(detail::storage_type::char_t){} + template + basic_format_arg::basic_format_arg(int b)noexcept: + int_v(b), + active_member(detail::storage_type::int_t){} + template + basic_format_arg::basic_format_arg(unsigned int b)noexcept: + uint_v(b), + active_member(detail::storage_type::uint_t){} + template + basic_format_arg::basic_format_arg(long long b)noexcept: + long_long_v(b), + active_member(detail::storage_type::long_long_t){} + template + basic_format_arg::basic_format_arg(unsigned long long b)noexcept: + ulong_long_v(b), + active_member(detail::storage_type::ulong_long_t){} + template + basic_format_arg::basic_format_arg(float b)noexcept: + float_v(b), + active_member(detail::storage_type::float_t){} + template + basic_format_arg::basic_format_arg(double b)noexcept: + double_v(b), + active_member(detail::storage_type::double_t){} + template + basic_format_arg::basic_format_arg(long double b)noexcept: + long_double_v(b), + active_member(detail::storage_type::long_double_t){} + template + basic_format_arg::basic_format_arg(const char_type* b)noexcept: + char_ptr_v(b), + active_member(detail::storage_type::char_ptr_t){} + template + basic_format_arg::basic_format_arg(basic_string_view b)noexcept: + string_v(b), + active_member(detail::storage_type::string_t){} + template + basic_format_arg::basic_format_arg(const void* b)noexcept: + ptr_v(b), + active_member(detail::storage_type::ptr_t){} + template + basic_format_arg::basic_format_arg(const handle b)noexcept: + custom_v(b), + active_member(detail::storage_type::custom_t){} + template + detail::storage_type basic_format_arg::type(void)const{ + return active_member; + } + template + basic_format_arg::operator bool(void)const noexcept{ + return active_member != detail::storage_type::none_t; + } + + ///////////////////////////////basic_format_args//////////////////////////////// + template + constexpr basic_format_args::basic_format_args(const detail::basic_format_arg_store&)noexcept{} + template + template + basic_format_args::basic_format_args(const detail::basic_format_arg_store& store)noexcept: + m_info(store.packed_data.info()), + m_data(store.packed_data.data()), + m_num_args(sizeof...(Args)){} + + template + basic_format_arg basic_format_args::get(std::size_t i)const noexcept{ + const bool named = m_info[i].named; + const std::size_t offset = m_info[i].offset + (named ? sizeof(detail::format_string_view) : 0); + const unsigned char* const data = m_data + offset; + + switch(m_info[i].type){ + case detail::storage_type::bool_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::char_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::int_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::uint_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::long_long_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::ulong_long_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::float_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::double_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::long_double_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::char_ptr_t: + return basic_format_arg{*reinterpret_cast(data)}; + case detail::storage_type::string_t: + return basic_format_arg{*reinterpret_cast*>(data)}; + case detail::storage_type::ptr_t: + return basic_format_arg{reinterpret_cast(*reinterpret_cast(data))}; + case detail::storage_type::custom_t: + return basic_format_arg{*reinterpret_cast::handle*>(data)}; + default: + return basic_format_arg{}; + }; + } + template + basic_format_arg basic_format_args::get(const char_type* first, const char_type* last)const{ + return get(get_index(first, last)); + } + template + std::size_t basic_format_args::get_index(const char_type* first, const char_type* last)const{ + for(std::size_t i = 0;i < m_num_args;++i){ + if(m_info[i].named){ + auto data = reinterpret_cast* const>(m_data + m_info[i].offset); + const std::size_t len = last - first; + if(len == data->length && !rexy::strncmp(data->name, first, len)){ + return i; + } + } + } + REXY_THROW_FORMAT_ERROR("Requested named arg does not exist"); + } + + + template + decltype(auto) visit_format_arg(Fun&& fun, const basic_format_arg& arg){ + switch(arg.type()){ + case detail::storage_type::bool_t: + return std::forward(fun)(arg.bool_v); + case detail::storage_type::char_t: + return std::forward(fun)(arg.char_v); + case detail::storage_type::int_t: + return std::forward(fun)(arg.int_v); + case detail::storage_type::uint_t: + return std::forward(fun)(arg.uint_v); + case detail::storage_type::long_long_t: + return std::forward(fun)(arg.long_long_v); + case detail::storage_type::ulong_long_t: + return std::forward(fun)(arg.ulong_long_v); + case detail::storage_type::float_t: + return std::forward(fun)(arg.float_v); + case detail::storage_type::double_t: + return std::forward(fun)(arg.double_v); + case detail::storage_type::long_double_t: + return std::forward(fun)(arg.long_double_v); + case detail::storage_type::char_ptr_t: + return std::forward(fun)(arg.char_ptr_v); + case detail::storage_type::string_t: + return std::forward(fun)(arg.string_v); + case detail::storage_type::ptr_t: + return std::forward(fun)(arg.ptr_v); + case detail::storage_type::custom_t: + return std::forward(fun)(arg.custom_v); + default: + REXY_THROW_FORMAT_ERROR("Invalid format_arg storage_type"); + }; + } + +} + +#endif diff --git a/include/rexy/detail/format/format_context.hpp b/include/rexy/detail/format/format_context.hpp new file mode 100644 index 0000000..aab03c9 --- /dev/null +++ b/include/rexy/detail/format/format_context.hpp @@ -0,0 +1,77 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMAT_CONTEXT_HPP +#define REXY_DETAIL_FORMAT_FORMAT_CONTEXT_HPP + +#include //remove_cvref, declval, integral_constant, void_t +#include //size_t +#include //locale + +#include "standard_types.hpp" //basic_format_args, formatter + +namespace rexy::fmt{ + + namespace detail{ + + //type trait used for properly cv-qualifying the argument to a call to formatter::format + template + struct is_formatter_const : public std::false_type{}; + template + struct is_formatter_const>>().format(std::declval(), std::declval())) + >> : public std::true_type{}; + template + concept has_formatter = requires(typename Context::template formatter_type> fmter, T& t, Context& ctx){ + fmter.format(t, ctx); + }; + template + concept has_const_formatter = has_formatter, Context>; + + } + + //Holds the arguments passed to calls to 'format' or 'vformat' in type erasing structure. + //Additionally holds the iterator where output is written. + template + class basic_format_context + { + public: + using iterator = OutIt; + using char_type = Char; + template + using formatter_type = formatter; + + private: + basic_format_args m_fmt_args; + iterator m_outit; + std::locale m_locale; + + public: + basic_format_context(basic_format_args fmt_args, iterator outit); + basic_format_context(basic_format_args fmt_args, iterator outit, const std::locale& l); + basic_format_arg arg(std::size_t id)const; + basic_format_arg arg(const char_type* first, const char_type* last)const; + std::size_t arg_index(const char_type* first, const char_type* last)const; + std::locale locale(void); + iterator out(void); + void advance_to(iterator it); + }; + +} + +#endif diff --git a/include/rexy/detail/format/format_context.tpp b/include/rexy/detail/format/format_context.tpp new file mode 100644 index 0000000..324f641 --- /dev/null +++ b/include/rexy/detail/format/format_context.tpp @@ -0,0 +1,70 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMAT_CONTEXT_TPP +#define REXY_DETAIL_FORMAT_FORMAT_CONTEXT_TPP + +#include "format_context.hpp" + +#include "format_args.hpp" + +#include //move +#include //size_t +#include //locale + +namespace rexy::fmt{ + + ///////////////////////////////basic_format_context//////////////////////////////// + template + basic_format_context::basic_format_context(basic_format_args fmt_args, iterator outit): + m_fmt_args(fmt_args), + m_outit(outit){} + template + basic_format_context::basic_format_context(basic_format_args fmt_args, iterator outit, const std::locale& l): + m_fmt_args(fmt_args), + m_outit(outit), + m_locale(l){} + template + auto basic_format_context::arg(std::size_t id)const -> basic_format_arg{ + return m_fmt_args.get(id); + } + template + auto basic_format_context::arg(const char_type* first, const char_type* last)const -> basic_format_arg{ + return m_fmt_args.get(first, last); + } + template + std::size_t basic_format_context::arg_index(const char_type* first, const char_type* last)const{ + return m_fmt_args.get_index(first, last); + } + + template + std::locale basic_format_context::locale(void){ + return m_locale; + } + template + auto basic_format_context::out(void) -> iterator{ + return std::move(m_outit); + } + template + void basic_format_context::advance_to(iterator it){ + m_outit = std::move(it); + } + +} + +#endif diff --git a/include/rexy/detail/format/format_error.hpp b/include/rexy/detail/format/format_error.hpp new file mode 100644 index 0000000..480c213 --- /dev/null +++ b/include/rexy/detail/format/format_error.hpp @@ -0,0 +1,47 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMAT_ERROR_HPP +#define REXY_DETAIL_FORMAT_FORMAT_ERROR_HPP + +#include "../../string_view.hpp" + +#include "../../detail/debug_config.hpp" + +#define REXY_FORMAT_REAL_STRINGIFY(str) #str +#define REXY_FORMAT_STRINGIFY(str) REXY_FORMAT_REAL_STRINGIFY(str) +#if LIBREXY_ENABLE_DEBUG_LEVEL > 0 + #define REXY_THROW_FORMAT_ERROR(errstring) throw format_error{__FILE__ ":" REXY_FORMAT_STRINGIFY(__LINE__) ": " errstring} +#else + #define REXY_THROW_FORMAT_ERROR(errstring) throw format_error{errstring} +#endif + +namespace rexy::fmt{ + + struct format_error{ + string_view errstr; + + constexpr const char* what(void)const{ + return errstr; + } + + }; + +} + +#endif diff --git a/include/rexy/detail/format/format_string.hpp b/include/rexy/detail/format/format_string.hpp new file mode 100644 index 0000000..0fc7eb8 --- /dev/null +++ b/include/rexy/detail/format/format_string.hpp @@ -0,0 +1,40 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMAT_STRING_HPP +#define REXY_DETAIL_FORMAT_FORMAT_STRING_HPP + +#include "../../string_view.hpp" + +namespace rexy::fmt::detail{ + + //Class accepted as an argument to 'format' calls. Will call a constant-evaluated parse of the format string + //to check for correctness. + template + class basic_format_string + { + public: + basic_string_view str; + + consteval basic_format_string(const Char* f); + consteval basic_format_string(basic_string_view f); + }; + +} + +#endif diff --git a/include/rexy/detail/format/format_string.tpp b/include/rexy/detail/format/format_string.tpp new file mode 100644 index 0000000..35dc133 --- /dev/null +++ b/include/rexy/detail/format/format_string.tpp @@ -0,0 +1,43 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMAT_STRING_TPP +#define REXY_DETAIL_FORMAT_FORMAT_STRING_TPP + +#include "format_string.hpp" + +#include "context_handler.hpp" +#include "parse.hpp" + +#include "../../string_view.hpp" + +namespace rexy::fmt::detail{ + + template + consteval basic_format_string::basic_format_string(const Char* f): + basic_format_string(basic_string_view{f}){} + template + consteval basic_format_string::basic_format_string(basic_string_view f): + str(f) + { + parse::perform_parse(format_checker{f, sizeof...(Args)}, str); + } + +} + +#endif diff --git a/include/rexy/detail/format/formatter.hpp b/include/rexy/detail/format/formatter.hpp new file mode 100644 index 0000000..7f2a257 --- /dev/null +++ b/include/rexy/detail/format/formatter.hpp @@ -0,0 +1,173 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMATTER_HPP +#define REXY_DETAIL_FORMAT_FORMATTER_HPP + +#include //size_t, nullptr_t +#include //monostate +#include //locale +#include //remove_cvref +#include //forward + +#include "basic_types.hpp" //format_specs +#include "internal_types.hpp" +#include "standard_types.hpp" //basic_parse_context +#include "specs_handler.hpp" //format_specs_checker +#include "traits.hpp" + +#include "../../string.hpp" +#include "../../string_view.hpp" +#include "../../allocator.hpp" + +namespace rexy::fmt{ + + namespace detail{ + + namespace format{ + + template + constexpr OutIt perform_standard_format(const void* t, OutIt out, const format_specs& specs, const std::locale& loc); + template + constexpr OutIt perform_standard_format(const void* t, OutIt out); + template + requires(!UTF8_String) + constexpr OutIt perform_standard_format(const Char* c, OutIt out, const format_specs& specs, const std::locale& loc); + template + constexpr OutIt perform_standard_format(const Char* c, OutIt out, const format_specs& specs, const std::locale& loc); + template + constexpr OutIt perform_standard_format(const Char* c, OutIt out); + template + constexpr OutIt perform_standard_format(Char b, OutIt out, const format_specs& specs, const std::locale& loc); + template + constexpr OutIt perform_standard_format(Char b, OutIt out); + template + constexpr OutIt perform_standard_format(bool b, OutIt out, const format_specs& specs, const std::locale& loc); + template + constexpr OutIt perform_standard_format(bool b, OutIt out); + template + constexpr OutIt perform_standard_format(T b, OutIt out, const format_specs& specs, const std::locale& loc); + template + constexpr OutIt perform_standard_format(T b, OutIt out); + template + constexpr OutIt perform_standard_format(T f, OutIt out, const format_specs& specs, const std::locale& loc); + template + constexpr OutIt perform_standard_format(std::monostate, OutIt out, const format_specs&, const std::locale&); + template + constexpr OutIt perform_standard_format(std::monostate, OutIt out); + + template + struct arg_formatter{ + FmtCtx& fmt_ctx; + ParseCtx& parse_ctx; + const Specs& specs; + + template T> + constexpr void operator()(T&& t); + template + constexpr void operator()(T&& t); + }; + template + struct empty_formatter{ + FmtCtx& fmt_ctx; + ParseCtx& parse_ctx; + + template T> + constexpr void operator()(T&& t); + template + constexpr void operator()(T&& t); + }; + } + + + template + class formatter_base + { + public: + using char_type = Char; + using parse_ctx_t = basic_format_parse_context; + using fmt_ctx_t = detail::fmt_context_t; + using format_spec_t = detail::dynamic_format_specs; + using format_spec_handler_t = detail::format_specs_checker>; + + protected: + format_spec_t specs; + + public: + constexpr auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()); + template + auto format(U&& t, FormatContext& ctx) -> decltype(ctx.out()); + }; + + } + +} + +namespace rexy{ + template + class formatter; + +#define IMPLEMENT_STANDARD_FORMATTER(type) \ + template \ + class formatter : public fmt::detail::formatter_base>, C>{} + + IMPLEMENT_STANDARD_FORMATTER(int); + IMPLEMENT_STANDARD_FORMATTER(unsigned int); + IMPLEMENT_STANDARD_FORMATTER(long long); + IMPLEMENT_STANDARD_FORMATTER(unsigned long long); + IMPLEMENT_STANDARD_FORMATTER(bool); + IMPLEMENT_STANDARD_FORMATTER(float); + IMPLEMENT_STANDARD_FORMATTER(double); + IMPLEMENT_STANDARD_FORMATTER(long double); + IMPLEMENT_STANDARD_FORMATTER(std::nullptr_t); + IMPLEMENT_STANDARD_FORMATTER(short); + IMPLEMENT_STANDARD_FORMATTER(unsigned short); + IMPLEMENT_STANDARD_FORMATTER(long); + IMPLEMENT_STANDARD_FORMATTER(unsigned long); + IMPLEMENT_STANDARD_FORMATTER(char); + IMPLEMENT_STANDARD_FORMATTER(unsigned char); + IMPLEMENT_STANDARD_FORMATTER(signed char); + + template + class formatter : public fmt::detail::formatter_base{}; + + template + class formatter : public fmt::detail::formatter_base{}; + + template + class formatter : public fmt::detail::formatter_base{}; + + template + class formatter,C> : public fmt::detail::formatter_base,C>{}; + + template + class formatter,C> : public fmt::detail::formatter_base,C>{}; + + template + class formatter : public fmt::detail::formatter_base{}; + + template + class formatter : public fmt::detail::formatter_base{}; + + +#undef IMPLEMENT_STANDARD_FORMATTER + + +} + +#endif diff --git a/include/rexy/detail/format/formatter.tpp b/include/rexy/detail/format/formatter.tpp new file mode 100644 index 0000000..94b68c6 --- /dev/null +++ b/include/rexy/detail/format/formatter.tpp @@ -0,0 +1,647 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_FORMATTER_TPP +#define REXY_DETAIL_FORMAT_FORMATTER_TPP + +#include "formatter.hpp" + +#include "basic_types.hpp" +#include "internal_types.hpp" +#include "standard_types.hpp" +#include "parse_context.hpp" +#include "format_context.hpp" +#include "format_args.hpp" +#include "parse.hpp" +#include "utf_iterator.hpp" + +#include "../../utility.hpp" //abs + +#include //remove_cvref +#include //forward, move +#include //size_t +#include //max +#include //signbit +#include //to_chars +#include //toupper +#include //uintptr_t +#include //locale +#include //monostate +#include + +namespace rexy::fmt::detail{ + + template + constexpr auto formatter_base::parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()){ + using stored_type = detail::stored_type_t,fmt_ctx_t>; + return detail::parse::perform_standard_parse( + ctx.begin(), + ctx.end(), + format_spec_handler_t{ + detail::dynamic_format_specs_handler{ctx, specs}, + detail::map_to_storage_enum_v + } + ); + } + template + template + auto formatter_base::format(U&& t, FormatContext& ctx) -> decltype(ctx.out()){ + normalize_dynamic_format_specs(ctx, specs); + return detail::format::perform_standard_format(std::forward(t), ctx.out(), specs); + } + + namespace format{ + + template + constexpr OutIt perform_format_write(const Char* c, OutIt out, std::size_t length){ + for(std::size_t i = 0;i < length;++i){ + *out++ = c[i]; + } + return std::move(out); + } + template + constexpr OutIt perform_format_write_aligned(OutIt out, int width, const format_specs& specs, alignment default_align, Outputer&& outputer){ + const int fill_amount = std::max(specs.width - width, 0); + int left_fill_amount = fill_amount; + int right_fill_amount = fill_amount; + const alignment align = specs.align == alignment::none ? default_align : specs.align; + + switch(align){ + case alignment::center: + left_fill_amount /= 2; + [[fallthrough]]; + case alignment::right: + for(std::size_t i = 0;i < left_fill_amount;++i){ + *out++ = specs.align_char; + } + right_fill_amount -= left_fill_amount; + [[fallthrough]]; + case alignment::left: + out = outputer(std::move(out)); + + for(std::size_t i = 0;i < right_fill_amount;++i){ + *out++ = specs.align_char; + } + break; + default: + REXY_THROW_FORMAT_ERROR("Invalid alignment state"); + }; + return std::move(out); + } + + template + consteval T cxlog10(T t){ + return t < 10 ? 1 : 1 + cxlog10(t / 10); + } + template + constexpr OutIt perform_format_write_sign(OutIt out, int sign, T val){ + if(val < 0){ + *out++ = '-'; + }else if(sign == '+'){ + *out++ = '+'; + }else if(sign == ' '){ + *out++ = ' '; + } + return std::move(out); + } + template + constexpr OutIt perform_format_write_sign(OutIt out, int sign, T val){ + if(std::signbit(val)){ + *out++ = '-'; + }else if(sign == '+'){ + *out++ = '+'; + }else if(sign == ' '){ + *out++ = ' '; + } + return std::move(out); + } + + template + constexpr OutIt perform_standard_format(const void* t, OutIt out, const format_specs& specs, const std::locale& loc){ + //2 hexidecimal digits per octet + constexpr std::size_t maxbufflen = sizeof(std::uintptr_t) * (CHAR_BIT / 4.0) + 3; //+2 for '0x', +1 for float rounding truncation + + char buff[maxbufflen] = {}; + buff[0] = '0'; + buff[1] = 'x'; + char* buffstart = buff + 2; + char* buffend = buff + maxbufflen; + + auto result = std::to_chars(buffstart, buffend, reinterpret_cast(t), 16); + if(result.ec != std::errc{}){ + REXY_THROW_FORMAT_ERROR("Unable to convert pointer type"); + } + return perform_standard_format(buff, out, specs, loc); + } + + template + constexpr OutIt perform_standard_format(const void* t, OutIt out){ + //2 hexidecimal digits per octet + constexpr std::size_t maxbufflen = sizeof(std::uintptr_t) * (CHAR_BIT / 4.0) + 4; //+2 for '0x', +1 for float rounding truncation + + char buff[maxbufflen] = {}; + buff[0] = '0'; + buff[1] = 'x'; + char* buffstart = buff + 2; + char* buffend = buff + maxbufflen; + + auto result = std::to_chars(buffstart, buffend, reinterpret_cast(t), 16); + if(result.ec != std::errc{}){ + REXY_THROW_FORMAT_ERROR("Unable to convert pointer type"); + } + const auto bufflen = result.ptr - buff; + return perform_format_write(buff, out, bufflen); + } + + + static constexpr unsigned int codepoint_fields[] = { + 0x1100u, 0x115Fu, + 0x2329u, 0x232Au, + 0x2E80u, 0x303Eu, + 0x3040u, 0xA4CFu, + 0xAC00u, 0xD7A3u, + 0xF900u, 0xFAFFu, + 0xFE10u, 0xFE19u, + 0xFE30u, 0xFE6Fu, + 0xFF00u, 0xFF60u, + 0xFFE0u, 0xFFE6u, + 0x1F300u, 0x1F64Fu, + 0x1F900u, 0x1F9FFu, + 0x20000u, 0x2FFFDu, + 0x30000u, 0x3FFFDu + }; + constexpr unsigned int estimate_unicode_width(const char32_t c){ + unsigned int width = 1; + for(auto field : codepoint_fields){ + if(c < field){ + return width; + } + //flip between 1 and 2 + width ^= 3u; + } + return width; + } + constexpr unsigned int estimate_unicode_string_width(const char* first, const char* last){ + unsigned int width = 0; + for(utf8_iterator it{first, last};it.valid();++it){ + const auto codepoint = *it; + width += estimate_unicode_width(codepoint); + } + return width; + } + template + requires(!UTF8_String) + constexpr OutIt perform_standard_format(const Char* c, OutIt out, const format_specs& specs, const std::locale& loc){ + const basic_string_view str{c}; + std::size_t est_width = 0; + std::size_t print_cnt = 0; + if(specs.precision > 0){ + est_width = std::min(str.length(), specs.precision); + print_cnt = est_width; + }else{ + est_width = str.length(); + print_cnt = str.length(); + } + + const auto outputter = [&](OutIt o){ + for(std::size_t i = 0;i < print_cnt;++i){ + *o++ = str[i]; + } + return std::move(o); + }; + + return perform_format_write_aligned(std::move(out), est_width, specs, alignment::left, outputter); + } + template + constexpr OutIt perform_standard_format(const Char* c, OutIt out, const format_specs& specs, const std::locale& loc){ + const basic_string_view str{c}; + std::size_t est_width = 0; + std::size_t print_cnt = 0; + if(specs.precision > 0){ + for(utf8_iterator it{str.begin(), str.end()};it.valid();++it){ + const auto ch_width = estimate_unicode_width(*it); + if(ch_width + est_width > specs.precision){ + break; + } + print_cnt += it.byte_count(); + est_width += ch_width; + } + }else{ + est_width = estimate_unicode_string_width(str.cbegin(), str.cend()); + print_cnt = str.length(); + } + + const auto outputter = [&](OutIt o){ + for(std::size_t i = 0;i < print_cnt;++i){ + *o++ = str[i]; + } + return std::move(o); + }; + + return perform_format_write_aligned(std::move(out), est_width, specs, alignment::left, outputter); + } + + template + constexpr OutIt perform_standard_format(const Char* c, OutIt out){ + for(const Char* i = c;*i;++i){ + *out++ = *i; + } + return std::move(out); + } + + template + constexpr OutIt perform_standard_format(Char b, OutIt out, const format_specs& specs, const std::locale& loc){ + if(specs.present == presentation::int_t){ + return perform_standard_format(static_cast(b), std::move(out), specs, loc); + } + const auto outputter = [=](OutIt o){ + *o++ = b; + return std::move(o); + }; + return perform_format_write_aligned(std::move(out), 1, specs, alignment::left, outputter); + } + + template + constexpr OutIt perform_standard_format(Char b, OutIt out){ + *out++ = b; + return std::move(out); + } + + template + constexpr OutIt perform_standard_format(bool b, OutIt out, const format_specs& specs, const std::locale& loc){ + switch(specs.present){ + case presentation::default_t: + case presentation::string_t: + break; + case presentation::int_t: + return perform_standard_format(static_cast(b), std::move(out), specs, loc); + default: + REXY_THROW_FORMAT_ERROR("Invalid type argument for bool"); + }; + + if(specs.locale){ + const auto& facet = std::use_facet>(loc); + const auto word = b ? facet.truename() : facet.falsename(); + format_specs copy_specs = specs; + copy_specs.locale = false; + return perform_standard_format( + word.c_str(), + std::move(out), + copy_specs, + loc + ); + } + + return perform_standard_format(b ? "true" : "false", std::move(out), specs, loc); + } + + template + constexpr OutIt perform_standard_format(bool b, OutIt out){ + return perform_standard_format(b ? "true" : "false", std::move(out)); + } + + template + constexpr OutIt perform_localized_integer_write(OutIt out, const char* start, const char* last, const std::locale& loc){ + const auto& facet = std::use_facet>(loc); + const int group_size = facet.grouping()[0]; + const Char sep = facet.thousands_sep(); + const int len = last - start; + if(group_size != 0){ + int write_count = (len-1) % group_size; + *out++ = *start++; + while(true){ + for(;write_count > 0;--write_count){ + *out++ = *start++; + } + if(start == last){ + break; + } + *out++ = sep; + write_count = std::min(group_size, int{last - start}); + } + return std::move(out); + } + return perform_format_write(start, std::move(out), len); + } + + template + constexpr OutIt perform_standard_format(T b, OutIt out, const format_specs& specs, const std::locale& loc){ + constexpr std::size_t maxbufflen = rexy::max( + std::numeric_limits::digits + 3, //add 1 for sign bit, 2 for prefix + std::numeric_limits::digits + 3 + ); + if(specs.present == presentation::char_t){ + return perform_standard_format(static_cast(b), std::move(out), specs, loc); + }; + + int base = 10; + int total_width = 0; + const bool should_zero_fill = specs.zero_fill && specs.align == alignment::none; + bool to_upper = false; + char buff[maxbufflen] = {}; + char* buffstart = buff; + char* buffend = buff + maxbufflen; + string_view prefix = ""; + + switch(specs.type){ + case 'b': + prefix = "0b"; + base = 2; + break; + case 'B': + prefix = "0B"; + base = 2; + break; + case 'o': + prefix = "0"; + base = 8; + break; + case 'x': + prefix = "0x"; + base = 16; + break; + case 'X': + prefix = "0X"; + to_upper = true; + base = 16; + break; + default: + break; + }; + total_width += prefix.length(); + + auto result = std::to_chars(buffstart, buffend, b, base); + if(result.ec != std::errc{}){ + REXY_THROW_FORMAT_ERROR("Unable to convert integral type"); + } + buffend = result.ptr; + if(b < 0){ + ++buffstart; + total_width += 1; + }else if(specs.sign != 0){ + total_width += 1; + } + const auto bufflen = buffend - buffstart; + total_width += bufflen; + + if(to_upper){ + for(auto it = buffstart;it != buffend;++it){ + *it = std::toupper(*it); + } + } + + const auto outputter = [&](OutIt o) -> OutIt{ + if(specs.alt_form){ + o = perform_format_write_sign(std::move(o), specs.sign, b); + o = perform_format_write(prefix.data(), std::move(o), prefix.length()); + }else{ + o = perform_format_write(prefix.data(), std::move(out), prefix.length()); + o = perform_format_write_sign(std::move(o), specs.sign, b); + } + if(should_zero_fill && specs.width > total_width){ + const int fill_width = specs.width - total_width; + for(int i = 0;i < fill_width;++i){ + *o++ = '0'; + } + } + if(specs.locale){ + const auto& facet = std::use_facet>(loc); + const int group_size = facet.grouping()[0]; + const Char sep = facet.thousands_sep(); + return perform_localized_integer_write(std::move(o), buffstart, buffend, loc); + } + + return perform_format_write(buffstart, std::move(o), bufflen); + }; + + if(should_zero_fill){ + return outputter(std::move(out)); + } + return perform_format_write_aligned(std::move(out), total_width, specs, alignment::right, outputter); + } + + + //////////////////////////////////////////////DONE/////////////////////////////////////////////// + template + constexpr OutIt perform_standard_format(T b, OutIt out){ + using limits = std::numeric_limits; + constexpr auto maxexp = limits::max_exponent10; //this is a constant expression but using limits::max_exponent10 directly isn't? + //long double will be the longest type possible, so operate on that. + //+4 for ones' place digit, decimal point, and 'e+' in scientific mode + //maximum buffer length is the maximum significant digits plus maximum length of exponent + constexpr std::size_t maxbufflen = 4 + limits::max_digits10 + cxlog10(maxexp); + + char buff[maxbufflen] = {}; + char* buffend = buff + maxbufflen; + + auto result = std::to_chars(buff, buffend, b); + if(result.ec != std::errc{}){ + REXY_THROW_FORMAT_ERROR("Unable to convert arithmetic type"); + } + + const auto bufflen = result.ptr - buff; + return perform_format_write(buff, std::move(out), bufflen); + } + + //TODO + template + constexpr OutIt perform_standard_format(T f, OutIt out, const format_specs& specs, const std::locale& loc){ + using limits = std::numeric_limits; + //max number of post-decimal digits is same as the inverted smallest radix exponent + constexpr int max_precision = rexy::abs(limits::min_exponent); + //max number of leading digits is same as biggest decimal exponent + constexpr int max_significants = limits::max_exponent10; + //+4 for ones' place digit, decimal point, and 'e+' in scientific mode + //maximum buffer length is the maximum significant digits plus maximum precision because the + //user can request any precision. So you can take the longest number with no decimal and add on + //the longest decimal trail allowed. + constexpr int maxbufflen = max_precision + max_significants + 4; + + char buff[maxbufflen] = {}; + char* buffstart = buff; + char* buffend = buff + maxbufflen; + + const bool supplied_precision = specs.precision > 0; + const bool is_infinity = f == std::numeric_limits::infinity() || f == -std::numeric_limits::infinity(); + const bool is_nan = (f != f); + const bool is_integer_representable = (static_cast(static_cast(f)) == f) && !is_infinity && !is_nan; + const bool should_zero_fill = specs.zero_fill && specs.align == alignment::none; + + std::chars_format fmt = std::chars_format::general; + bool to_upper = false; + bool manual_precision = supplied_precision; + bool trailing_dot = false; + std::size_t total_width = 0; + + //TODO handle any other modes and formatting options + std::to_chars_result result{}; + switch(specs.type){ + case 'A': + to_upper = true; + [[fallthrough]]; + case 'a': + fmt = std::chars_format::hex; + break; + case 'E': + to_upper = true; + [[fallthrough]]; + case 'e': + fmt = std::chars_format::scientific; + manual_precision = true; + break; + case 'F': + to_upper = true; + [[fallthrough]]; + case 'f': + fmt = std::chars_format::fixed; + manual_precision = true; + break; + case 'G': + to_upper = true; + [[fallthrough]]; + case 'g': + manual_precision = true; + if(specs.alt_form){ + //keep trailing zeros + fmt = std::chars_format::fixed; + } + break; + default: + trailing_dot = is_integer_representable && !supplied_precision && specs.alt_form; + break; + }; + + if(manual_precision){ + const int precision = supplied_precision ? specs.precision : 6; + result = std::to_chars(buffstart, buffend, f, fmt, precision); + }else{ + result = std::to_chars(buffstart, buffend, f, fmt); + } + + if(result.ec != std::errc{}){ + REXY_THROW_FORMAT_ERROR("Unable to convert floating type"); + } + buffend = result.ptr; + + //exclude negative sign automatically put there + if(buffstart[0] == '-'){ + ++buffstart; + ++total_width; + }else if(specs.sign != 0){ + ++total_width; + } + if(trailing_dot){ + ++total_width; + } + + const auto bufflen = buffend - buffstart; + total_width += bufflen; + + if(to_upper){ + for(auto it = buffstart;it != buffend;++it){ + *it = std::toupper(*it); + } + } + + const auto outputter = [&](OutIt o){ + Char radix_char = '.'; + o = perform_format_write_sign(std::move(o), specs.sign, f); + if(should_zero_fill && specs.width > total_width){ + const int fill_width = specs.width - total_width; + for(int i = 0;i < fill_width;++i){ + *o++ = '0'; + } + } + + if(specs.locale){ + const auto& facet = std::use_facet>(loc); + const int group_size = facet.grouping()[0]; + radix_char = facet.decimal_point(); + string_view buff_view{buffstart, buffend}; + const auto radix_pos = buff_view.find_first_of('.'); + if(radix_pos != string_view::npos){ + buff[radix_pos] = radix_char; + o = perform_localized_integer_write(std::move(o), buffstart, buffstart + radix_pos, loc); + buffstart += radix_pos; + } + } + + o = perform_format_write(buffstart, std::move(o), bufflen); + if(trailing_dot){ + *o++ = radix_char; + } + return std::move(o); + }; + + if(should_zero_fill){ + return outputter(std::move(out)); + } + return perform_format_write_aligned(std::move(out), total_width, specs, alignment::right, outputter); + } + + template + constexpr OutIt perform_standard_format(std::monostate, OutIt out, const format_specs&, const std::locale&){ + return std::move(out); + } + template + constexpr OutIt perform_standard_format(std::monostate, OutIt out){ + return std::move(out); + } + + template + template T> + constexpr void arg_formatter::operator()(T&& t){ + t.format(parse_ctx, fmt_ctx); + } + template + template + constexpr void arg_formatter::operator()(T&& t){ + using handler_t = format_specs_checker>; + using specs_t = dynamic_format_specs; + + specs_t specs; + parse_ctx.advance_to(parse::perform_standard_parse( + parse_ctx.begin(), + parse_ctx.end(), + handler_t{ + dynamic_format_specs_handler{ + parse_ctx, + specs + }, + map_to_storage_enum_v + } + )); + normalize_dynamic_format_specs(fmt_ctx, specs); + fmt_ctx.advance_to(perform_standard_format(t, fmt_ctx.out(), specs, fmt_ctx.locale())); + } + template + template T> + constexpr void empty_formatter::operator()(T&& t){ + t.format(parse_ctx, fmt_ctx); + } + template + template + constexpr void empty_formatter::operator()(T&& t){ + perform_standard_format(t, fmt_ctx.out()); + } + + } //namespace format + +} + +#endif diff --git a/include/rexy/detail/format/internal_types.hpp b/include/rexy/detail/format/internal_types.hpp new file mode 100644 index 0000000..3210b58 --- /dev/null +++ b/include/rexy/detail/format/internal_types.hpp @@ -0,0 +1,83 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_INTERNAL_TYPES_HPP +#define REXY_DETAIL_FORMAT_INTERNAL_TYPES_HPP + +#include "../../cx/string.hpp" +#include "standard_types.hpp" + +#include //back_insert_iterator +#include //type_identity + +namespace rexy::fmt::detail{ + + //forward declare implementation types + template + class basic_format_string; + template + class basic_format_arg_store; + template + class format_output_buffer_base; + template + class basic_format_output_buffer; + struct format_specs; + template + struct dynamic_format_specs; + template + struct checker_format_specs_handler; + template + struct format_specs_handler; + template + struct dynamic_format_specs_handler; + template + struct runtime_arg; + template + struct static_arg; + + //aliases for easier access to both implementation and standard defined types + template + using fmt_buffer_t = format_output_buffer_base; + template + using output_iterator_t = std::back_insert_iterator>; + template + using fmt_context_t = basic_format_context,Char>; + template + using parse_context_t = basic_format_parse_context; + template + using format_arg_store = basic_format_arg_store, Args...>; + template + using wformat_arg_store = basic_format_arg_store, Args...>; + template + using format_string = basic_format_string...>; + template + using wformat_string = basic_format_string...>; + template + using format_output_buffer = basic_format_output_buffer; + template + using wformat_output_buffer = basic_format_output_buffer; + + template + using impl_format_args = basic_format_args>; + template + using format_arg = basic_format_arg>; + + +} + +#endif diff --git a/include/rexy/detail/format/named_args.hpp b/include/rexy/detail/format/named_args.hpp new file mode 100644 index 0000000..8d6201f --- /dev/null +++ b/include/rexy/detail/format/named_args.hpp @@ -0,0 +1,117 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_NAMED_ARGS_HPP +#define REXY_DETAIL_FORMAT_NAMED_ARGS_HPP + +#include //remove_cvref, declval, integral_constant +#include //forward +#include //size_t + +#include "../../string_view.hpp" + +namespace rexy::fmt{ + + namespace detail{ + + template + struct arg_base{ + const T& value; + const basic_string_view name; + }; + + //class used to store name and value for runtime analysis of named arguments + //the type T is the actual formatted type and name is used only for lookup + //during runtime formatting + template + struct runtime_arg : public arg_base{ + using char_type = Char; + using value_type = std::remove_cvref_t; + }; + + //class used to store name and value for compile time analysis of named arguments + //the type T is the actual formatted type and Name is used for name lookup in + //format string checking + template + struct static_arg : public arg_base{ + using char_type = typename decltype(Name)::value_type; + using value_type = std::remove_cvref_t; + static constexpr auto static_name = Name; + + constexpr static_arg(const T& v): + arg_base{v, {Name.c_str(), Name.length()}}{} + static_arg(const static_arg&) = delete; + constexpr static_arg(static_arg&&) = default; + }; + + //temporary object to store Name to pass off to a compile time argument during assignment + template + struct arg_literal_result{ + template + constexpr auto operator=(T&& t){ + return static_arg, Name>{std::forward(t)}; + } + }; + + template + struct is_runtime_named_arg{ + template + static auto check(runtime_arg) -> std::true_type; + static auto check(...) -> std::false_type; + + static constexpr bool value = decltype(check(std::declval>()))::value; + }; + template + static constexpr bool is_runtime_named_arg_v = is_runtime_named_arg::value; + + template + struct is_static_named_arg{ + template + static auto check(static_arg) -> std::true_type; + static auto check(...) -> std::false_type; + + static constexpr bool value = decltype(check(std::declval>()))::value; + }; + template + static constexpr bool is_static_named_arg_v = is_static_named_arg::value; + + template + concept RuntimeNamedArg = is_runtime_named_arg_v; + template + concept StaticNamedArg = is_static_named_arg_v; + + template + concept NamedArg = StaticNamedArg || RuntimeNamedArg; + + template + constexpr std::size_t find_static_named_arg_id_impl(It first, It last); + template + constexpr std::size_t find_static_named_arg_id(It first, It last); + + template + struct count_named_args{ + static constexpr std::size_t value = (std::size_t{NamedArg} + ...); + }; + template + static constexpr std::size_t count_named_args_v = count_named_args::value; + + } + +} + +#endif diff --git a/include/rexy/detail/format/named_args.tpp b/include/rexy/detail/format/named_args.tpp new file mode 100644 index 0000000..356be7d --- /dev/null +++ b/include/rexy/detail/format/named_args.tpp @@ -0,0 +1,57 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_NAMED_ARGS_TPP +#define REXY_DETAIL_FORMAT_NAMED_ARGS_TPP + +#include "named_args.hpp" + +#include "basic_types.hpp" +#include "../../utility.hpp" //strcmp + +namespace rexy::fmt::detail{ + + template + constexpr std::size_t find_static_named_arg_id_impl(It first, It last){ + if constexpr(StaticNamedArg){ + const auto len = last - first; + const auto arg_len = Arg::static_name.length(); + if(len == arg_len){ + if(!rexy::strncmp(first, Arg::static_name.c_str(), arg_len)){ + return I; + } + } + } + if constexpr(sizeof...(Args)){ + return find_static_named_arg_id_impl(first, last); + }else{ + return invalid_arg_index; + } + } + template + constexpr std::size_t find_static_named_arg_id(It first, It last){ + if constexpr(!sizeof...(Args)){ + return invalid_arg_index; + }else{ + return find_static_named_arg_id_impl(first, last); + } + } + +} + +#endif diff --git a/include/rexy/detail/format/output_buffer.hpp b/include/rexy/detail/format/output_buffer.hpp new file mode 100644 index 0000000..7e8b95c --- /dev/null +++ b/include/rexy/detail/format/output_buffer.hpp @@ -0,0 +1,131 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_OUTPUT_BUFFER_HPP +#define REXY_DETAIL_FORMAT_OUTPUT_BUFFER_HPP + +#include //size_t +#include //fputc, FILE +#include //fputwc + +namespace rexy::fmt::detail{ + //polymorphic buffer of which a reference can be passed around to prevent + //passing around multiple output iterator types + template + class format_output_buffer_base + { + public: + using value_type = Char; + protected: + static constexpr std::size_t s_bufsize = 256; + protected: + Char m_data[s_bufsize]; + std::size_t m_size = 0; + std::size_t m_written_count = 0; + public: + format_output_buffer_base(void) = default; + virtual ~format_output_buffer_base(void) = default; + + virtual std::size_t write_out(void) = 0; + void clear(void); + void push_back(Char c); + + constexpr std::size_t count(void)const; + }; + + //Used with the 'formatter' to output a type + //OutIt is an iterator where the output is written at the end. + template + class basic_format_output_buffer : public format_output_buffer_base + { + private: + using base = format_output_buffer_base; + private: + OutIt m_out; + public: + basic_format_output_buffer(OutIt out); + ~basic_format_output_buffer(void)override; + + std::size_t write_out(void)override; + + constexpr OutIt out(void); + private: + std::size_t write_out_simple(void); + }; + template + class basic_format_output_n_buffer : public format_output_buffer_base + { + private: + using base = format_output_buffer_base; + private: + OutIt m_out; + std::size_t m_max_write = 0; + public: + basic_format_output_n_buffer(OutIt out, std::size_t max); + ~basic_format_output_n_buffer(void)override; + + std::size_t write_out(void)override; + + constexpr OutIt out(void); + }; + + template + class basic_format_size_buffer : public format_output_buffer_base + { + public: + constexpr basic_format_size_buffer(void) = default; + constexpr ~basic_format_size_buffer(void)override; + + std::size_t write_out(void)override; + }; + + + template + struct print_iterator; + template<> + struct print_iterator + { + private: + FILE* m_stream; + public: + constexpr print_iterator(FILE* stream): + m_stream(stream){} + print_iterator& operator=(char c){fputc(c, m_stream);return *this;} + + constexpr print_iterator& operator*(void){return *this;} + constexpr print_iterator& operator++(void){return *this;} + constexpr print_iterator operator++(int){return *this;} + }; + template<> + struct print_iterator + { + private: + FILE* m_stream; + public: + constexpr print_iterator(FILE* stream): + m_stream(stream){} + print_iterator& operator=(wchar_t c){fputwc(c, m_stream);return *this;} + + constexpr print_iterator& operator*(void){return *this;} + constexpr print_iterator& operator++(void){return *this;} + constexpr print_iterator operator++(int){return *this;} + }; + +} + +#endif diff --git a/include/rexy/detail/format/output_buffer.tpp b/include/rexy/detail/format/output_buffer.tpp new file mode 100644 index 0000000..2fb251c --- /dev/null +++ b/include/rexy/detail/format/output_buffer.tpp @@ -0,0 +1,156 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_OUTPUT_BUFFER_TPP +#define REXY_DETAIL_FORMAT_OUTPUT_BUFFER_TPP + +#include "output_buffer.hpp" + +#include //size_t +#include //move +#include //is_pointer, remove_cvref, integral_constant +#include //min + +namespace rexy::fmt::detail{ + + ///////////////////////format_output_buffer_base//////////////////////// + template + void format_output_buffer_base::clear(void){ + m_size = 0; + } + template + void format_output_buffer_base::push_back(Char c){ + if(m_size == s_bufsize){ + m_written_count += write_out(); + clear(); + } + m_data[m_size++] = c; + } + template + constexpr std::size_t format_output_buffer_base::count(void)const{ + //include data that will be written during next call to write_out + return m_written_count + m_size; + } + + ///////////////////////basic_format_output_buffer//////////////////////// + template + basic_format_output_buffer::basic_format_output_buffer(OutIt out): + m_out(out){} + template + basic_format_output_buffer::~basic_format_output_buffer(void){ + write_out(); + } + + template + concept HasContainerAlias = requires{ + typename T::container_type; + }; + + template + concept HasInsertMethod = requires(T t, It i){ + {t.insert(0, i, i)}; + {t.size()}; + }; + + struct container_traits_helper{ + using container = int; + }; + template + struct container_traits : public It, public container_traits_helper{ + template + static std::false_type check(int); + static std::true_type check(...); + }; + template + OutIt real_write_out(const Char* start, std::size_t write_count, OutIt out){ + static constexpr bool has_container = decltype(container_traits::check(0))::value; + + //optimize for types where direct access to the underlying container is available + //check for a container member and a 'container_type' typedef in OutIt + //reason for the type check is that without 'container_type' it might not be safe to access the 'container' + if constexpr(has_container && HasContainerAlias){ + struct container_access : public OutIt{ + using OutIt::container; + }; + container_access ca{out}; + auto& container = ca.container; + + if constexpr(HasInsertMethod){ + if constexpr(std::is_pointer_v>){ + container->insert(container->size(), start, start + write_count); + }else{ + container.insert(container.size(), start, start + write_count); + } + return std::move(out); + } + } + for(std::size_t i = 0;i < write_count;++i){ + out = start[i]; + } + return std::move(out); + } + template + std::size_t basic_format_output_buffer::write_out(void){ + m_out = real_write_out(this->m_data, this->m_size, std::move(m_out)); + return this->m_size; + } + template + constexpr OutIt basic_format_output_buffer::out(void){ + return std::move(m_out); + } + + ///////////////////////basic_format_output_n_buffer//////////////////////// + template + basic_format_output_n_buffer::basic_format_output_n_buffer(OutIt out, std::size_t max): + m_out(out), + m_max_write(max){} + template + basic_format_output_n_buffer::~basic_format_output_n_buffer(void){ + write_out(); + } + + template + std::size_t basic_format_output_n_buffer::write_out(void){ + const auto to_write = std::min(this->size, m_max_write); + + m_out = real_write_out(this->m_data, to_write, std::move(m_out)); + m_max_write -= to_write; + return to_write; + } + template + constexpr OutIt basic_format_output_n_buffer::out(void){ + return std::move(m_out); + } + + + /////////////////////////basic_format_size_buffer////////////////////////// + template + constexpr basic_format_size_buffer::~basic_format_size_buffer(void){ + write_out(); + } + template + std::size_t basic_format_size_buffer::write_out(void){ + return this->m_size; + } + + + + +} + +#endif diff --git a/include/rexy/detail/format/parse.hpp b/include/rexy/detail/format/parse.hpp new file mode 100644 index 0000000..8e82d72 --- /dev/null +++ b/include/rexy/detail/format/parse.hpp @@ -0,0 +1,116 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_PARSE_HPP +#define REXY_DETAIL_FORMAT_PARSE_HPP + +#include "../../string_view.hpp" +#include //size_t + +namespace rexy::fmt::detail::parse{ + + //A few basic string checking functions + template + constexpr auto find_first_of_it(Char v, + typename basic_string_view::const_iterator start_it, + typename basic_string_view::const_iterator last_it); + template + constexpr auto find_first_of_it(const basic_string_view& str, Char v); + template + constexpr bool check_duplicate_char(It start, It end); + template + constexpr bool is_a_number(Char c); + template + constexpr int to_integer(It start, It end); + template + constexpr It find_valid_index(It start, It end); + + //Used with 'visit_format_arg' as the 'fun' arg. Gets the value of the argument as a 'long long' + //if the argument is of integral type. Throws an error otherwise + struct dynamic_integer_retriever{ + template + constexpr long long operator()(T&& t); + }; + + //Used during call to 'perform_standard_nested_replacement_field' as the 'Adapter' type. + //Calls 'on_dynamic_width' of 'specs' + template + struct dynamic_width_adapter{ + Specs& specs; + + constexpr decltype(auto) operator()(void); + constexpr decltype(auto) operator()(int i); + template + constexpr decltype(auto) operator()(It start, It last); + }; + //Used during call to 'perform_standard_nested_replacement_field' as the 'Adapter' type. + //Calls 'on_dynamic_precision' of 'specs' + template + struct dynamic_precision_adapter{ + Specs& specs; + + constexpr decltype(auto) operator()(void); + constexpr decltype(auto) operator()(int i); + template + constexpr decltype(auto) operator()(It start, It last); + }; + template + struct dynamic_index_adapter{ + Handler& handler; + std::size_t& id; + constexpr decltype(auto) operator()(void); + constexpr decltype(auto) operator()(int i); + template + constexpr decltype(auto) operator()(It start, It last); + }; + + //Standalone functions for parsing standard format fields + template + constexpr It perform_standard_fill_align_option(It start, It last, FormatSpecs&& specs); + template + constexpr It perform_standard_sign_option(It start, It last, FormatSpecs&& specs); + template + constexpr It perform_standard_alt_form_option(It start, It last, FormatSpecs&& specs); + template + constexpr It perform_standard_zero_fill_option(It start, It last, FormatSpecs&& specs); + template + constexpr It perform_standard_nested_replacement_field(It start, It last, Adapter&& func); + template + constexpr It perform_standard_width_option(It start, It last, FormatSpecs&& specs); + template + constexpr It perform_standard_precision_option(It start, It last, FormatSpecs&& specs); + template + constexpr It perform_standard_locale_option(It start, It last, FormatSpecs&& specs); + template + constexpr It perform_standard_type_option(It start, It last, FormatSpecs&& specs); + template + constexpr It perform_standard_parse(It start, It last, FormatSpecs&& specs); + + //Standalone functions for parsing the entire format string + template + constexpr It perform_format_index_spec(Handler&& handler, It start, It last, std::size_t& id); + template + constexpr It perform_empty_format_field_parse(Handler&& handler, It start, It last); + template + constexpr It perform_format_field_parse(Handler&& handler, It start, It last); + template + constexpr void perform_parse(Handler&& handler, basic_string_view fmt); + +} + +#endif diff --git a/include/rexy/detail/format/parse.tpp b/include/rexy/detail/format/parse.tpp new file mode 100644 index 0000000..b6b4b49 --- /dev/null +++ b/include/rexy/detail/format/parse.tpp @@ -0,0 +1,355 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_PARSE_TPP +#define REXY_DETAIL_FORMAT_PARSE_TPP + +#include "parse.hpp" + +#include "format_error.hpp" + +#include "../../string_view.hpp" + +#include //size_t +#include //is_integral, remove_cvref, add_lvalue_reference, remove_reference +#include //min + +namespace rexy::fmt::detail::parse{ + + template + constexpr auto find_first_of_it(Char v, + typename basic_string_view::const_iterator start_it, + typename basic_string_view::const_iterator last_it) + { + const basic_string_view view{start_it, last_it}; + const std::size_t range_size = last_it - start_it; + return start_it + std::min(view.find_first_of(v), range_size); + } + template + constexpr auto find_first_of_it(const basic_string_view& str, Char v){ + return find_first_of_it(v, str.begin(), str.end()); + } + template + constexpr bool check_duplicate_char(It start, It end){ + if(start == end){ + return false; + } + const auto next = start + 1; + return *start == *next; + } + template + constexpr bool is_a_number(Char c){ + return (c >= '0' && c <= '9'); + } + template + constexpr bool is_valid_name_char(Char c){ + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '_'); + } + template + constexpr int to_integer(It start, It end){ + int retval = 0; + std::size_t mul = 1; + for(auto it = end;it != start;--it){ + const auto cur = it-1; + retval += ((*cur - '0') * mul); + mul *= 10; + } + return retval; + } + + template + constexpr long long dynamic_integer_retriever::operator()(T&& t){ + if constexpr(std::is_integral_v>){ + return static_cast(t); + }else{ + REXY_THROW_FORMAT_ERROR("Invalid dynamic specifier"); + return static_cast(0); + } + } + template + constexpr decltype(auto) dynamic_width_adapter::operator()(void){ + return specs.on_dynamic_width(); + } + template + constexpr decltype(auto) dynamic_width_adapter::operator()(int i){ + return specs.on_dynamic_width(i); + } + template + template + constexpr decltype(auto) dynamic_width_adapter::operator()(It start, It last){ + return specs.on_dynamic_width(start, last); + } + template + constexpr decltype(auto) dynamic_precision_adapter::operator()(void){ + return specs.on_dynamic_precision(); + } + template + constexpr decltype(auto) dynamic_precision_adapter::operator()(int i){ + return specs.on_dynamic_precision(i); + } + template + template + constexpr decltype(auto) dynamic_precision_adapter::operator()(It start, It last){ + return specs.on_dynamic_precision(start, last); + } + template + constexpr decltype(auto) dynamic_index_adapter::operator()(void){ + return (id = handler.on_arg_id()); + } + template + constexpr decltype(auto) dynamic_index_adapter::operator()(int i){ + id = i; + return handler.on_arg_id(i); + } + template + template + constexpr decltype(auto) dynamic_index_adapter::operator()(It start, It last){ + id = handler.on_arg_id(start, last); + return id; + } + + template + constexpr It perform_standard_fill_align_option(It start, It last, FormatSpecs&& specs){ + char fill_val = ' '; + It it = start; + for(int i = 0;i < 2 && it != last;++i){ + if(*it == '>' || *it == '<' || *it == '^'){ + specs.on_align(*it, fill_val); + return ++it; + } + fill_val = *it; + ++it; + } + return start; + } + template + constexpr It perform_standard_sign_option(It start, It last, FormatSpecs&& specs){ + if(*start == '+' || *start == '-' || *start == ' '){ + specs.on_sign(*start); + return ++start; + } + return start; + } + template + constexpr It perform_standard_alt_form_option(It start, It last, FormatSpecs&& specs){ + if(*start == '#'){ + specs.on_alt_form(); + ++start; + } + return start; + } + template + constexpr It perform_standard_zero_fill_option(It start, It last, FormatSpecs&& specs){ + if(*start == '0'){ + specs.on_zero_fill(); + ++start; + } + return start; + } + template + constexpr It perform_index_retrieve(It start, It last, Adapter&& func){ + if(*start == '0'){ + func(0); + return start+1; + } + if(*start == '}' || *start == ':'){ + func(); + return start; + } + if(!is_a_number(*start)){ + auto it = start; + for(;it != last;++it){ + if(!is_valid_name_char(*it)){ + func(start, it); + return it; + } + } + return last; + REXY_THROW_FORMAT_ERROR("Invalid index spec"); + } + for(auto it = start+1;it != last;++it){ + if(!is_a_number(*it)){ + func(to_integer(start, it)); + return it; + } + } + return last; + } + template + constexpr It perform_standard_nested_replacement_field(It start, It last, Adapter&& func){ + auto it = perform_index_retrieve(start, last, func); + if(*it != '}'){ + REXY_THROW_FORMAT_ERROR("Dynamic index invalid spec"); + } + return it+1; + } + + template + constexpr It perform_standard_width_option(It start, It last, FormatSpecs&& specs){ + using specs_t = std::add_lvalue_reference_t>; + if(*start == '{'){ + return perform_standard_nested_replacement_field(start+1, last, dynamic_width_adapter{specs}); + } + if(*start == '0'){ + REXY_THROW_FORMAT_ERROR("Field invalid spec"); + } + It it = start; + for(;is_a_number(*it) && it != last;++it){} + if(it == start){ + return start; + } + specs.on_width(to_integer(start, it)); + return it; + } + template + constexpr It perform_standard_precision_option(It start, It last, FormatSpecs&& specs){ + using specs_t = std::add_lvalue_reference_t>; + if(*start != '.'){ + return start; + } + ++start; + if(*start == '{'){ + return perform_standard_nested_replacement_field(start+1, last, dynamic_precision_adapter{specs}); + } + if(*start == '0'){ + REXY_THROW_FORMAT_ERROR("Field invalid spec"); + } + It it = start; + for(;is_a_number(*it) && it != last;++it){} + if(it == start){ + REXY_THROW_FORMAT_ERROR("Field invalid spec"); + } + specs.on_precision(to_integer(start, it)); + return it; + } + template + constexpr It perform_standard_locale_option(It start, It last, FormatSpecs&& specs){ + if(*start == 'L'){ + specs.on_locale(); + return ++start; + } + return start; + } + template + constexpr It perform_standard_type_option(It start, It last, FormatSpecs&& specs){ + if(*start == '}'){ + specs.on_type_option(); + return start; + } + specs.on_type_option(*start); + return ++start; + } + + template + constexpr It perform_standard_parse(It start, It last, FormatSpecs&& specs){ + using specs_t = std::add_lvalue_reference_t>; + using std_fmt_fn = It(*)(It,It,specs_t); + constexpr std_fmt_fn fns[] = { + perform_standard_fill_align_option, + perform_standard_sign_option, + perform_standard_alt_form_option, + perform_standard_zero_fill_option, + perform_standard_width_option, + perform_standard_precision_option, + perform_standard_locale_option, + perform_standard_type_option + }; + constexpr int num_std_fns = sizeof(fns) / sizeof(fns[0]); + + for(int i = 0;i < num_std_fns;++i){ + start = fns[i](start, last, specs); + if(start == last){ + break; + } + } + if(*start != '}'){ + REXY_THROW_FORMAT_ERROR("Missing closing brace for format field"); + } + return start; + } + + template + constexpr It perform_format_index_spec(Handler&& handler, It start, It last, std::size_t& id){ + dynamic_index_adapter adapter{handler, id}; + const auto it = perform_index_retrieve(start, last, adapter); + if(*it != '}' && *it != ':'){ + REXY_THROW_FORMAT_ERROR("Invalid index spec"); + } + return it; + } + template + constexpr It perform_format_field_parse(Handler&& handler, It start, It last){ + if(start == last){ + REXY_THROW_FORMAT_ERROR("Unmatched brace"); + } + std::size_t index = 0; + start = perform_format_index_spec(handler, start, last, index); + if(*start == ':'){ + return handler.do_format_spec(start+1, last, index); + } + handler.do_empty_format(start, last, index); + return start; + } + template + constexpr void perform_parse(Handler&& handler, basic_string_view fmt){ + using char_type = Char; + + auto work_it = fmt.begin(); + auto open_brace_it = fmt.begin(); + auto close_brace_it = fmt.begin(); + + while(true){ + open_brace_it = find_first_of_it(char_type{'{'}, work_it, fmt.end()); + + while(true){ + close_brace_it = find_first_of_it(char_type{'}'}, work_it, open_brace_it); + if(close_brace_it == open_brace_it){ + //don't go past open brace because we still need to parse it + handler.do_raw(work_it, close_brace_it); + work_it = close_brace_it; + break; + } + if(check_duplicate_char(close_brace_it, fmt.end())){ + ++close_brace_it; + handler.do_raw(work_it, close_brace_it); + //go past closing brace to not double parse it + work_it = close_brace_it + 1; + }else{ + REXY_THROW_FORMAT_ERROR("Unmatched brace"); + } + } + if(open_brace_it == fmt.end()){ + break; + } + + if(check_duplicate_char(open_brace_it, fmt.end())){ + ++open_brace_it; + handler.do_raw(work_it, open_brace_it); + work_it = open_brace_it + 1; + continue; + } + work_it = perform_format_field_parse(handler, open_brace_it+1, fmt.end()) + 1; + } + return; + } + +} + +#endif diff --git a/include/rexy/detail/format/parse_context.hpp b/include/rexy/detail/format/parse_context.hpp new file mode 100644 index 0000000..831c13e --- /dev/null +++ b/include/rexy/detail/format/parse_context.hpp @@ -0,0 +1,61 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_PARSE_CONTEXT_HPP +#define REXY_DETAIL_FORMAT_PARSE_CONTEXT_HPP + +#include //size_t + +#include "../../string_view.hpp" + +namespace rexy::fmt{ + + //Holds the format string for use in calls to 'formatter::parse' + //Additionally keeps tally of the current argument index for automatic replacement indexing or + //switches to manual indexing mode when encountering a manually indexed field. Throws error on mode switch. + template + class basic_format_parse_context + { + public: + using char_type = Char; + using iterator = typename basic_string_view::const_iterator; + using const_iterator = typename basic_string_view::const_iterator; + + public: + basic_string_view format; + std::size_t arg_count; + long long arg_index = 0; + + public: + //fmt is the format specifier, num_args is 'sizeof...(Args)' where 'Args' is the parameter pack of the format call + constexpr explicit basic_format_parse_context(basic_string_view fmt, std::size_t num_args = 0)noexcept; + basic_format_parse_context(const basic_format_parse_context&) = delete; + basic_format_parse_context(basic_format_parse_context&&) = delete; + basic_format_parse_context& operator=(basic_format_parse_context&&) = delete; + basic_format_parse_context& operator=(const basic_format_parse_context&) = delete; + + constexpr const_iterator begin(void)const noexcept; + constexpr const_iterator end(void)const noexcept; + constexpr void advance_to(const_iterator it); + constexpr std::size_t next_arg_id(void); + constexpr void check_arg_id(std::size_t id); + }; + +} + +#endif diff --git a/include/rexy/detail/format/parse_context.tpp b/include/rexy/detail/format/parse_context.tpp new file mode 100644 index 0000000..308d56a --- /dev/null +++ b/include/rexy/detail/format/parse_context.tpp @@ -0,0 +1,75 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_PARSE_CONTEXT_TPP +#define REXY_DETAIL_FORMAT_PARSE_CONTEXT_TPP + +#include "parse_context.hpp" + +#include "format_error.hpp" + +#include //size_t +#include //is_constant_evaluated + +namespace rexy::fmt{ + + /////////////////////////////basic_format_parse_context////////////////////////////// + template + constexpr basic_format_parse_context::basic_format_parse_context(basic_string_view fmt, std::size_t num_args)noexcept: + format(fmt), + arg_count(num_args){} + + template + constexpr auto basic_format_parse_context::begin(void)const noexcept -> const_iterator{ + return format.begin(); + } + template + constexpr auto basic_format_parse_context::end(void)const noexcept -> const_iterator{ + return format.end(); + } + template + constexpr void basic_format_parse_context::advance_to(const_iterator it){ + const std::size_t diff = it - format.begin(); + format.remove_prefix(diff); + } + template + constexpr std::size_t basic_format_parse_context::next_arg_id(void){ + if(arg_index < 0){ + //error, already in manual mode + REXY_THROW_FORMAT_ERROR("Invalid indexing mode switch"); + } + if(std::is_constant_evaluated() && arg_index >= arg_count){ + REXY_THROW_FORMAT_ERROR("Missing argument"); + } + return arg_index++; + } + template + constexpr void basic_format_parse_context::check_arg_id(std::size_t id){ + if(arg_index > 0){ + //error, already in automatic mode + REXY_THROW_FORMAT_ERROR("Invalid indexing mode switch"); + } + if(std::is_constant_evaluated() && id >= arg_count){ + REXY_THROW_FORMAT_ERROR("Missing argument"); + } + arg_index = detail::invalid_arg_index; + } + +} + +#endif diff --git a/include/rexy/detail/format/specs_handler.hpp b/include/rexy/detail/format/specs_handler.hpp new file mode 100644 index 0000000..693d62a --- /dev/null +++ b/include/rexy/detail/format/specs_handler.hpp @@ -0,0 +1,129 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_SPECS_HANDLER_HPP +#define REXY_DETAIL_FORMAT_SPECS_HANDLER_HPP + +#include //is_constant_evaluated + +namespace rexy::fmt::detail{ + + //Stores standard format-spec information to use later in formatting + template + struct format_specs_handler{ + ParseCtx& parse_ctx; + FmtCtx& fmt_ctx; + format_specs& specs; + + constexpr format_specs_handler(ParseCtx& pc, FmtCtx& fc, format_specs& s): + parse_ctx(pc), + fmt_ctx(fc), + specs(s){} + + constexpr void on_width(int w); + constexpr void on_dynamic_width(void); + constexpr void on_dynamic_width(int index); + template + constexpr void on_dynamic_width(It first, It last); + constexpr void on_precision(int p); + constexpr void on_dynamic_precision(void); + constexpr void on_dynamic_precision(int index); + template + constexpr void on_dynamic_precision(It first, It last); + constexpr void on_locale(void); + constexpr void on_type_option(int o = 0); + constexpr void on_align(int al, int val); + constexpr void on_sign(int s); + constexpr void on_alt_form(void); + constexpr void on_zero_fill(void); + }; + + //Stores standard format-spec information to use later in formatting. Stores indices of dynamic specifiers + //to be retrieved later. + template + struct dynamic_format_specs_handler{ + using specs_type = dynamic_format_specs; + ParseCtx& parse_ctx; + specs_type& specs; + + constexpr dynamic_format_specs_handler(ParseCtx& pc, specs_type& s); + + constexpr void on_width(int w); + constexpr void on_dynamic_width(void); + constexpr void on_dynamic_width(int index); + template + constexpr void on_dynamic_width(It first, It last); + constexpr void on_precision(int p); + constexpr void on_dynamic_precision(void); + constexpr void on_dynamic_precision(int index); + template + constexpr void on_dynamic_precision(It first, It last); + constexpr void on_locale(void); + constexpr void on_type_option(int o = 0); + constexpr void on_align(int al, int val); + constexpr void on_sign(int s); + constexpr void on_alt_form(void); + constexpr void on_zero_fill(void); + }; + //Used during compile time only. Checks dynamic specifiers given types pack as template parameters. + template + struct cx_format_specs_handler : public dynamic_format_specs_handler{ + using char_type = typename ParseCtx::char_type; + + constexpr cx_format_specs_handler(ParseCtx& pc, dynamic_format_specs& s): + dynamic_format_specs_handler(pc, s){ + if(!std::is_constant_evaluated()){ + throw 0; + } + } + + constexpr void on_dynamic_width(void); + constexpr void on_dynamic_width(int index); + template + constexpr void on_dynamic_width(It first, It last); + constexpr void on_dynamic_precision(void); + constexpr void on_dynamic_precision(int index); + template + constexpr void on_dynamic_precision(It first, It last); + }; + + template + struct format_specs_checker : public Handler{ + storage_type arg_type = storage_type::none_t; + bool requires_arithmetic_presentation = false; + + constexpr format_specs_checker(const Handler& h, storage_type type); + + constexpr void on_precision(int p); + constexpr void on_dynamic_precision(void); + constexpr void on_dynamic_precision(int index); + template + constexpr void on_dynamic_precision(It first, It last); + constexpr void on_locale(void); + constexpr void on_sign(int s); + constexpr void on_alt_form(void); + constexpr void on_zero_fill(void); + constexpr void on_type_option(int type = 0); + private: + constexpr void check_precision(void); + constexpr void check_arithmetic_type(void); + }; + +} + +#endif diff --git a/include/rexy/detail/format/specs_handler.tpp b/include/rexy/detail/format/specs_handler.tpp new file mode 100644 index 0000000..68e4bc6 --- /dev/null +++ b/include/rexy/detail/format/specs_handler.tpp @@ -0,0 +1,389 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_SPECS_HANDLER_TPP +#define REXY_DETAIL_FORMAT_SPECS_HANDLER_TPP + +#include "specs_handler.hpp" + +#include "format_error.hpp" +#include "traits.hpp" +#include "named_args.hpp" +#include "format_args.hpp" + +#include "internal_types.hpp" +#include "standard_types.hpp" +#include "basic_types.hpp" + +namespace rexy::fmt::detail{ + + /////////////////////////////format_specs_handler////////////////////////////// + template + constexpr void format_specs_handler::on_width(int w){ + specs.width = w; + } + template + constexpr void format_specs_handler::on_dynamic_width(void){ + const auto index = parse_ctx.next_arg_id(); + const auto arg = fmt_ctx.arg(index); + const auto value = visit_format_arg(parse::dynamic_integer_retriever{}, arg); + on_width(value); + } + template + constexpr void format_specs_handler::on_dynamic_width(int index){ + parse_ctx.check_arg_id(index); + const auto arg = fmt_ctx.arg(index); + const auto value = visit_format_arg(parse::dynamic_integer_retriever{}, arg); + on_width(value); + } + template + template + constexpr void format_specs_handler::on_dynamic_width(It first, It last){ + on_dynamic_width(fmt_ctx.arg_index(first, last)); + } + template + constexpr void format_specs_handler::on_precision(int p){ + specs.precision = p; + } + template + constexpr void format_specs_handler::on_dynamic_precision(void){ + const auto index = parse_ctx.next_arg_id(); + const auto arg = fmt_ctx.arg(index); + const auto value = visit_format_arg(parse::dynamic_integer_retriever{}, arg); + on_precision(value); + } + template + constexpr void format_specs_handler::on_dynamic_precision(int index){ + parse_ctx.check_arg_id(index); + const auto arg = fmt_ctx.arg(index); + const auto value = visit_format_arg(parse::dynamic_integer_retriever{}, arg); + on_precision(value); + } + template + template + constexpr void format_specs_handler::on_dynamic_precision(It first, It last){ + on_dynamic_precision(fmt_ctx.arg_index(first, last)); + } + template + constexpr void format_specs_handler::on_locale(void){ + specs.locale = true; + } + template + constexpr void format_specs_handler::on_type_option(int o){ + specs.type = o; + } + template + constexpr void format_specs_handler::on_align(int al, int val){ + specs.align = static_cast(al); + specs.align_char = val; + } + template + constexpr void format_specs_handler::on_sign(int s){ + specs.sign = s; + } + template + constexpr void format_specs_handler::on_alt_form(void){ + specs.alt_form = true; + } + template + constexpr void format_specs_handler::on_zero_fill(void){ + specs.zero_fill = true; + } + + /////////////////////////////dynamic_format_specs_handler////////////////////////////// + template + constexpr dynamic_format_specs_handler::dynamic_format_specs_handler(ParseCtx& pc, dynamic_format_specs& s): + parse_ctx(pc), + specs(s){} + template + constexpr void dynamic_format_specs_handler::on_width(int w){ + specs.width = w; + } + template + constexpr void dynamic_format_specs_handler::on_dynamic_width(void){ + specs.dyn_width.index = parse_ctx.next_arg_id(); + } + template + constexpr void dynamic_format_specs_handler::on_dynamic_width(int index){ + parse_ctx.check_arg_id(index); + specs.dyn_width.index = index; + } + template + template + constexpr void dynamic_format_specs_handler::on_dynamic_width(It first, It last){ + parse_ctx.check_arg_id(0); //just to make sure we aren't switching indexing modes + specs.dyn_width.name = {first, last - first}; + specs.width_type = dyn_type::string; + } + template + constexpr void dynamic_format_specs_handler::on_precision(int p){ + specs.precision = p; + } + template + constexpr void dynamic_format_specs_handler::on_dynamic_precision(void){ + specs.dyn_precision.index = parse_ctx.next_arg_id(); + } + template + constexpr void dynamic_format_specs_handler::on_dynamic_precision(int index){ + parse_ctx.check_arg_id(index); + specs.dyn_precision.index = index; + } + template + template + constexpr void dynamic_format_specs_handler::on_dynamic_precision(It first, It last){ + parse_ctx.check_arg_id(0); //just to make sure we aren't switching indexing modes + specs.dyn_precision.name = {first, last - first}; + specs.precision_type = dyn_type::string; + } + template + constexpr void dynamic_format_specs_handler::on_locale(void){ + specs.locale = true; + } + template + constexpr void dynamic_format_specs_handler::on_type_option(int o){ + specs.type = o; + } + template + constexpr void dynamic_format_specs_handler::on_align(int al, int val){ + specs.align = static_cast(al); + specs.align_char = val; + } + template + constexpr void dynamic_format_specs_handler::on_sign(int s){ + specs.sign = s; + } + template + constexpr void dynamic_format_specs_handler::on_alt_form(void){ + specs.alt_form = true; + } + template + constexpr void dynamic_format_specs_handler::on_zero_fill(void){ + specs.zero_fill = true; + } + + + /////////////////////////////cx_format_specs_handler////////////////////////////// + template + constexpr void cx_format_specs_handler::on_dynamic_width(void){ + const auto index = this->parse_ctx.next_arg_id(); + if(!is_dynamic_integer(index)){ + REXY_THROW_FORMAT_ERROR("Invalid type given as dynamic width"); + } + } + template + constexpr void cx_format_specs_handler::on_dynamic_width(int index){ + this->parse_ctx.check_arg_id(index); + if(!is_dynamic_integer(index)){ + REXY_THROW_FORMAT_ERROR("Invalid type given as dynamic width"); + } + } + template + template + constexpr void cx_format_specs_handler::on_dynamic_width(It first, It last){ + on_dynamic_width(find_static_named_arg_id(first, last)); + } + template + constexpr void cx_format_specs_handler::on_dynamic_precision(void){ + const auto index = this->parse_ctx.next_arg_id(); + if(!is_dynamic_integer(index)){ + REXY_THROW_FORMAT_ERROR("Invalid type given as dynamic precision"); + } + } + template + constexpr void cx_format_specs_handler::on_dynamic_precision(int index){ + this->parse_ctx.check_arg_id(index); + if(!is_dynamic_integer(index)){ + REXY_THROW_FORMAT_ERROR("Invalid type given as dynamic precision"); + } + } + template + template + constexpr void cx_format_specs_handler::on_dynamic_precision(It first, It last){ + on_dynamic_precision(find_static_named_arg_id(first, last)); + } + + /////////////////////////////format_specs_checker////////////////////////////// + template + constexpr format_specs_checker::format_specs_checker(const Handler& h, storage_type type): + Handler(h), arg_type(type){} + template + constexpr void format_specs_checker::on_precision(int p){ + check_precision(); + Handler::on_precision(p); + } + template + constexpr void format_specs_checker::on_dynamic_precision(void){ + check_precision(); + Handler::on_dynamic_precision(); + } + template + constexpr void format_specs_checker::on_dynamic_precision(int index){ + check_precision(); + Handler::on_dynamic_precision(index); + } + template + template + constexpr void format_specs_checker::on_dynamic_precision(It first, It last){ + check_precision(); + Handler::on_dynamic_precision(first, last); + } + template + constexpr void format_specs_checker::on_locale(void){ + check_arithmetic_type(); + Handler::on_locale(); + } + template + constexpr void format_specs_checker::on_sign(int s){ + requires_arithmetic_presentation = true; + check_arithmetic_type(); + Handler::on_sign(s); + } + template + constexpr void format_specs_checker::on_alt_form(void){ + requires_arithmetic_presentation = true; + check_arithmetic_type(); + Handler::on_alt_form(); + } + template + constexpr void format_specs_checker::on_zero_fill(void){ + requires_arithmetic_presentation = true; + check_arithmetic_type(); + Handler::on_zero_fill(); + } + + template + constexpr void format_specs_checker::on_type_option(int type){ + presentation& chosen = this->specs.present; + + switch(type){ + case 0: + break; + case 'b': + case 'B': + case 'd': + case 'o': + case 'x': + case 'X': + chosen = presentation::int_t; + break; + case 'c': + chosen = presentation::char_t; + break; + case 's': + chosen = presentation::string_t; + break; + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + chosen = presentation::float_t; + break; + case 'p': + chosen = presentation::ptr_t; + break; + default: + REXY_THROW_FORMAT_ERROR("Invalid type specifier"); + }; + + switch(arg_type){ + case storage_type::none_t: + REXY_THROW_FORMAT_ERROR("Invalid argument"); + break; + case storage_type::bool_t: + if(chosen == presentation::default_t){ + chosen = presentation::string_t; + } + if(chosen != presentation::string_t && chosen != presentation::int_t){ + REXY_THROW_FORMAT_ERROR("Invalid type specifier for bool"); + } + break; + case storage_type::char_t: + if(chosen == presentation::default_t){ + chosen = presentation::char_t; + } + if(chosen != presentation::char_t && chosen != presentation::int_t){ + REXY_THROW_FORMAT_ERROR("Invalid type specifier for character"); + } + break; + case storage_type::int_t: + case storage_type::uint_t: + case storage_type::long_long_t: + case storage_type::ulong_long_t: + if(chosen == presentation::default_t){ + chosen = presentation::int_t; + } + if(chosen != presentation::int_t && chosen != presentation::char_t){ + REXY_THROW_FORMAT_ERROR("Invalid type specifier for integral"); + } + break; + case storage_type::float_t: + case storage_type::double_t: + case storage_type::long_double_t: + if(chosen == presentation::default_t){ + chosen = presentation::float_t; + } + if(chosen != presentation::float_t){ + REXY_THROW_FORMAT_ERROR("Invalid type specifier for floating point"); + } + break; + case storage_type::char_ptr_t: + case storage_type::string_t: + if(chosen == presentation::default_t){ + chosen = presentation::string_t; + } + if(chosen != presentation::string_t){ + REXY_THROW_FORMAT_ERROR("Invalid type specifier for string"); + } + break; + case storage_type::ptr_t: + if(chosen == presentation::default_t){ + chosen = presentation::ptr_t; + } + if(chosen != presentation::ptr_t){ + REXY_THROW_FORMAT_ERROR("Invalid type specifier for pointer"); + } + break; + case storage_type::custom_t: + break; + }; + + if(requires_arithmetic_presentation && chosen != presentation::int_t && chosen != presentation::float_t){ + REXY_THROW_FORMAT_ERROR("Invalid type specifier with modifier requiring integer presentation"); + } + return Handler::on_type_option(type); + } + template + constexpr void format_specs_checker::check_precision(void){ + if(!(is_floating_storage_type(arg_type) || is_string_storage_type(arg_type))){ + REXY_THROW_FORMAT_ERROR("Precision not valid for argument type"); + } + } + template + constexpr void format_specs_checker::check_arithmetic_type(void){ + if(!is_integral_storage_type(arg_type)){ + REXY_THROW_FORMAT_ERROR("Formatting argument requires an arithmetic type"); + } + } + +} + +#endif diff --git a/include/rexy/detail/format/standard_types.hpp b/include/rexy/detail/format/standard_types.hpp new file mode 100644 index 0000000..6f2d406 --- /dev/null +++ b/include/rexy/detail/format/standard_types.hpp @@ -0,0 +1,38 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_STANDARD_TYPES_HPP +#define REXY_DETAIL_FORMAT_STANDARD_TYPES_HPP + +namespace rexy::fmt{ + + template + class formatter; + template + class basic_format_arg; + template + class basic_format_args; + template + class basic_format_context; + template + class basic_format_parse_context; + struct format_error; + +} + +#endif diff --git a/include/rexy/detail/format/storage.hpp b/include/rexy/detail/format/storage.hpp new file mode 100644 index 0000000..fbd7802 --- /dev/null +++ b/include/rexy/detail/format/storage.hpp @@ -0,0 +1,71 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_STORAGE_HPP +#define REXY_DETAIL_FORMAT_STORAGE_HPP + +#include //declval + +namespace rexy::fmt::detail{ + + //tag for use in basic_format_arg(s) + enum class storage_type{ + none_t = 0, + + bool_t, + char_t, + int_t, + uint_t, + long_long_t, + ulong_long_t, + float_t, + double_t, + long_double_t, + + char_ptr_t, + string_t, + ptr_t, + + custom_t + }; + constexpr bool is_integral_storage_type(storage_type t){ + return t >= storage_type::bool_t && t <= storage_type::long_double_t; + } + constexpr bool is_integer_storage_type(storage_type t){ + return t >= storage_type::bool_t && t <= storage_type::ulong_long_t; + } + constexpr bool is_floating_storage_type(storage_type t){ + return t >= storage_type::float_t && t <= storage_type::long_double_t; + } + constexpr bool is_string_storage_type(storage_type t){ + return t == storage_type::string_t || t == storage_type::char_ptr_t; + } + + //Mappings from actual type to the type used in basic_format_arg(s) + template + consteval storage_type map_to_storage_enum(void); + template + static constexpr storage_type map_to_storage_enum_v = map_to_storage_enum(); + template + struct map_to_stored_type_helper; + template + using stored_type_t = decltype(std::declval>()(std::declval())); + +} + +#endif diff --git a/include/rexy/detail/format/storage.tpp b/include/rexy/detail/format/storage.tpp new file mode 100644 index 0000000..19dc5c5 --- /dev/null +++ b/include/rexy/detail/format/storage.tpp @@ -0,0 +1,111 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_STORAGE_TPP +#define REXY_DETAIL_FORMAT_STORAGE_TPP + +#include "storage.hpp" + +#include "named_args.hpp" + +#include //is_pointer, remove_cvref, remove_pointer, is_same, is_arithmetic +#include //unsigned_integral, signed_integral, floating_point +#include //nullptr_t + +namespace rexy::fmt::detail{ + + ///////////////////////storage type mapping//////////////////////// + template + consteval storage_type map_to_storage_enum(void){ + using type = std::remove_cvref_t; + if constexpr(std::is_pointer_v){ + if constexpr(std::is_same_v>,Char>){ + return storage_type::char_ptr_t; + }else{ + return storage_type::ptr_t; + } + }else if constexpr(std::is_same_v>){ + return storage_type::string_t; + }else if constexpr(std::is_same_v){ + return storage_type::char_t; + }else if constexpr(std::is_same_v){ + return storage_type::none_t; + }else if constexpr(std::is_same_v){ + return storage_type::bool_t; + }else if constexpr(std::is_same_v){ + return storage_type::int_t; + }else if constexpr(std::is_same_v){ + return storage_type::uint_t; + }else if constexpr(std::is_same_v){ + return storage_type::long_long_t; + }else if constexpr(std::is_same_v){ + return storage_type::ulong_long_t; + }else if constexpr(std::is_same_v){ + return storage_type::float_t; + }else if constexpr(std::is_same_v){ + return storage_type::double_t; + }else if constexpr(std::is_same_v){ + return storage_type::long_double_t; + }else{ + return storage_type::custom_t; + } + } + + template + struct map_to_stored_type_helper{ + + using char_type = typename Context::char_type; + + template + requires (!std::is_arithmetic_v && !std::is_pointer_v && !NamedArg) + constexpr auto operator()(T) -> typename basic_format_arg::handle; + constexpr auto operator()(std::unsigned_integral auto i){ + if constexpr(sizeof(i) <= sizeof(unsigned int)){ + return (unsigned int){}; + }else{ + return (unsigned long long){}; + } + } + constexpr auto operator()(std::signed_integral auto i){ + if constexpr(sizeof(i) <= sizeof(int)){ + return int{}; + }else{ + return (long long){}; + } + } + template + constexpr auto operator()(T) -> std::remove_cvref_t; + constexpr auto operator()(char_type) -> char_type; + constexpr auto operator()(bool) -> bool; + constexpr auto operator()(float) -> float; + constexpr auto operator()(double) -> double; + constexpr auto operator()(long double) -> long double; + constexpr auto operator()(const char_type*) -> const char_type*; + constexpr auto operator()(basic_string_view) -> basic_string_view; + constexpr auto operator()(basic_string) -> basic_string_view; + template + requires (!std::is_same_v,char_type>) + constexpr auto operator()(T*) -> const void*; + constexpr auto operator()(std::nullptr_t) -> const void*; + template + constexpr auto operator()(T t) -> decltype((*this)(t.value)); + }; + +} + +#endif diff --git a/include/rexy/detail/format/traits.hpp b/include/rexy/detail/format/traits.hpp new file mode 100644 index 0000000..6d8a132 --- /dev/null +++ b/include/rexy/detail/format/traits.hpp @@ -0,0 +1,126 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_TRAITS_HPP +#define REXY_DETAIL_FORMAT_TRAITS_HPP + +#include //is_convertible, is_floating_point, integral_constant, is_same, remove_cvref +#include //integral, floating_point +#include //size_t + +#include "standard_types.hpp" +#include "storage.hpp" +#include "named_args.hpp" +#include "internal_types.hpp" + +namespace rexy::fmt::detail{ + + template + struct valid_dynamic_index{ + static constexpr bool value = std::is_convertible_v && !std::is_floating_point_v; + }; + template + static constexpr bool valid_dynamic_index_v = valid_dynamic_index::value; + + template + struct is_char_or_bool : public std::false_type{}; + template<> + struct is_char_or_bool : public std::true_type{}; + template<> + struct is_char_or_bool : public std::true_type{}; + template<> + struct is_char_or_bool : public std::true_type{}; + + template + concept Integral = std::integral && !is_char_or_bool::value; + template + concept Bool = std::is_same_v,bool>; + template + concept Floating = std::floating_point; + template + concept Arithmetic = Integral || Floating; + template + concept Handle = std::is_same_v,typename basic_format_arg::handle>; + template + concept Formatter_Char = std::is_same_v || std::is_same_v; + template + struct is_handle{ + static constexpr bool value = Handle; + }; + template + static constexpr bool is_handle_v = is_handle::value; + + template + struct extract_char_type_from_context; + template + struct extract_char_type_from_context>{ + using type = Char; + }; + template + using extract_char_type_from_context_t = typename extract_char_type_from_context::type; + + + template + constexpr bool is_dynamic_integer_impl(int index){ + using mapped_t = stored_type_t; + if(index == I){ + return valid_dynamic_index_v; + } + if constexpr(sizeof...(Args) > 0){ + return is_dynamic_integer_impl(index); + } + return false; + } + template + constexpr bool is_dynamic_integer(int index){ + using fmt_ctx_t = fmt_context_t; + if constexpr(sizeof...(Args) == 0){ + return false; + }else{ + return is_dynamic_integer_impl<0,fmt_ctx_t,Args...>(index); + } + } + + static constexpr unsigned char utf_test[] = "\u00B5"; + constexpr bool is_utf8(void){ + using uchar = unsigned char; + return (sizeof(utf_test) == 3) && (uchar(utf_test[0]) == 0xC2) && (uchar(utf_test[1]) == 0xB5); + } + + template + struct is_utf8_encoded_string_type{ + static constexpr bool value = false; + }; + template<> + struct is_utf8_encoded_string_type{ + static constexpr bool value = true; + }; + template<> + struct is_utf8_encoded_string_type{ + static constexpr bool value = is_utf8(); + }; + + template + static constexpr bool is_utf8_encoded_string_type_v = is_utf8_encoded_string_type::value; + + template + concept UTF8_String = is_utf8_encoded_string_type_v; + +} + +#endif diff --git a/include/rexy/detail/format/utf_iterator.hpp b/include/rexy/detail/format/utf_iterator.hpp new file mode 100644 index 0000000..8350be3 --- /dev/null +++ b/include/rexy/detail/format/utf_iterator.hpp @@ -0,0 +1,91 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_DETAIL_FORMAT_UTF_ITERATOR_TPP +#define REXY_DETAIL_FORMAT_UTF_ITERATOR_TPP + +namespace rexy::fmt::detail{ + + template + class utf8_iterator + { + private: + const Char* m_start = nullptr; + const Char* m_last = nullptr; + const Char* m_next = nullptr; + char32_t m_value = 0; + + public: + constexpr utf8_iterator(const Char* first, const Char* last): + m_start(first), m_last(last) + { + m_next = convert_codepoint(m_start, m_last, m_value); + } + + constexpr utf8_iterator& operator++(void){ + m_start = m_next; + if(m_start != m_last){ + m_next = convert_codepoint(m_start, m_last, m_value); + } + return *this; + } + constexpr utf8_iterator operator++(int){ + auto tmp = *this; + ++(*this); + return tmp; + } + constexpr char32_t operator*(void)const{ + return m_value; + } + constexpr bool valid(void)const{ + return m_start != m_last; + } + constexpr std::size_t byte_count(void)const{ + return m_next - m_start; + } + + private: + static constexpr const Char* convert_codepoint(const Char* first, const Char* last, char32_t& codepoint){ + const std::size_t maxlen = last - first; + if((*first & 0x80) == 0){ + codepoint = first[0]; + return first + 1; + }else if((*first & 0xE0) == 0xC0 && maxlen > 1){ + codepoint = ((first[0] & 0x1F) << 6); + codepoint += (first[1] & 0x3F); + return first + 2; + }else if((*first & 0xF0) == 0xE0 && maxlen > 2){ + codepoint = ((first[0] & 0x0F) << 12); + codepoint += ((first[1] & 0x3F) << 6); + codepoint += (first[2] & 0x3F); + return first + 3; + }else if((*first & 0xF8) == 0xF0 && maxlen > 3){ + codepoint = ((first[0] & 0x07) << 18); + codepoint += ((first[1] & 0x3F) << 12); + codepoint += (first[2] & 0x3F) << 6; + codepoint += (first[3] & 0x3F); + return first + 4; + } + codepoint = 0; + return first; + } + }; + +} + +#endif diff --git a/include/rexy/format.hpp b/include/rexy/format.hpp new file mode 100644 index 0000000..d634ef7 --- /dev/null +++ b/include/rexy/format.hpp @@ -0,0 +1,147 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_FORMAT_HPP +#define REXY_FORMAT_HPP + +#include "string.hpp" +#include "string_view.hpp" + +#include "detail/format/standard_types.hpp" +#include "detail/format/internal_types.hpp" +#include "detail/format/arg_store.hpp" +#include "detail/format/format_string.hpp" +#include "detail/format/format_args.hpp" + +#include //FILE + +#include //size_t +#include //locale + +namespace rexy{ + + //Alias declarations of standard defined types + using format_context = fmt::detail::fmt_context_t; + using wformat_context = fmt::detail::fmt_context_t; + using parse_context = fmt::detail::parse_context_t; + using wparse_context = fmt::detail::parse_context_t; + using format_arg = fmt::basic_format_arg; + using wformat_arg = fmt::basic_format_arg; + using format_args = fmt::basic_format_args; + using wformat_args = fmt::basic_format_args; + + template + fmt::detail::basic_format_arg_store make_format_args(Args&&... args); + template + fmt::detail::basic_format_arg_store make_wformat_args(Args&&... args); + + template + OutIt vformat_to(OutIt out, string_view fmt, format_args args); + template + OutIt vformat_to(OutIt out, const std::locale& loc, string_view fmt, wformat_args args); + + string vformat(string_view fmt, format_args args); + string vformat(const std::locale& loc, string_view fmt, format_args args); + + template + OutIt format_to(OutIt out, fmt::detail::format_string fmt, Args&&... args); + template + OutIt format_to(OutIt out, const std::locale& loc, fmt::detail::format_string fmt, Args&&... args); + + template + format_to_n_result format_to_n(OutIt out, std::size_t max, fmt::detail::format_string fmt, Args&&... args); + template + format_to_n_result format_to_n(OutIt out, const std::locale& loc, std::size_t max, fmt::detail::format_string fmt, Args&&... args); + + template + string format(fmt::detail::format_string fmt, Args&&... args); + template + string format(const std::locale& loc, fmt::detail::format_string fmt, Args&&... args); + + template + std::size_t formatted_size(fmt::detail::format_string fmt, Args&&... args); + template + std::size_t formatted_size(const std::locale& loc, fmt::detail::format_string fmt, Args&&... args); + + + + + template + OutIt vformat_to(OutIt out, wstring_view fmt, wformat_args args); + template + OutIt vformat_to(OutIt out, const std::locale& loc, wstring_view fmt, wformat_args args); + + wstring vformat(wstring_view fmt, format_args args); + wstring vformat(const std::locale& loc, wstring_view fmt, wformat_args args); + + template + OutIt format_to(OutIt out, fmt::detail::wformat_string fmt, Args&&... args); + template + OutIt format_to(OutIt out, const std::locale& loc, fmt::detail::wformat_string fmt, Args&&... args); + + template + format_to_n_result format_to_n(OutIt out, std::size_t max, fmt::detail::wformat_string fmt, Args&&... args); + template + format_to_n_result format_to_n(OutIt out, const std::locale& loc, std::size_t max, fmt::detail::wformat_string fmt, Args&&... args); + + template + wstring format(fmt::detail::wformat_string fmt, Args&&... args); + template + wstring format(const std::locale& loc, fmt::detail::wformat_string fmt, Args&&... args); + + template + std::size_t formatted_size(fmt::detail::wformat_string fmt, Args&&... args); + template + std::size_t formatted_size(const std::locale& loc, fmt::detail::wformat_string fmt, Args&&... args); + + + + + template + std::size_t print(fmt::detail::format_string fmt, Args&&... args); + template + std::size_t print(FILE* stream, fmt::detail::format_string fmt, Args&&... args); + + template + std::size_t println(fmt::detail::format_string fmt, Args&&... args); + template + std::size_t println(FILE* stream, fmt::detail::format_string fmt, Args&&... args); + + std::size_t vprint_unicode(string_view fmt, format_args args); + std::size_t vprint_unicode(FILE* stream, string_view fmt, format_args args); + + + + template + constexpr auto arg(const Char* name, T&& t); + template + constexpr auto arg(rexy::basic_string_view name, T&& t); + template + constexpr auto arg(T&& t); + + inline namespace fmt_literals{ + template + constexpr auto operator""_a(void); + } + +} + + +#include "format.tpp" + +#endif diff --git a/include/rexy/format.tpp b/include/rexy/format.tpp new file mode 100644 index 0000000..f739fad --- /dev/null +++ b/include/rexy/format.tpp @@ -0,0 +1,338 @@ +/** + This file is a part of rexy's general purpose library + Copyright (C) 2022 rexy712 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef REXY_FORMAT_TPP +#define REXY_FORMAT_TPP + +#include "detail/format/format_args.tpp" +#include "detail/format/formatter.tpp" +#include "detail/format/storage.tpp" +#include "detail/format/format_string.tpp" +#include "detail/format/arg_store.tpp" +#include "detail/format/output_buffer.tpp" +#include "detail/format/basic_types.tpp" +#include "detail/format/specs_handler.tpp" +#include "detail/format/parse.tpp" +#include "detail/format/context_handler.tpp" +#include "detail/format/named_args.tpp" +#include "detail/format/parse_context.tpp" +#include "detail/format/format_context.tpp" + +#include //back_insert_iterator +#include //move, forward +#include //is_same +#include //locale +#include //size_t + +namespace rexy{ + + namespace fmt::detail{ + + template + OutIt vformat_to(OutIt out, basic_string_view> fmt, impl_format_args> args){ + using char_type = Char; + using format_handler_t = fmt::detail::format_handler; + + if constexpr(std::is_same_v>){ + + fmt::detail::format_handler handler{out, fmt, args}; + fmt::detail::parse::perform_parse(handler, fmt); + + return std::move(out); + }else{ + + fmt::detail::basic_format_output_buffer outbuf{out}; + fmt::detail::format_handler handler{fmt::detail::output_iterator_t{outbuf}, fmt, args}; + + fmt::detail::parse::perform_parse(handler, fmt); + + return std::move(outbuf.out()); + } + + } + template + OutIt vformat_to(OutIt out, const std::locale& loc, basic_string_view> fmt, impl_format_args> args){ + using char_type = Char; + using format_handler_t = fmt::detail::format_handler; + + if constexpr(std::is_same_v>){ + + fmt::detail::format_handler handler{out, fmt, args, loc}; + fmt::detail::parse::perform_parse(handler, fmt); + + return std::move(out); + }else{ + + fmt::detail::basic_format_output_buffer outbuf{out}; + fmt::detail::format_handler handler{fmt::detail::output_iterator_t{outbuf}, fmt, args, loc}; + + fmt::detail::parse::perform_parse(handler, fmt); + + return std::move(outbuf.out()); + } + } + + + template + basic_format_arg_store,Args...> make_format_args(Args&&... args){ + return basic_format_arg_store,Args...>{std::forward(args)...}; + } + } + + template + fmt::detail::basic_format_arg_store make_format_args(Args&&... args){ + return fmt::detail::basic_format_arg_store{std::forward(args)...}; + } + template + fmt::detail::basic_format_arg_store make_wformat_args(Args&&... args){ + return fmt::detail::basic_format_arg_store{std::forward(args)...}; + } + + + + //////////////////////////////////////char overloads/////////////////////////////////////////////// + template + OutIt vformat_to(OutIt out, string_view fmt, format_args args){ + return fmt::detail::vformat_to(std::move(out), fmt, std::move(args)); + } + template + OutIt vformat_to(OutIt out, const std::locale& loc, string_view fmt, format_args args){ + return fmt::detail::vformat_to(std::move(out), loc, fmt, std::move(args)); + } + + string vformat(string_view fmt, format_args args){ + using outit_t = std::back_insert_iterator; + string output; + outit_t out{output}; + vformat_to(std::move(out), fmt, std::move(args)); + return output; + } + + string vformat(const std::locale& loc, string_view fmt, format_args args){ + using outit_t = std::back_insert_iterator; + string output; + outit_t out{output}; + vformat_to(std::move(out), loc, fmt, std::move(args)); + return output; + } + + template + OutIt format_to(OutIt out, fmt::detail::format_string fmt, Args&&... args){ + return vformat_to(std::move(out), fmt.str, make_format_args(std::forward(args)...)); + } + + template + OutIt format_to(OutIt out, const std::locale& loc, fmt::detail::format_string fmt, Args&&... args){ + return vformat_to(std::move(out), loc, fmt.str, make_format_args(std::forward(args)...)); + } + + template + format_to_n_result format_to_n(OutIt out, std::size_t max, fmt::detail::format_string fmt, Args&&... args){ + fmt::detail::basic_format_output_n_buffer outbuf{std::move(out), max}; + vformat_to(fmt::detail::output_iterator_t{outbuf}, fmt.str, make_format_args(std::forward(args)...)); + return {outbuf.out(), outbuf.count()}; + } + + template + format_to_n_result format_to_n(OutIt out, const std::locale& loc, std::size_t max, fmt::detail::format_string fmt, Args&&... args){ + fmt::detail::basic_format_output_n_buffer outbuf{std::move(out), max}; + vformat_to(fmt::detail::output_iterator_t{outbuf}, loc, fmt.str, make_format_args(std::forward(args)...)); + return {outbuf.out(), outbuf.count()}; + } + + template + string format(fmt::detail::format_string fmt, Args&&... args){ + using outit_t = std::back_insert_iterator; + string output; + outit_t out{output}; + vformat_to(std::move(out), fmt.str, make_format_args(std::forward(args)...)); + return output; + } + + template + string format(const std::locale& loc, fmt::detail::format_string fmt, Args&&... args){ + using outit_t = std::back_insert_iterator; + string output; + outit_t out{output}; + vformat_to(std::move(out), loc, fmt.str, make_format_args(std::forward(args)...)); + return output; + } + + template + std::size_t formatted_size(fmt::detail::format_string fmt, Args&&... args){ + fmt::detail::basic_format_size_buffer counter; + vformat_to(fmt::detail::output_iterator_t{counter}, fmt.str, make_format_args(std::forward(args)...)); + return counter.count(); + } + template + std::size_t formatted_size(const std::locale& loc, fmt::detail::format_string fmt, Args&&... args){ + fmt::detail::basic_format_size_buffer counter; + vformat_to(fmt::detail::output_iterator_t{counter}, loc, fmt.str, make_format_args(std::forward(args)...)); + return counter.count(); + } + + + + //////////////////////////////////////wchar_t overloads/////////////////////////////////////////////// + template + OutIt vformat_to(OutIt out, wstring_view fmt, wformat_args args){ + return fmt::detail::vformat_to(std::move(out), fmt, std::move(args)); + } + template + OutIt vformat_to(OutIt out, const std::locale& loc, wstring_view fmt, wformat_args args){ + return fmt::detail::vformat_to(std::move(out), loc, fmt, std::move(args)); + } + + wstring vformat(wstring_view fmt, wformat_args args){ + using outit_t = std::back_insert_iterator; + wstring output; + outit_t out{output}; + vformat_to(std::move(out), fmt, std::move(args)); + return output; + } + wstring vformat(const std::locale& loc, wstring_view fmt, wformat_args args){ + using outit_t = std::back_insert_iterator; + wstring output; + outit_t out{output}; + vformat_to(std::move(out), loc, fmt, std::move(args)); + return output; + } + + template + OutIt format_to(OutIt out, fmt::detail::wformat_string fmt, Args&&... args){ + return vformat_to(std::move(out), fmt.str, make_wformat_args(std::forward(args)...)); + } + template + OutIt format_to(OutIt out, const std::locale& loc, fmt::detail::wformat_string fmt, Args&&... args){ + return vformat_to(std::move(out), loc, fmt.str, make_wformat_args(std::forward(args)...)); + } + + template + format_to_n_result format_to_n(OutIt out, std::size_t max, fmt::detail::wformat_string fmt, Args&&... args){ + fmt::detail::basic_format_output_n_buffer outbuf{std::move(out), max}; + vformat_to(fmt::detail::output_iterator_t{outbuf}, fmt.str, make_wformat_args(std::forward(args)...)); + return {outbuf.out(), outbuf.count()}; + } + template + format_to_n_result format_to_n(OutIt out, const std::locale& loc, std::size_t max, fmt::detail::wformat_string fmt, Args&&... args){ + fmt::detail::basic_format_output_n_buffer outbuf{std::move(out), max}; + vformat_to(fmt::detail::output_iterator_t{outbuf}, loc, fmt.str, make_wformat_args(std::forward(args)...)); + return {outbuf.out(), outbuf.count()}; + } + + template + wstring format(fmt::detail::wformat_string fmt, Args&&... args){ + using outit_t = std::back_insert_iterator; + wstring output; + outit_t out{output}; + vformat_to(out, fmt.str, make_wformat_args(std::forward(args)...)); + return output; + } + template + wstring format(const std::locale& loc, fmt::detail::wformat_string fmt, Args&&... args){ + using outit_t = std::back_insert_iterator; + wstring output; + outit_t out{output}; + vformat_to(out, fmt.str, make_wformat_args(std::forward(args)...)); + return output; + } + + template + std::size_t formatted_size(fmt::detail::wformat_string fmt, Args&&... args){ + + } + template + std::size_t formatted_size(const std::locale& loc, fmt::detail::wformat_string fmt, Args&&... args); + + + + + //////////////////////////////////////print char overloads/////////////////////////////////////////////// + template + std::size_t print(fmt::detail::format_string fmt, Args&&... args){ + fmt::detail::basic_format_output_buffer> out{fmt::detail::print_iterator{stdout}}; + vformat_to(fmt::detail::output_iterator_t{out}, fmt.str, make_format_args(std::forward(args)...)); + return out.count(); + } + template + std::size_t print(FILE* stream, fmt::detail::format_string fmt, Args&&... args){ + fmt::detail::basic_format_output_buffer> out{fmt::detail::print_iterator{stream}}; + vformat_to(fmt::detail::output_iterator_t{out}, fmt.str, make_format_args(std::forward(args)...)); + return out.count(); + } + template + std::size_t println(fmt::detail::format_string fmt, Args&&... args){ + fmt::detail::basic_format_output_buffer> out{fmt::detail::print_iterator{stdout}}; + vformat_to(fmt::detail::output_iterator_t{out}, fmt.str, make_format_args(std::forward(args)...)); + out.push_back('\n'); + return out.count(); + } + template + std::size_t println(FILE* stream, fmt::detail::format_string fmt, Args&&... args){ + fmt::detail::basic_format_output_buffer> out{fmt::detail::print_iterator{stream}}; + vformat_to(fmt::detail::output_iterator_t{out}, fmt.str, make_format_args(std::forward(args)...)); + out.push_back('\n'); + return out.count(); + } + std::size_t vprint_unicode(string_view fmt, format_args args){ + fmt::detail::basic_format_output_buffer> out{fmt::detail::print_iterator{stdout}}; + vformat_to(fmt::detail::output_iterator_t{out}, fmt, args); + return out.count(); + } + std::size_t vprint_unicode(FILE* stream, string_view fmt, format_args args){ + fmt::detail::basic_format_output_buffer> out{fmt::detail::print_iterator{stream}}; + vformat_to(fmt::detail::output_iterator_t{out}, fmt, args); + return out.count(); + } + + + + //creates a runtime argument from a c string + template + constexpr auto arg(const Char* name, T&& t){ + return fmt::detail::runtime_arg{std::forward(t), name}; + } + + //creates a runtime argument from a string view + template + constexpr auto arg(rexy::basic_string_view name, T&& t){ + return fmt::detail::runtime_arg{std::forward(t), name}; + } + + //create a compile time argument from a string literal + template + constexpr auto arg(T&& t){ + return fmt::detail::static_arg{t}; + } + + inline namespace fmt_literals{ + //create a compile time argument from a string literal + template + constexpr auto operator""_a(void){ + return fmt::detail::arg_literal_result{}; + } + } + +} + +#undef REXY_REAL_STRINGIFY +#undef REXY_STRINGIFY +#undef REXY_THROW_FORMAT_ERROR + +#endif + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 991b52f..9af46fa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,6 +11,9 @@ if(ENABLE_PROFILING) endif() add_executable(basic_string "basic_string.cpp") +add_executable(format "format.cpp") set_target_properties(basic_string PROPERTIES OUTPUT_NAME basic_string) +set_target_properties(format PROPERTIES OUTPUT_NAME format) add_test(NAME basic_string-test COMMAND basic_string) +add_test(NAME format-test COMMAND format) diff --git a/tests/format.cpp b/tests/format.cpp new file mode 100644 index 0000000..9bd129f --- /dev/null +++ b/tests/format.cpp @@ -0,0 +1,111 @@ +#define LIBREXY_ENABLE_DEBUG_LEVEL 2 + +#include "rexy/format.hpp" + +#include "rexy/string.hpp" +#include "rexy/debug_print.hpp" +#include "rexy/cx/string.hpp" + +#include + +using namespace rexy::str_literals; + +[[noreturn]] +void test_fail(rexy::string_view failed, rexy::string_view expected){ + rexy::debug::print_error("expected \"%s\", got \"%s\"\n", expected.c_str(), failed.c_str()); + exit(-1); +} +[[noreturn]] +void test_fail(void){ + exit(-1); +} + +#define PERFORM_TEST(desired, formt, ...) \ +do{ \ + rexy::string str = rexy::format((formt) __VA_OPT__(,) __VA_ARGS__); \ + if(str != (desired)){ \ + test_fail(str, desired); \ + } \ +}while(0) + +struct X +{ + int i; + constexpr operator int(void)const{return i;} +}; + +template +class rexy::formatter : public rexy::formatter +{ +public: + template + auto format(X&, FormatContext& ctx) -> decltype(ctx.out()){ + *(ctx.out()++) = 'X'; + return std::move(ctx.out()); + //return rexy::formatter::format(t.i, ctx); + } + +}; + + + +void do_brace_escapes(void){ + PERFORM_TEST("this is } a test string"_sv, "this is }} a test string", nullptr); + PERFORM_TEST("this is a test {string"_sv, "this is a test {{string", nullptr); + PERFORM_TEST("this }is a test {string"_sv, "this }}is a test {{string", nullptr); + PERFORM_TEST("this {is a tes}t string"_sv, "this {{is a tes}}t string", nullptr); +} + + +void do_empty_format(void){ +do{ + rexy::string str = rexy::format("{}", 0); + if(str != "0"_sv){ + test_fail(str.c_str(), "0"_sv); + } +}while(0); + PERFORM_TEST("0"_sv, "{}", 0); + PERFORM_TEST("0"_sv, "{}", 0ul); + PERFORM_TEST("0"_sv, "{}", short{0}); + PERFORM_TEST("0"_sv, "{}", 0.0f); + PERFORM_TEST("0x0"_sv, "{}", nullptr); + PERFORM_TEST("true"_sv, "{}", true); + PERFORM_TEST("cstring"_sv, "{}", "cstring"); + PERFORM_TEST("string_view"_sv, "{}", "string_view"_sv); + PERFORM_TEST("string"_sv, "{}", rexy::string{"string"}); + PERFORM_TEST("c"_sv, "{}", 'c'); +} + +void do_index_specifiers(void){ + PERFORM_TEST("2"_sv, "{2}", 0, 1, 2); + PERFORM_TEST("0"_sv, "{0}", 0, 1, 2); + PERFORM_TEST("1"_sv, "{1}", "string", 1, 2.8f); +} + +void do_dynamic_index_specifiers(void){ + PERFORM_TEST("| 5|"_sv, "|{:{}}|", 5, 10); + PERFORM_TEST("| 5.80|"_sv, "|{:{}.{}f}|", 5.8f, 10, 2); + PERFORM_TEST("| 5|"_sv, "|{1:{0}}|", 10, 5); + PERFORM_TEST("| 5.80|"_sv, "|{0:{4}.{1}f}|", 5.8f, 2, 7, 8, 10); +} + +void do_fill_and_align(void){ + PERFORM_TEST("*****6"_sv, "{:*>6}", 6); + PERFORM_TEST("6*****"_sv, "{:*<6}", 6); + PERFORM_TEST("**6***"_sv, "{:*^6}", 6); + + PERFORM_TEST("*8.20*"_sv, "{:*^6.2f}", 8.2f); + PERFORM_TEST("-2****"_sv, "{:*<6}", -2); + PERFORM_TEST("***str"_sv, "{:*>6}", "str"); +} + +//TODO more test coverage + +int main(){ + do_brace_escapes(); + do_empty_format(); + do_index_specifiers(); + do_dynamic_index_specifiers(); + do_fill_and_align(); +} +