From 7698d0f3c650149c0c0910c765d0014aca8bbf41 Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Sat, 28 May 2022 14:45:43 +0300 Subject: [PATCH] mruby: implement KernAux.snprintf1 (with some bug) --- pkgs/mruby/src/dynarg.c | 45 +++++++++++++++ pkgs/mruby/src/dynarg.h | 35 ++++++++++++ pkgs/mruby/src/printf.c | 101 ++++++++++++++++++++++++++++++++- pkgs/mruby/test/printf.rb | 15 ++++- pkgs/ruby/ext/default/printf.c | 1 - 5 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 pkgs/mruby/src/dynarg.c create mode 100644 pkgs/mruby/src/dynarg.h diff --git a/pkgs/mruby/src/dynarg.c b/pkgs/mruby/src/dynarg.c new file mode 100644 index 0000000..d45b72c --- /dev/null +++ b/pkgs/mruby/src/dynarg.c @@ -0,0 +1,45 @@ +#include "dynarg.h" + +struct DynArg DynArg_create() +{ + struct DynArg dynarg; + DynArg_init(&dynarg); + return dynarg; +} + +void DynArg_init(struct DynArg *const dynarg) +{ + dynarg->use_dbl = false; + dynarg->dbl = 0.0; + dynarg->arg.str = ""; +} + +void DynArg_use_char(struct DynArg *const dynarg, const char chr) +{ + dynarg->use_dbl = false; + dynarg->arg.chr = chr; +} + +void DynArg_use_double(struct DynArg *const dynarg, const double dbl) +{ + dynarg->use_dbl = true; + dynarg->dbl = dbl; +} + +void DynArg_use_long_long(struct DynArg *const dynarg, const long long ll) +{ + dynarg->use_dbl = false; + dynarg->arg.ll = ll; +} + +void DynArg_use_str(struct DynArg *const dynarg, const char *const str) +{ + dynarg->use_dbl = false; + dynarg->arg.str = str; +} + +void DynArg_use_unsigned_long_long(struct DynArg *const dynarg, const unsigned long long ull) +{ + dynarg->use_dbl = false; + dynarg->arg.ull = ull; +} diff --git a/pkgs/mruby/src/dynarg.h b/pkgs/mruby/src/dynarg.h new file mode 100644 index 0000000..e6f70c0 --- /dev/null +++ b/pkgs/mruby/src/dynarg.h @@ -0,0 +1,35 @@ +#ifndef INCLUDED_DYNARG +#define INCLUDED_DYNARG + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct DynArg { + bool use_dbl; + double dbl; + // TODO: check if this will work on different endianness. + union { + char chr; + long long ll; + const char *str; + unsigned long long ull; + } __attribute__((packed)) arg; +}; + +struct DynArg DynArg_create(); +void DynArg_init(struct DynArg *dynarg); + +void DynArg_use_char(struct DynArg *dynarg, char chr); +void DynArg_use_double(struct DynArg *dynarg, double dbl); +void DynArg_use_long_long(struct DynArg *dynarg, long long ll); +void DynArg_use_str(struct DynArg *dynarg, const char *str); +void DynArg_use_unsigned_long_long(struct DynArg *dynarg, unsigned long long ull); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/pkgs/mruby/src/printf.c b/pkgs/mruby/src/printf.c index 0bb4d2c..24a15af 100644 --- a/pkgs/mruby/src/printf.c +++ b/pkgs/mruby/src/printf.c @@ -1,3 +1,4 @@ +#include "dynarg.h" #include "main.h" #include @@ -5,6 +6,9 @@ #include #include #include +#include + +#include static mrb_value rb_KernAux_snprintf1(mrb_state *mrb, mrb_value self); @@ -16,10 +20,103 @@ void init_printf(mrb_state *const mrb) MRB_ARGS_REQ(2) | MRB_ARGS_OPT(2)); } +// FIXME: rewrite to ensure no memory leak on exception. mrb_value rb_KernAux_snprintf1(mrb_state *const mrb, mrb_value self) { + mrb_int size = 0; + const char *format = NULL; + mrb_value rest[3]; + mrb_bool rest_given[3]; + + 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(); + + fmt = KernAux_PrintfFmt_Spec_parse(&spec, 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; + if (spec.set_width && argc > arg_index) { + 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(); + if (argc > arg_index) { + mrb_value arg_rb = rest[arg_index]; + + if (spec.type == KERNAUX_PRINTF_FMT_TYPE_INT) { + DynArg_use_long_long(&dynarg, mrb_integer(arg_rb)); + } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_UINT) { + mrb_int arg = mrb_integer(arg_rb); + if (arg < 0) mrb_raise(mrb, E_ARGUMENT_ERROR, "expected non-negative argument"); + DynArg_use_unsigned_long_long(&dynarg, arg); + } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_FLOAT || + spec.type == KERNAUX_PRINTF_FMT_TYPE_EXP) + { + DynArg_use_double(&dynarg, mrb_as_float(mrb, arg_rb)); + } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_CHAR) { + DynArg_use_char(&dynarg, *RSTRING_CSTR(mrb, arg_rb)); + } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_STR) { + DynArg_use_str(&dynarg, RSTRING_CSTR(mrb, arg_rb)); + } + } + + char *const str = malloc(size); + if (!str) mrb_raise(mrb, mrb_exc_get_id(mrb, MRB_ERROR_SYM(NoMemoryError)), "snprintf1 buffer malloc"); + + int slen; + if (spec.set_width) { + if (spec.set_precision) { + slen = dynarg.use_dbl + ? kernaux_snprintf(str, size, format, spec.width, spec.precision, dynarg.dbl) + : kernaux_snprintf(str, size, format, spec.width, spec.precision, dynarg.arg); + } else { + slen = dynarg.use_dbl + ? kernaux_snprintf(str, size, format, spec.width, dynarg.dbl) + : kernaux_snprintf(str, size, format, spec.width, dynarg.arg); + } + } else { + if (spec.set_precision) { + slen = dynarg.use_dbl + ? kernaux_snprintf(str, size, format, spec.precision, dynarg.dbl) + : kernaux_snprintf(str, size, format, spec.precision, dynarg.arg); + } else { + slen = dynarg.use_dbl + ? kernaux_snprintf(str, size, format, dynarg.dbl) + : kernaux_snprintf(str, size, format, dynarg.arg); + } + } + + mrb_value output_rb = + mrb_obj_freeze(mrb, mrb_str_cat_cstr(mrb, mrb_str_new_lit(mrb, ""), str)); + free(str); + mrb_value values[2]; - values[0] = mrb_obj_freeze(mrb, mrb_str_new_lit(mrb, "")); - values[1] = mrb_nil_value(); + values[0] = output_rb; + values[1] = mrb_fixnum_value(slen); return mrb_obj_freeze(mrb, mrb_ary_new_from_values(mrb, 2, values)); } diff --git a/pkgs/mruby/test/printf.rb b/pkgs/mruby/test/printf.rb index 6574a33..a81d88a 100644 --- a/pkgs/mruby/test/printf.rb +++ b/pkgs/mruby/test/printf.rb @@ -12,7 +12,20 @@ assert 'KernAux.sprintf' do YAML.load(File.read(printf_yml)).each do |test| expected = test['result'] - args = test['args'] + + args = test['args'].map do |arg| + if arg.is_a? String + arg + else + arg.map do |item| + if item.is_a? Array + item[0] + else + item + end + end + end + end assert "transforms #{args.inspect} to #{expected.inspect}" do assert_equal expected, KernAux.sprintf(*args) diff --git a/pkgs/ruby/ext/default/printf.c b/pkgs/ruby/ext/default/printf.c index 314bf04..1ca4f95 100644 --- a/pkgs/ruby/ext/default/printf.c +++ b/pkgs/ruby/ext/default/printf.c @@ -19,7 +19,6 @@ void init_printf() rb_KernAux_snprintf1, -1); } -// TODO: is this implementation correct? // FIXME: rewrite to ensure no memory leak on exception. VALUE rb_KernAux_snprintf1( const int argc,