355 lines
10 KiB
C++
355 lines
10 KiB
C++
/**
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef REXY_DETAIL_FORMAT_PARSE_TPP
|
|
#define REXY_DETAIL_FORMAT_PARSE_TPP
|
|
|
|
#include "parse.hpp"
|
|
|
|
#include "format_error.hpp"
|
|
|
|
#include "../../string_view.hpp"
|
|
|
|
#include <cstddef> //size_t
|
|
#include <type_traits> //is_integral, remove_cvref, add_lvalue_reference, remove_reference
|
|
#include <algorithm> //min
|
|
|
|
namespace rexy::fmt::detail::parse{
|
|
|
|
template<class Char>
|
|
constexpr auto find_first_of_it(Char v,
|
|
typename basic_string_view<Char>::const_iterator start_it,
|
|
typename basic_string_view<Char>::const_iterator last_it)
|
|
{
|
|
const basic_string_view<Char> 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<class Char>
|
|
constexpr auto find_first_of_it(const basic_string_view<Char>& str, Char v){
|
|
return find_first_of_it(v, str.begin(), str.end());
|
|
}
|
|
template<class It>
|
|
constexpr bool check_duplicate_char(It start, It end){
|
|
if(start == end){
|
|
return false;
|
|
}
|
|
const auto next = start + 1;
|
|
return *start == *next;
|
|
}
|
|
template<class Char>
|
|
constexpr bool is_a_number(Char c){
|
|
return (c >= '0' && c <= '9');
|
|
}
|
|
template<class Char>
|
|
constexpr bool is_valid_name_char(Char c){
|
|
return (c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
(c == '_');
|
|
}
|
|
template<class It>
|
|
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<class T>
|
|
constexpr long long dynamic_integer_retriever::operator()(T&& t){
|
|
if constexpr(std::is_integral_v<std::remove_cvref_t<T>>){
|
|
return static_cast<long long>(t);
|
|
}else{
|
|
REXY_THROW_FORMAT_ERROR("Invalid dynamic specifier");
|
|
}
|
|
return {};
|
|
}
|
|
template<class Specs>
|
|
constexpr decltype(auto) dynamic_width_adapter<Specs>::operator()(void){
|
|
return specs.on_dynamic_width();
|
|
}
|
|
template<class Specs>
|
|
constexpr decltype(auto) dynamic_width_adapter<Specs>::operator()(int i){
|
|
return specs.on_dynamic_width(i);
|
|
}
|
|
template<class Specs>
|
|
template<class It>
|
|
constexpr decltype(auto) dynamic_width_adapter<Specs>::operator()(It start, It last){
|
|
return specs.on_dynamic_width(start, last);
|
|
}
|
|
template<class Specs>
|
|
constexpr decltype(auto) dynamic_precision_adapter<Specs>::operator()(void){
|
|
return specs.on_dynamic_precision();
|
|
}
|
|
template<class Specs>
|
|
constexpr decltype(auto) dynamic_precision_adapter<Specs>::operator()(int i){
|
|
return specs.on_dynamic_precision(i);
|
|
}
|
|
template<class Specs>
|
|
template<class It>
|
|
constexpr decltype(auto) dynamic_precision_adapter<Specs>::operator()(It start, It last){
|
|
return specs.on_dynamic_precision(start, last);
|
|
}
|
|
template<class Handler>
|
|
constexpr decltype(auto) dynamic_index_adapter<Handler>::operator()(void){
|
|
return (id = handler.on_arg_id());
|
|
}
|
|
template<class Handler>
|
|
constexpr decltype(auto) dynamic_index_adapter<Handler>::operator()(int i){
|
|
id = i;
|
|
return handler.on_arg_id(i);
|
|
}
|
|
template<class Handler>
|
|
template<class It>
|
|
constexpr decltype(auto) dynamic_index_adapter<Handler>::operator()(It start, It last){
|
|
id = handler.on_arg_id(start, last);
|
|
return id;
|
|
}
|
|
|
|
template<class It, class FormatSpecs>
|
|
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<class It, class FormatSpecs>
|
|
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<class It, class FormatSpecs>
|
|
constexpr It perform_standard_alt_form_option(It start, It last, FormatSpecs&& specs){
|
|
if(*start == '#'){
|
|
specs.on_alt_form();
|
|
++start;
|
|
}
|
|
return start;
|
|
}
|
|
template<class It, class FormatSpecs>
|
|
constexpr It perform_standard_zero_fill_option(It start, It last, FormatSpecs&& specs){
|
|
if(*start == '0'){
|
|
specs.on_zero_fill();
|
|
++start;
|
|
}
|
|
return start;
|
|
}
|
|
template<class It, class Adapter>
|
|
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;
|
|
}
|
|
for(auto it = start+1;it != last;++it){
|
|
if(!is_a_number(*it)){
|
|
func(to_integer(start, it));
|
|
return it;
|
|
}
|
|
}
|
|
return last;
|
|
}
|
|
template<class It, class Adapter>
|
|
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<class It, class FormatSpecs>
|
|
constexpr It perform_standard_width_option(It start, It last, FormatSpecs&& specs){
|
|
using specs_t = std::add_lvalue_reference_t<std::remove_reference_t<FormatSpecs>>;
|
|
if(*start == '{'){
|
|
return perform_standard_nested_replacement_field(start+1, last, dynamic_width_adapter<specs_t>{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<class It, class FormatSpecs>
|
|
constexpr It perform_standard_precision_option(It start, It last, FormatSpecs&& specs){
|
|
using specs_t = std::add_lvalue_reference_t<std::remove_reference_t<FormatSpecs>>;
|
|
if(*start != '.'){
|
|
return start;
|
|
}
|
|
++start;
|
|
if(*start == '{'){
|
|
return perform_standard_nested_replacement_field(start+1, last, dynamic_precision_adapter<specs_t>{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<class It, class FormatSpecs>
|
|
constexpr It perform_standard_locale_option(It start, It last, FormatSpecs&& specs){
|
|
if(*start == 'L'){
|
|
specs.on_locale();
|
|
return ++start;
|
|
}
|
|
return start;
|
|
}
|
|
template<class It, class FormatSpecs>
|
|
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<class It, class FormatSpecs>
|
|
constexpr It perform_standard_parse(It start, It last, FormatSpecs&& specs){
|
|
using specs_t = std::add_lvalue_reference_t<std::remove_reference_t<FormatSpecs>>;
|
|
using std_fmt_fn = It(*)(It,It,specs_t);
|
|
constexpr std_fmt_fn fns[] = {
|
|
perform_standard_fill_align_option<It,specs_t>,
|
|
perform_standard_sign_option<It,specs_t>,
|
|
perform_standard_alt_form_option<It,specs_t>,
|
|
perform_standard_zero_fill_option<It,specs_t>,
|
|
perform_standard_width_option<It,specs_t>,
|
|
perform_standard_precision_option<It,specs_t>,
|
|
perform_standard_locale_option<It,specs_t>,
|
|
perform_standard_type_option<It,specs_t>
|
|
};
|
|
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<class Handler, class It>
|
|
constexpr It perform_format_index_spec(Handler&& handler, It start, It last, std::size_t& id){
|
|
dynamic_index_adapter<Handler> 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<class Handler, class It>
|
|
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<class Handler, class Char>
|
|
constexpr void perform_parse(Handler&& handler, basic_string_view<Char> 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
|