1
0
Fork 0
mirror of https://github.com/tailix/libkernaux.git synced 2025-07-07 18:51:58 -04:00

Improve printf bindings for Ruby & mruby (#145)

This commit is contained in:
Alex Kotov 2022-12-15 03:23:58 +04:00 committed by GitHub
parent be761bd9f7
commit 18f3c70c1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 404 additions and 813 deletions

View file

@ -27,7 +27,11 @@ Metrics/BlockLength:
Metrics/BlockNesting: Metrics/BlockNesting:
Exclude: Exclude:
- 'test/printf.rb' - 'test/sprintf.rb'
Performance/CollectionLiteralInLoop:
Exclude:
- 'test/**/*.rb'
Security/Eval: Security/Eval:
Exclude: Exclude:

View file

@ -2,4 +2,5 @@ MRuby::Build.new do |conf|
conf.toolchain conf.toolchain
conf.enable_test conf.enable_test
conf.gem '.' conf.gem '.'
conf.gem core: 'mruby-bin-mirb'
end end

View file

@ -7,24 +7,6 @@ module KernAux
raise AssertError, "#{file}:#{line}:#{msg}" raise AssertError, "#{file}:#{line}:#{msg}"
} }
SPRINTF1_BUFFER_SIZE = 10_000
if Version.with_printf?
def self.sprintf(*args)
args.map do |arg|
if arg.is_a? Array
sprintf1(*arg)
else
arg
end
end.join.freeze
end
def self.sprintf1(format, *args)
snprintf1(SPRINTF1_BUFFER_SIZE, format, *args).first
end
end
class Error < RuntimeError; end class Error < RuntimeError; end
class AssertError < Error; end class AssertError < Error; end
class CmdlineError < Error; end class CmdlineError < Error; end

View file

@ -1,155 +1,147 @@
#include "main.h" #include "main.h"
#include "dynarg.h" #include "dynarg.h"
#include <stddef.h>
#include <stdlib.h>
#include <mruby/array.h> #include <mruby/array.h>
#include <mruby/error.h> #include <mruby/error.h>
#include <mruby/presym.h> #include <mruby/presym.h>
#include <mruby/string.h> #include <mruby/string.h>
#define BUFFER_SIZE 4096
#ifdef KERNAUX_VERSION_WITH_PRINTF #ifdef KERNAUX_VERSION_WITH_PRINTF
struct snprintf1_userdata { static mrb_value rb_KernAux_sprintf(mrb_state *mrb, mrb_value self);
const struct KernAux_PrintfFmt_Spec *spec;
const struct DynArg *dynarg;
mrb_int size;
const char *format;
char *str;
};
static mrb_value rb_KernAux_snprintf1(mrb_state *mrb, mrb_value self);
static mrb_value snprintf1_protect(mrb_state *mrb, void *userdata);
void init_printf(mrb_state *const mrb) void init_printf(mrb_state *const mrb)
{ {
struct RClass *const rb_KernAux = mrb_module_get_id(mrb, MRB_SYM(KernAux)); struct RClass *const rb_KernAux = mrb_module_get_id(mrb, MRB_SYM(KernAux));
mrb_define_class_method(mrb, rb_KernAux, "snprintf1", rb_KernAux_snprintf1, mrb_define_class_method(mrb, rb_KernAux, "sprintf", rb_KernAux_sprintf,
MRB_ARGS_REQ(2) | MRB_ARGS_OPT(2)); MRB_ARGS_REQ(1) | MRB_ARGS_REST());
} }
mrb_value rb_KernAux_snprintf1(mrb_state *const mrb, mrb_value self) #define TAKE_ARG \
if (arg_index >= argc) { \
mrb_raise(mrb, E_ARGUMENT_ERROR, "too few arguments"); \
} \
mrb_value arg_rb = args[arg_index++]; \
do {} while (0)
mrb_value rb_KernAux_sprintf(mrb_state *const mrb, mrb_value self)
{ {
mrb_int size = 0; // FIXME: const
const char *format = NULL; char *format;
mrb_value rest[3]; mrb_value *args;
mrb_bool rest_given[3]; mrb_int argc;
mrb_get_args(mrb, "z*", &format, &args, &argc);
mrb_get_args(
mrb,
"iz|o?o?o?",
&size,
&format,
&rest[0], &rest_given[0],
&rest[1], &rest_given[1],
&rest[2], &rest_given[2]
);
if (size < 0) mrb_raise(mrb, E_RANGE_ERROR, "expected non-negative size");
const char *fmt = format;
while (*fmt && *fmt != '%') ++fmt;
if (*(fmt++) != '%') mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid format");
struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create_out(&fmt);
while (*fmt) {
if (*(fmt++) == '%') mrb_raise(mrb, E_ARGUMENT_ERROR, "invalid format");
}
int argc = 0;
for (int i = 0; i < 3; ++i) if (rest_given[i]) ++argc;
int arg_index = 0; int arg_index = 0;
if (spec.set_width && argc > arg_index) { mrb_value result = mrb_str_new_lit(mrb, "");
KernAux_PrintfFmt_Spec_set_width(&spec, mrb_integer(rest[arg_index++]));
}
if (spec.set_precision && argc > arg_index) {
KernAux_PrintfFmt_Spec_set_precision(&spec, mrb_integer(rest[arg_index++]));
}
struct DynArg dynarg = DynArg_create(); while (*format) {
if (argc > arg_index) { if (*format != '%') {
mrb_value arg_rb = rest[arg_index]; mrb_str_cat(mrb, result, format, 1);
++format;
continue;
}
// FIXME: unnecessary
const char *const old_format = format;
++format;
struct KernAux_PrintfFmt_Spec spec =
// FIXME: no type cast
KernAux_PrintfFmt_Spec_create_out((const char**)&format);
if (spec.set_width) {
TAKE_ARG;
KernAux_PrintfFmt_Spec_set_width(&spec, mrb_integer(arg_rb));
}
if (spec.set_precision) {
TAKE_ARG;
KernAux_PrintfFmt_Spec_set_precision(&spec, mrb_integer(arg_rb));
}
struct DynArg dynarg = DynArg_create();
if (spec.type == KERNAUX_PRINTF_FMT_TYPE_INT) { if (spec.type == KERNAUX_PRINTF_FMT_TYPE_INT) {
TAKE_ARG;
mrb_ensure_int_type(mrb, arg_rb);
DynArg_use_long_long(&dynarg, mrb_integer(arg_rb)); DynArg_use_long_long(&dynarg, mrb_integer(arg_rb));
} else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_UINT) { } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_UINT) {
mrb_int arg = mrb_integer(arg_rb); TAKE_ARG;
if (arg < 0) mrb_raise(mrb, E_ARGUMENT_ERROR, "expected non-negative argument"); mrb_ensure_int_type(mrb, arg_rb);
DynArg_use_unsigned_long_long(&dynarg, arg); DynArg_use_unsigned_long_long(&dynarg, mrb_integer(arg_rb));
} else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_FLOAT || } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_FLOAT ||
spec.type == KERNAUX_PRINTF_FMT_TYPE_EXP) spec.type == KERNAUX_PRINTF_FMT_TYPE_EXP)
{ {
DynArg_use_double(&dynarg, mrb_as_float(mrb, arg_rb)); TAKE_ARG;
mrb_ensure_float_type(mrb, arg_rb);
DynArg_use_double(&dynarg, mrb_float(arg_rb));
} else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_CHAR) { } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_CHAR) {
DynArg_use_char(&dynarg, *RSTRING_CSTR(mrb, arg_rb)); TAKE_ARG;
mrb_ensure_string_type(mrb, arg_rb);
DynArg_use_char(&dynarg, *RSTRING_PTR(arg_rb));
} else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_STR) { } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_STR) {
TAKE_ARG;
mrb_ensure_string_type(mrb, arg_rb);
DynArg_use_str(&dynarg, RSTRING_CSTR(mrb, arg_rb)); DynArg_use_str(&dynarg, RSTRING_CSTR(mrb, arg_rb));
} }
}
char *const str = malloc(size); char buffer[BUFFER_SIZE];
if (!str) mrb_raise(mrb, mrb_exc_get_id(mrb, MRB_ERROR_SYM(NoMemoryError)), "snprintf1 buffer malloc"); int slen;
struct snprintf1_userdata userdata = { // FIXME: it's a hack
.spec = &spec, // TODO: convert printf format spec to string
.dynarg = &dynarg, const char tmp = *format;
.size = size, *format = '\0';
.format = format,
.str = str,
};
mrb_bool error;
mrb_value result = mrb_protect_error(mrb, snprintf1_protect, &userdata, &error);
free(str); if (spec.set_width) {
if (spec.set_precision) {
if (error) { if (dynarg.use_dbl) {
mrb_exc_raise(mrb, result); slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
} else { spec.width, spec.precision,
return result; dynarg.dbl);
} } else {
} slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
spec.width, spec.precision,
mrb_value snprintf1_protect(mrb_state *const mrb, void *const userdata_raw) dynarg.arg);
{ }
const struct snprintf1_userdata *const userdata = userdata_raw; } else {
if (dynarg.use_dbl) {
int slen; slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
if (userdata->spec->set_width) { spec.width, dynarg.dbl);
if (userdata->spec->set_precision) { } else {
slen = userdata->dynarg->use_dbl slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->spec->precision, userdata->dynarg->dbl) spec.width, dynarg.arg);
: kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->spec->precision, userdata->dynarg->arg); }
}
} else { } else {
slen = userdata->dynarg->use_dbl if (spec.set_precision) {
? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->dynarg->dbl) if (dynarg.use_dbl) {
: kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->dynarg->arg); slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
} spec.precision, dynarg.dbl);
} else { } else {
if (userdata->spec->set_precision) { slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
slen = userdata->dynarg->use_dbl spec.precision, dynarg.arg);
? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->precision, userdata->dynarg->dbl) }
: kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->precision, userdata->dynarg->arg); } else {
} else { if (dynarg.use_dbl) {
slen = userdata->dynarg->use_dbl slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, dynarg.dbl);
? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->dynarg->dbl) } else {
: kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->dynarg->arg); slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, dynarg.arg);
}
}
} }
*format = tmp;
mrb_str_cat(mrb, result, buffer, slen);
} }
mrb_value output_rb = if (arg_index < argc) {
mrb_obj_freeze(mrb, mrb_str_cat_cstr(mrb, mrb_str_new_lit(mrb, ""), userdata->str)); mrb_raise(mrb, E_ARGUMENT_ERROR, "too many arguments");
}
mrb_value values[2]; return mrb_obj_freeze(mrb, result);
values[0] = output_rb;
values[1] = mrb_fixnum_value(slen);
return mrb_obj_freeze(mrb, mrb_ary_new_from_values(mrb, 2, values));
} }
#endif // KERNAUX_VERSION_WITH_PRINTF #endif // KERNAUX_VERSION_WITH_PRINTF

View file

@ -1,82 +0,0 @@
if KernAux::Version.with_printf?
assert 'KernAux.sprintf' do
assert 'integers' do
assert_equal 'i:0', KernAux.sprintf('i:', ['%i', 0])
assert_equal 'u:0', KernAux.sprintf('u:', ['%u', 0])
assert_equal 'i:1', KernAux.sprintf('i:', ['%i', 1])
assert_equal 'u:1', KernAux.sprintf('u:', ['%u', 1])
assert_equal 'i:10', KernAux.sprintf('i:', ['%i', 10])
assert_equal 'u:10', KernAux.sprintf('u:', ['%u', 10])
assert_equal 'i:100', KernAux.sprintf('i:', ['%i', 100])
assert_equal 'u:100', KernAux.sprintf('u:', ['%u', 100])
assert_equal 'i:1000', KernAux.sprintf('i:', ['%i', 1000])
assert_equal 'u:1000', KernAux.sprintf('u:', ['%u', 1000])
assert_equal 'i:10000', KernAux.sprintf('i:', ['%i', 10_000])
assert_equal 'u:10000', KernAux.sprintf('u:', ['%u', 10_000])
assert_equal 'i:100000', KernAux.sprintf('i:', ['%i', 100_000])
assert_equal 'u:100000', KernAux.sprintf('u:', ['%u', 100_000])
assert_equal 'i:1000000', KernAux.sprintf('i:', ['%i', 1_000_000])
assert_equal 'u:1000000', KernAux.sprintf('u:', ['%u', 1_000_000])
assert_equal 'i:10000000', KernAux.sprintf('i:', ['%i', 10_000_000])
assert_equal 'u:10000000', KernAux.sprintf('u:', ['%u', 10_000_000])
assert_equal 'i:100000000', KernAux.sprintf('i:', ['%i', 10**8])
assert_equal 'u:100000000', KernAux.sprintf('u:', ['%u', 10**8])
assert_equal 'i:1000000000', KernAux.sprintf('i:', ['%i', 10**9])
assert_equal 'u:1000000000', KernAux.sprintf('u:', ['%u', 10**9])
assert_equal 'i:2147483647', KernAux.sprintf('i:', ['%i', 2**31 - 1])
assert_equal 'u:2147483647', KernAux.sprintf('u:', ['%u', 2**31 - 1])
end
# TODO: test with different boxing
# assert 'integer overflows' do
# assert_equal 'i:-2147483648', KernAux.sprintf('i:', ['%i', 2**31])
# assert_equal 'u: 2147483648', KernAux.sprintf('u: ', ['%u', 2**31])
# assert_equal 'i:-2147483647', KernAux.sprintf('i:', ['%i', 2**31 + 1])
# assert_equal 'u: 2147483649', KernAux.sprintf('u: ', ['%u', 2**31 + 1])
# assert_equal 'i:-1', KernAux.sprintf('i:', ['%i', 2**32 - 1])
# assert_equal 'u: 4294967295', KernAux.sprintf('u: ', ['%u', 2**32 - 1])
# assert_equal 'i: 0', KernAux.sprintf('i: ', ['%i', 2**32])
# assert_equal 'u: 0', KernAux.sprintf('u: ', ['%u', 2**32])
# end
[
['', 'using regular tests'],
['_orig', 'using original tests'],
].each do |(suffix, description)|
assert description do
printf_yml =
File.expand_path("../../../../common/printf#{suffix}.yml", __FILE__)
YAML.load(File.read(printf_yml)).each do |test|
expected = test['result']
args = test['args'].map do |arg|
if arg.is_a? String
arg
else
arg.map do |item|
if item.is_a? Array
if item.length == 1
item[0]
elsif item[0] == 'long long'
item[1]
else
raise "Unknown format: #{args.inspect}"
end
elsif item.is_a?(Float) && item.round == item
item.round
else
item
end
end
end
end
assert "transforms #{args.inspect} to #{expected.inspect}" do
assert_equal expected, KernAux.sprintf(*args)
end
end
end
end
end
end

View file

@ -0,0 +1,113 @@
if KernAux::Version.with_printf?
assert 'KernAux.sprintf' do
assert 'integers' do
assert_equal 'i:0', KernAux.sprintf('i:%i', 0)
assert_equal 'u:0', KernAux.sprintf('u:%u', 0)
assert_equal 'i:1', KernAux.sprintf('i:%i', 1)
assert_equal 'u:1', KernAux.sprintf('u:%u', 1)
assert_equal 'i:10', KernAux.sprintf('i:%i', 10)
assert_equal 'u:10', KernAux.sprintf('u:%u', 10)
assert_equal 'i:100', KernAux.sprintf('i:%i', 100)
assert_equal 'u:100', KernAux.sprintf('u:%u', 100)
assert_equal 'i:1000', KernAux.sprintf('i:%i', 1000)
assert_equal 'u:1000', KernAux.sprintf('u:%u', 1000)
assert_equal 'i:10000', KernAux.sprintf('i:%i', 10_000)
assert_equal 'u:10000', KernAux.sprintf('u:%u', 10_000)
assert_equal 'i:100000', KernAux.sprintf('i:%i', 100_000)
assert_equal 'u:100000', KernAux.sprintf('u:%u', 100_000)
assert_equal 'i:1000000', KernAux.sprintf('i:%i', 1_000_000)
assert_equal 'u:1000000', KernAux.sprintf('u:%u', 1_000_000)
assert_equal 'i:10000000', KernAux.sprintf('i:%i', 10_000_000)
assert_equal 'u:10000000', KernAux.sprintf('u:%u', 10_000_000)
assert_equal 'i:100000000', KernAux.sprintf('i:%i', 10**8)
assert_equal 'u:100000000', KernAux.sprintf('u:%u', 10**8)
assert_equal 'i:1000000000', KernAux.sprintf('i:%i', 10**9)
assert_equal 'u:1000000000', KernAux.sprintf('u:%u', 10**9)
assert_equal 'i:2147483647', KernAux.sprintf('i:%i', 2**31 - 1)
assert_equal 'u:2147483647', KernAux.sprintf('u:%u', 2**31 - 1)
end
# TODO: test with different boxing
# assert 'integer overflows' do
# assert_equal 'i:-2147483648', KernAux.sprintf('i:%i', 2**31)
# assert_equal 'u: 2147483648', KernAux.sprintf('u: %u', 2**31)
# assert_equal 'i:-2147483647', KernAux.sprintf('i:%i', 2**31 + 1)
# assert_equal 'u: 2147483649', KernAux.sprintf('u: %u', 2**31 + 1)
# assert_equal 'i:-1', KernAux.sprintf('i:%i', 2**32 - 1)
# assert_equal 'u: 4294967295', KernAux.sprintf('u: %u', 2**32 - 1)
# assert_equal 'i: 0', KernAux.sprintf('i: %i', 2**32)
# assert_equal 'u: 0', KernAux.sprintf('u: %u', 2**32)
# end
assert 'when there are too many arguments' do
[
['Hello!', 'World!'],
['Hello, %s!', 'World', 'Alex'],
].each do |args|
assert_raise ArgumentError, 'too many arguments' do
KernAux.sprintf(*args)
end
end
end
assert 'when there are too few arguments' do
[
[],
['Hello, %s!'],
['Hello, %*s!', 20],
['Hello, %.*s!', 20],
['Hello, %*.*s!', 20, 20],
].each do |args|
assert_raise ArgumentError, 'too few arguments' do
KernAux.sprintf(*args)
end
end
end
[
['', 'using regular tests'],
['_orig', 'using original tests'],
].each do |(suffix, description)|
assert description do
printf_yml =
File.expand_path("../../../../common/printf#{suffix}.yml", __FILE__)
YAML.load(File.read(printf_yml)).each do |test|
expected = test['result']
format = ''
args = []
test['args'].each do |arg|
if arg.is_a? String
format += arg
else
format += arg[0]
is_int_format = %w[i d u x X o b].any? { |s| arg[0].include? s }
arg[1..].each do |item|
if item.is_a? Array
if item.length == 1
args << item[0]
elsif item[0] == 'long long'
args << item[1]
else
raise "Unknown format: #{args.inspect}"
end
elsif is_int_format && item.is_a?(Float)
args << item.round
else
args << item
end
end
end
end
assert "transforms (#{format.inspect}, #{args.inspect[1...-1]}) " \
"to #{expected.inspect}" do
assert_equal expected, KernAux.sprintf(format, *args)
end
end
end
end
end
end

View file

@ -1,208 +1,140 @@
#include "main.h" #include "main.h"
#include "dynarg.h" #include "dynarg.h"
#include <stddef.h> #define BUFFER_SIZE 4096
#include <stdlib.h>
#ifdef KERNAUX_VERSION_WITH_PRINTF #ifdef KERNAUX_VERSION_WITH_PRINTF
/************* /**
* ::KernAux * * Typical `printf`.
*************/ *
* @param format [String] format string
static VALUE rb_KernAux_snprintf1(int argc, const VALUE *argv, VALUE self); * @return [String] formatted output
*
static VALUE rb_KernAux_snprintf1_PROTECT(VALUE userdata); * @example
* KernAux.sprintf 'foo%*scar%d', 5, 'bar', 123
/************************ * #=> "foo barcar123"
* ::KernAux::Snprintf1 * */
************************/ static VALUE rb_KernAux_sprintf(int argc, VALUE *argv, VALUE self);
static VALUE rb_KernAux_Snprintf1 = Qnil;
static size_t rb_KernAux_Snprintf1_DSIZE(const void *ptr);
const rb_data_type_t rb_KernAux_Snprintf1_DTYPE = {
.wrap_struct_name = "KernAux::Snprintf1",
.parent = NULL,
.data = NULL,
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
.function = {
.dfree = RUBY_DEFAULT_FREE,
.dsize = rb_KernAux_Snprintf1_DSIZE,
.dmark = NULL,
.dcompact = NULL,
.reserved = { 0 },
},
};
struct rb_KernAux_Snprintf1_DATA {
const struct KernAux_PrintfFmt_Spec *spec;
const struct DynArg *dynarg;
int size;
const char *format;
char *str;
};
/********
* Main *
********/
void init_printf() void init_printf()
{ {
rb_gc_register_mark_object( rb_define_singleton_method(rb_KernAux, "sprintf", rb_KernAux_sprintf, -1);
rb_KernAux_Snprintf1 =
// @api private
rb_define_class_under(rb_KernAux, "Snprintf1", rb_cObject));
rb_funcall(rb_KernAux, rb_intern("private_constant"), 1, ID2SYM(rb_intern("Snprintf1")));
rb_define_singleton_method(rb_KernAux, "snprintf1",
rb_KernAux_snprintf1, -1);
} }
/************* #define TAKE_ARG \
* ::KernAux * if (arg_index >= argc) rb_raise(rb_eArgError, "too few arguments"); \
*************/ VALUE arg_rb = argv[arg_index++]; \
do {} while (0)
VALUE rb_KernAux_snprintf1( VALUE rb_KernAux_sprintf(const int argc, VALUE *const argv, VALUE self)
const int argc, {
const VALUE *const argv_rb, if (argc == 0) rb_raise(rb_eArgError, "too few arguments");
const VALUE self KERNAUX_UNUSED
) {
if (argc < 2 || argc > 5) rb_raise(rb_eArgError, "expected 2, 3, 4 or 5 args");
const VALUE size_rb = argv_rb[0]; // FIXME: const
VALUE format_rb = argv_rb[1]; char *format = StringValueCStr(argv[0]);
int arg_index = 1;
VALUE result = rb_str_new_literal("");
const int size = NUM2INT(size_rb); while (*format) {
const char *const format = StringValueCStr(format_rb); if (*format != '%') {
rb_str_cat(result, format, 1);
++format;
continue;
}
if (size < 0) rb_raise(rb_eRangeError, "expected non-negative size"); // FIXME: unnecessary
const char *const old_format = format;
++format;
struct KernAux_PrintfFmt_Spec spec =
// FIXME: no type cast
KernAux_PrintfFmt_Spec_create_out((const char**)&format);
const char *fmt = format; if (spec.set_width) {
TAKE_ARG;
KernAux_PrintfFmt_Spec_set_width(&spec, NUM2INT(arg_rb));
}
if (spec.set_precision) {
TAKE_ARG;
KernAux_PrintfFmt_Spec_set_precision(&spec, NUM2INT(arg_rb));
}
while (*fmt && *fmt != '%') ++fmt; struct DynArg dynarg = DynArg_create();
if (*(fmt++) != '%') rb_raise(rb_eArgError, "invalid format");
struct KernAux_PrintfFmt_Spec spec = KernAux_PrintfFmt_Spec_create_out(&fmt);
while (*fmt) {
if (*(fmt++) == '%') rb_raise(rb_eArgError, "invalid format");
}
int arg_index = 2;
if (spec.set_width && argc > arg_index) {
KernAux_PrintfFmt_Spec_set_width(&spec, NUM2INT(argv_rb[arg_index++]));
}
if (spec.set_precision && argc > arg_index) {
KernAux_PrintfFmt_Spec_set_precision(&spec, NUM2INT(argv_rb[arg_index++]));
}
struct DynArg dynarg = DynArg_create();
if (argc > arg_index) {
VALUE arg_rb = argv_rb[arg_index];
if (spec.type == KERNAUX_PRINTF_FMT_TYPE_INT) { if (spec.type == KERNAUX_PRINTF_FMT_TYPE_INT) {
TAKE_ARG;
DynArg_use_long_long(&dynarg, NUM2LL(arg_rb)); DynArg_use_long_long(&dynarg, NUM2LL(arg_rb));
} else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_UINT) { } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_UINT) {
TAKE_ARG;
DynArg_use_unsigned_long_long(&dynarg, NUM2ULL(arg_rb)); DynArg_use_unsigned_long_long(&dynarg, NUM2ULL(arg_rb));
} else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_FLOAT || } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_FLOAT ||
spec.type == KERNAUX_PRINTF_FMT_TYPE_EXP) spec.type == KERNAUX_PRINTF_FMT_TYPE_EXP)
{ {
TAKE_ARG;
DynArg_use_double(&dynarg, NUM2DBL(arg_rb)); DynArg_use_double(&dynarg, NUM2DBL(arg_rb));
} else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_CHAR) { } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_CHAR) {
TAKE_ARG;
Check_Type(arg_rb, T_STRING); Check_Type(arg_rb, T_STRING);
DynArg_use_char(&dynarg, *StringValuePtr(arg_rb)); DynArg_use_char(&dynarg, *StringValuePtr(arg_rb));
} else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_STR) { } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_STR) {
TAKE_ARG;
Check_Type(arg_rb, T_STRING); Check_Type(arg_rb, T_STRING);
DynArg_use_str(&dynarg, StringValueCStr(arg_rb)); DynArg_use_str(&dynarg, StringValueCStr(arg_rb));
} }
}
char *const str = malloc(size); char buffer[BUFFER_SIZE];
if (!str) rb_raise(rb_eNoMemError, "snprintf1 buffer malloc"); int slen;
struct rb_KernAux_Snprintf1_DATA *userdata; // FIXME: it's a hack
VALUE userdata_rb = TypedData_Make_Struct( // TODO: convert printf format spec to string
rb_KernAux_Snprintf1, const char tmp = *format;
struct rb_KernAux_Snprintf1_DATA, *format = '\0';
&rb_KernAux_Snprintf1_DTYPE,
userdata
);
if (NIL_P(userdata_rb) || userdata == NULL) {
free(str);
rb_raise(rb_eNoMemError, "snprintf1 userdata alloc");
}
userdata->spec = &spec; if (spec.set_width) {
userdata->dynarg = &dynarg; if (spec.set_precision) {
userdata->size = size; if (dynarg.use_dbl) {
userdata->format = format; slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
userdata->str = str; spec.width, spec.precision,
dynarg.dbl);
int state = 0; } else {
VALUE result = slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
rb_protect(rb_KernAux_snprintf1_PROTECT, userdata_rb, &state); spec.width, spec.precision,
dynarg.arg);
free(str); }
} else {
if (state == 0) { if (dynarg.use_dbl) {
return result; slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
} else { spec.width, dynarg.dbl);
rb_jump_tag(state); } else {
} slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
} spec.width, dynarg.arg);
}
VALUE rb_KernAux_snprintf1_PROTECT(VALUE userdata_rb) }
{
const struct rb_KernAux_Snprintf1_DATA *userdata = NULL;
TypedData_Get_Struct(
userdata_rb,
struct rb_KernAux_Snprintf1_DATA,
&rb_KernAux_Snprintf1_DTYPE,
userdata
);
int slen;
if (userdata->spec->set_width) {
if (userdata->spec->set_precision) {
slen = userdata->dynarg->use_dbl
? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->spec->precision, userdata->dynarg->dbl)
: kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->spec->precision, userdata->dynarg->arg);
} else { } else {
slen = userdata->dynarg->use_dbl if (spec.set_precision) {
? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->dynarg->dbl) if (dynarg.use_dbl) {
: kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->dynarg->arg); slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
} spec.precision, dynarg.dbl);
} else { } else {
if (userdata->spec->set_precision) { slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format,
slen = userdata->dynarg->use_dbl spec.precision, dynarg.arg);
? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->precision, userdata->dynarg->dbl) }
: kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->precision, userdata->dynarg->arg); } else {
} else { if (dynarg.use_dbl) {
slen = userdata->dynarg->use_dbl slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, dynarg.dbl);
? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->dynarg->dbl) } else {
: kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->dynarg->arg); slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, dynarg.arg);
}
}
} }
*format = tmp;
rb_str_cat(result, buffer, slen);
} }
const VALUE output_rb = if (arg_index < argc) rb_raise(rb_eArgError, "too many arguments");
rb_funcall(rb_str_new2(userdata->str), rb_intern_freeze, 0);
const VALUE result_rb = rb_ary_new2(2); return rb_funcall(result, rb_intern_freeze, 0);
rb_ary_push(result_rb, output_rb);
rb_ary_push(result_rb, INT2NUM(slen));
return rb_funcall(result_rb, rb_intern_freeze, 0);
}
/************************
* ::KernAux::Snprintf1 *
************************/
size_t rb_KernAux_Snprintf1_DSIZE(const void *const ptr)
{
return sizeof(struct rb_KernAux_Snprintf1_DATA);
} }
#endif // KERNAUX_VERSION_WITH_PRINTF #endif // KERNAUX_VERSION_WITH_PRINTF

View file

@ -17,4 +17,3 @@ require_relative 'kernaux/assert'
require_relative 'kernaux/cmdline' require_relative 'kernaux/cmdline'
require_relative 'kernaux/errors' require_relative 'kernaux/errors'
require_relative 'kernaux/ntoa' require_relative 'kernaux/ntoa'
require_relative 'kernaux/printf'

View file

@ -1,70 +0,0 @@
# frozen_string_literal: true
# rubocop:disable Style/Documentation
begin; end
module KernAux
# Buffer size for {.sprintf1}.
# @todo Make it dynamic.
SPRINTF1_BUFFER_SIZE = 10_000
# @!scope class
# @!parse [ruby]
if Version.with_printf?
##
# Typical `printf`.
#
# @param args [Array<String,
# Array<(String, Object)>,
# Array<(String, Integer, Object)>>]
# @return [String] formatted output
#
# @example
# KernAux.sprintf 'foo', ['%*s', 5, 'bar'], 'car', ['%d', 123]
# #=> "foo barcar123"
#
def self.sprintf(*args)
args.map do |arg|
if arg.is_a? Array
sprintf1(*arg)
else
arg
end
end.join.freeze
end
##
# `printf` for single formatting parameter.
#
# @param format [String] formatting string
# @return [String] formatted output
#
# @see .sprintf Multiple formatting parameters
#
# @example
# KernAux.sprintf1 '%%' #=> "%"
# KernAux.sprintf1 '%s', 'foo' #=> "foo"
# KernAux.sprintf1 '%5s', 'foo' #=> " foo"
# KernAux.sprintf1 '%*s', 5, 'foo' #=> " foo"
#
def self.sprintf1(format, *args)
snprintf1(SPRINTF1_BUFFER_SIZE, format, *args).first
end
##
# @!method snprintf1(buffer_size, format, ...)
# `printf` for single formatting parameter with manual buffer size.
#
# @param buffer_size [Integer] buffer size (including terminating null
# character)
# @param format [String] formatting string
# @return [Array<(String, Integer)>] formatted output and it's size
#
# @see .sprintf1 Automatic buffer size
##
end
end
# rubocop:enable Style/Documentation

View file

@ -1,280 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
KernAux::Version.with_printf? and RSpec.describe KernAux, '.snprintf1' do
let(:size) { 10_000 }
context 'with 0 arguments' do
specify do
expect { described_class.snprintf1 }.to \
raise_error ArgumentError, 'expected 2, 3, 4 or 5 args'
end
end
context 'with 1 argument' do
specify do
expect { described_class.snprintf1 size }.to \
raise_error ArgumentError, 'expected 2, 3, 4 or 5 args'
end
end
context 'with 6 arguments' do
specify do
expect do
described_class.snprintf1 size, '%*.*s', 20, 10, 'foo', 'bar'
end.to \
raise_error ArgumentError, 'expected 2, 3, 4 or 5 args'
end
end
context 'with 2 arguments' do
subject(:snprintf1) { described_class.snprintf1 size, format }
let(:format) { '%%' }
it { is_expected.to be_instance_of Array }
it { is_expected.to be_frozen }
it { is_expected.to all be_frozen }
specify { expect(snprintf1.size).to equal 2 }
specify { expect(snprintf1[0]).to be_instance_of String }
specify { expect(snprintf1[1]).to be_instance_of Integer }
specify { expect(snprintf1[0]).to eq '%' }
specify { expect(snprintf1[1]).to eq 1 }
context 'with leading and trailing spaces' do
let(:format) { ' %% ' }
specify { expect(snprintf1[0]).to eq ' % ' }
end
context 'with "%s" format' do
let(:format) { '%s' }
specify { expect(snprintf1[0]).to eq '' }
end
context 'when size has invalid type' do
let(:size) { '10000' }
specify { expect { snprintf1 }.to raise_error TypeError }
end
context 'when size is negative' do
let(:size) { -1 }
specify do
expect { snprintf1 }.to \
raise_error RangeError, 'expected non-negative size'
end
end
context 'when format doesn\'t include "%" char' do
let(:format) { 'foo' }
specify do
expect { snprintf1 }.to raise_error ArgumentError, 'invalid format'
end
end
context 'when format includes more than two "%" chars' do
let(:format) { '%%%' }
specify do
expect { snprintf1 }.to raise_error ArgumentError, 'invalid format'
end
end
end
context 'with 3 arguments' do
subject(:snprintf1) { described_class.snprintf1 size, format, arg }
let(:format) { '%s' }
let(:arg) { 'Hello, World!' }
it { is_expected.to be_instance_of Array }
it { is_expected.to be_frozen }
it { is_expected.to all be_frozen }
specify { expect(snprintf1.size).to equal 2 }
specify { expect(snprintf1[0]).to be_instance_of String }
specify { expect(snprintf1[1]).to be_instance_of Integer }
specify { expect(snprintf1[0]).to eq arg }
specify { expect(snprintf1[1]).to eq arg.size }
context 'with leading and trailing spaces' do
let(:format) { ' %s ' }
specify { expect(snprintf1[0]).to eq " #{arg} " }
end
context 'with "%%" format' do
let(:format) { '%%' }
specify { expect(snprintf1[0]).to eq '%' }
end
context 'when size has invalid type' do
let(:size) { '10000' }
specify { expect { snprintf1 }.to raise_error TypeError }
end
context 'when size is negative' do
let(:size) { -1 }
specify do
expect { snprintf1 }.to \
raise_error RangeError, 'expected non-negative size'
end
end
context 'when format doesn\'t include "%" char' do
let(:format) { 'foo' }
specify do
expect { snprintf1 }.to raise_error ArgumentError, 'invalid format'
end
end
context 'when format includes more than two "%" chars' do
let(:format) { '%%%' }
specify do
expect { snprintf1 }.to raise_error ArgumentError, 'invalid format'
end
end
end
context 'with 4 arguments' do
subject(:snprintf1) { described_class.snprintf1 size, format, width, arg }
let(:format) { '%*s' }
let(:width) { 20 }
let(:arg) { 'Hello, World!' }
it { is_expected.to be_instance_of Array }
it { is_expected.to be_frozen }
it { is_expected.to all be_frozen }
specify { expect(snprintf1.size).to equal 2 }
specify { expect(snprintf1[0]).to be_instance_of String }
specify { expect(snprintf1[1]).to be_instance_of Integer }
specify { expect(snprintf1[0]).to eq arg.rjust(width, ' ') }
specify { expect(snprintf1[1]).to eq width }
context 'with leading and trailing spaces' do
let(:format) { ' %*s ' }
specify { expect(snprintf1[0]).to eq " #{arg.rjust(width, ' ')} " }
end
context 'with "%*%" format' do
let(:format) { '%*%' }
specify { expect(snprintf1[0]).to eq '%' }
end
context 'when size has invalid type' do
let(:size) { '10000' }
specify { expect { snprintf1 }.to raise_error TypeError }
end
context 'when size is negative' do
let(:size) { -1 }
specify do
expect { snprintf1 }.to \
raise_error RangeError, 'expected non-negative size'
end
end
context 'when format doesn\'t include "%" char' do
let(:format) { 'foo' }
specify do
expect { snprintf1 }.to raise_error ArgumentError, 'invalid format'
end
end
context 'when format includes more than two "%" chars' do
let(:format) { '%%%' }
specify do
expect { snprintf1 }.to raise_error ArgumentError, 'invalid format'
end
end
end
context 'with 5 arguments' do
subject :snprintf1 do
described_class.snprintf1 size, format, width, precision, arg
end
let(:format) { '%*.*f' }
let(:width) { 20 }
let(:precision) { 3 }
let(:arg) { 123.456789 }
it { is_expected.to be_instance_of Array }
it { is_expected.to be_frozen }
it { is_expected.to all be_frozen }
specify { expect(snprintf1.size).to equal 2 }
specify { expect(snprintf1[0]).to be_instance_of String }
specify { expect(snprintf1[1]).to be_instance_of Integer }
specify { expect(snprintf1[1]).to eq width }
specify do
expect(snprintf1[0]).to eq arg.round(precision).to_s.rjust(width, ' ')
end
context 'with leading and trailing spaces' do
let(:format) { ' %*.*f ' }
specify do
expect(snprintf1[0]).to \
eq " #{arg.round(precision).to_s.rjust(width, ' ')} "
end
end
context 'with "%*.*%" format' do
let(:format) { '%*.*%' }
specify { expect(snprintf1[0]).to eq '%' }
end
context 'when size has invalid type' do
let(:size) { '10000' }
specify { expect { snprintf1 }.to raise_error TypeError }
end
context 'when size is negative' do
let(:size) { -1 }
specify do
expect { snprintf1 }.to \
raise_error RangeError, 'expected non-negative size'
end
end
context 'when format doesn\'t include "%" char' do
let(:format) { 'foo' }
specify do
expect { snprintf1 }.to raise_error ArgumentError, 'invalid format'
end
end
context 'when format includes more than two "%" chars' do
let(:format) { '%%%' }
specify do
expect { snprintf1 }.to raise_error ArgumentError, 'invalid format'
end
end
end
end

View file

@ -1,26 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
KernAux::Version.with_printf? and RSpec.describe KernAux, '.sprintf1' do
context 'with 1 argument' do
subject(:sprintf1) { described_class.sprintf1 format }
let(:format) { '%%' }
it { is_expected.to be_instance_of String }
it { is_expected.to be_frozen }
it { is_expected.to eq described_class.snprintf1(1000, format).first }
end
context 'with 2 argument' do
subject(:sprintf1) { described_class.sprintf1 format, arg }
let(:format) { '%s' }
let(:arg) { 'Hello, World!' }
it { is_expected.to be_instance_of String }
it { is_expected.to be_frozen }
it { is_expected.to eq described_class.snprintf1(100, format, arg).first }
end
end

View file

@ -1,59 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
KernAux::Version.with_printf? and RSpec.describe KernAux, '.sprintf' do
subject :sprintf do
described_class.sprintf 'Hello, ', ['%s', 'World'], '!'
end
it { is_expected.to be_instance_of String }
it { is_expected.to be_frozen }
it { is_expected.to eq 'Hello, World!' }
context 'for empty string value' do
subject(:sprintf) { described_class.sprintf ['Hello testing%s'] }
it { is_expected.to eq 'Hello testing' }
end
[
['', 'using regular tests'],
['_orig', 'using original tests'],
].each do |(suffix, description)|
context description do
printf_yml = File.expand_path(
"../../../../../../common/printf#{suffix}.yml",
__dir__,
)
YAML.safe_load_file(printf_yml).each do |test|
expected = test['result']
args = test['args'].map do |arg|
if arg.is_a? String
arg
else
arg.map do |item|
if item.is_a? Array
if item.length == 1
item[0]
elsif item[0] == 'long long'
item[1]
else
raise "Unknown format: #{args.inspect}"
end
else
item
end
end
end
end
it "transforms #{args.inspect} to #{expected.inspect}" do
expect(described_class.sprintf(*args)).to eq expected
end
end
end
end
end

View file

@ -0,0 +1,85 @@
# frozen_string_literal: true
require 'spec_helper'
KernAux::Version.with_printf? and RSpec.describe KernAux, '.sprintf' do
subject :sprintf do
described_class.sprintf 'Hello, %s!', 'World'
end
it { is_expected.to be_instance_of String }
it { is_expected.to be_frozen }
it { is_expected.to eq 'Hello, World!' }
context 'when there are too many arguments' do
[
['Hello!', 'World!'],
['Hello, %s!', 'World', 'Alex'],
].each do |args|
it "raises on #{args.inspect}" do
expect { described_class.sprintf(*args) }.to \
raise_error ArgumentError, 'too many arguments'
end
end
end
context 'when there are too few arguments' do
[
[],
['Hello, %s!'],
['Hello, %*s!', 20],
['Hello, %.*s!', 20],
['Hello, %*.*s!', 20, 20],
].each do |args|
it "raises on #{args.inspect}" do
expect { described_class.sprintf(*args) }.to \
raise_error ArgumentError, 'too few arguments'
end
end
end
[
['', 'using regular tests'],
['_orig', 'using original tests'],
].each do |(suffix, description)|
context description do
printf_yml = File.expand_path(
"../../../../../common/printf#{suffix}.yml",
__dir__,
)
YAML.safe_load_file(printf_yml).each do |test|
expected = test['result']
format = ''
args = []
test['args'].each do |arg|
if arg.is_a? String
format += arg
else
format += arg[0]
arg[1..].each do |item|
if item.is_a? Array
if item.length == 1
args << item[0]
elsif item[0] == 'long long'
args << item[1]
else
raise "Unknown format: #{args.inspect}"
end
else
args << item
end
end
end
end
it "transforms (#{format.inspect}, #{args.inspect[1...-1]}) " \
"to #{expected.inspect}" do
expect(described_class.sprintf(format, *args)).to eq expected
end
end
end
end
end