223 lines
6.1 KiB
Ragel
223 lines
6.1 KiB
Ragel
/*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* Copyright 2017 Danny Robson <danny@nerdcruft.net>
|
|
*/
|
|
|
|
#include "./format.hpp"
|
|
|
|
#include <iostream>
|
|
|
|
namespace util::format {
|
|
std::ostream&
|
|
operator<< (std::ostream &os, type_t val)
|
|
{
|
|
switch (val) {
|
|
case type_t::LITERAL: return os << "LITERAL";
|
|
case type_t::USER: return os << "USER";
|
|
case type_t::ESCAPE: return os << "ESCAPE";
|
|
case type_t::SIGNED: return os << "SIGNED";
|
|
case type_t::UNSIGNED: return os << "UNSIGNED";
|
|
case type_t::REAL: return os << "REAL";
|
|
case type_t::STRING: return os << "STRING";
|
|
case type_t::CHAR: return os << "CHAR";
|
|
case type_t::POINTER: return os << "POINTER";
|
|
case type_t::COUNT: return os << "COUNT";
|
|
}
|
|
|
|
return os << "UNKNOWN_" << static_cast<std::underlying_type_t<type_t>> (val);
|
|
}
|
|
|
|
|
|
std::ostream&
|
|
operator<< (std::ostream &os, const specifier &val)
|
|
{
|
|
return os << "{ fmt: " << val.fmt << ", type: " << val.type << " }";
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
%%{
|
|
machine printf;
|
|
|
|
parameter = digit+ '$';
|
|
|
|
flag = '+' %{ s.flags.plus = true; }
|
|
| '-' %{ s.flags.minus = true; }
|
|
| ' ' %{ s.flags.space = true; }
|
|
| '0' %{ s.flags.zero = true; }
|
|
| '#' %{ s.flags.hash = true; }
|
|
;
|
|
|
|
width = digit+ >{ s.width = 0; } ${ s.width *= 10; s.width += fc - '0'; };
|
|
|
|
# precision may be zero digits which implies zero precision.
|
|
precision = '.' >{ s.precision = 0; } digit* ${ s.precision *= 10; s.precision += fc - '0'; };
|
|
|
|
length =
|
|
'hh' %{ s.length = sizeof (char); }
|
|
| 'h' %{ s.length = sizeof (short); }
|
|
| 'l' %{ s.length = sizeof (long); }
|
|
| 'll' %{ s.length = sizeof (long long); }
|
|
| 'L' %{ s.length = sizeof (long double); }
|
|
| 'z' %{ s.length = sizeof (size_t); }
|
|
| 'j' %{ s.length = sizeof (intmax_t); }
|
|
| 't' %{ s.length = sizeof (ptrdiff_t); }
|
|
;
|
|
|
|
type = (
|
|
'!' >{ s.type = type_t::USER; }
|
|
| '%' >{ s.type = type_t::ESCAPE; }
|
|
| (
|
|
'd'
|
|
| 'i'
|
|
) >{ s.type = type_t::SIGNED; }
|
|
| (
|
|
'u'
|
|
| 'x' %{ s.base = 16; }
|
|
| 'X' %{ s.base = 16; s.upper = true; }
|
|
| 'o' %{ s.base = 8; }
|
|
) >{ s.type = type_t::UNSIGNED; }
|
|
| (
|
|
('f' | 'F' %{ s.upper = true; }) %{ s.representation = specifier::FIXED; }
|
|
| ('e' | 'E' %{ s.upper = true; }) %{ s.representation = specifier::SCIENTIFIC; }
|
|
| ('g' | 'G' %{ s.upper = true; }) %{ s.representation = specifier::DEFAULT; }
|
|
| ('a' | 'A' %{ s.upper = true; }) %{ s.representation = specifier::HEX; s.base = 16; }
|
|
) >{ s.type = type_t::REAL; }
|
|
| 's' >{ s.type = type_t::STRING; }
|
|
| 'c' >{ s.type = type_t::CHAR; }
|
|
| 'p' >{ s.type = type_t::POINTER; }
|
|
| 'n' >{ s.type = type_t::COUNT; }
|
|
);
|
|
|
|
literal = ([^%]+)
|
|
>{
|
|
s = specifier {};
|
|
s.fmt = {fpc,fpc};
|
|
s.type = type_t::LITERAL;
|
|
}
|
|
%{
|
|
s.fmt = {s.fmt.begin(),fpc};
|
|
if (!s.fmt.empty ()) {
|
|
specs.push_back (s);
|
|
}
|
|
};
|
|
|
|
specifier = (
|
|
'%'
|
|
parameter?
|
|
flag**
|
|
width?
|
|
precision?
|
|
length?
|
|
type
|
|
)
|
|
>{
|
|
s = specifier {};
|
|
s.fmt = {fpc,fpc};
|
|
}
|
|
%{
|
|
s.fmt = {s.fmt.begin(),fpc};
|
|
specs.push_back (s);
|
|
};
|
|
|
|
format := literal? (specifier literal?)**
|
|
>{ success = false; }
|
|
%{ success = true; }
|
|
;
|
|
|
|
write data;
|
|
}%%
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
util::format::parsed
|
|
util::format::printf (util::view<const char*> fmt)
|
|
{
|
|
std::vector<specifier> specs;
|
|
specifier s;
|
|
bool success = false;
|
|
(void)s;
|
|
|
|
int cs;
|
|
|
|
auto p = std::cbegin (fmt);
|
|
auto pe = std::cend (fmt);
|
|
auto eof = pe;
|
|
|
|
%%write init;
|
|
%%write exec;
|
|
|
|
if (!success)
|
|
throw std::runtime_error ("invalid format specification");
|
|
|
|
return parsed { std::move (specs) };
|
|
}
|
|
|
|
|
|
/// parses a format specifier in the style of PEP3101 (with the notable
|
|
/// exception of named parameters).
|
|
///
|
|
/// in the event of a parsing error the function will throw. makes no
|
|
/// attempt to cater for constexpr validation.
|
|
util::format::parsed
|
|
util::format::python (util::view<const char*> fmt)
|
|
{
|
|
std::vector<specifier> specs;
|
|
|
|
const auto *prev = std::begin (fmt);
|
|
const auto *cursor = std::begin (fmt);
|
|
while (*cursor) {
|
|
switch (*cursor) {
|
|
case '{':
|
|
{
|
|
{
|
|
specifier s;
|
|
s.fmt = { prev, cursor };
|
|
s.type = type_t::LITERAL;
|
|
specs.push_back (s);
|
|
}
|
|
|
|
auto first = cursor;
|
|
while (*++cursor != '}')
|
|
;
|
|
|
|
++cursor;
|
|
{
|
|
specifier s;
|
|
s.fmt = {first, cursor};
|
|
s.type = type_t::USER;
|
|
specs.push_back (s);
|
|
}
|
|
|
|
prev = cursor;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
++cursor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
specifier s;
|
|
s.fmt = {prev,cursor};
|
|
s.type = type_t::LITERAL;
|
|
specs.push_back (s);
|
|
}
|
|
|
|
return parsed {std::move (specs)};
|
|
}
|