From 187475826e496ad1733fd62060974f2e9dc22099 Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Tue, 13 Dec 2022 02:55:25 +0400 Subject: [PATCH 1/5] Use programs more consistently (#141) --- .github/workflows/main.yml | 4 ++-- README.md | 7 ++++--- configure.ac | 4 +--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1745f6c..2962234 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -76,7 +76,7 @@ jobs: - name: autogen run: ./autogen.sh - name: configure - run: ./configure --host='${{matrix.cross.arch}}-unknown-elf' --enable-checks --enable-checks-pthreads --enable-checks-python AR='${{matrix.cross.cprefix}}ar' CC='${{matrix.cross.cprefix}}gcc' LD='${{matrix.cross.cprefix}}ld' RANLIB='${{matrix.cross.cprefix}}ranlib' + run: ./configure --host='${{matrix.cross.arch}}-unknown-elf' --enable-checks --enable-checks-pthreads --enable-checks-python CC='${{matrix.cross.cprefix}}gcc' - name: make run: make - name: check @@ -121,7 +121,7 @@ jobs: - name: autogen run: ./autogen.sh - name: configure - run: ./configure --host='i386-elf' ${{matrix.debug}} --enable-freestanding --with-libc AR="$(which i686-linux-gnu-ar)" CC="$(which i686-linux-gnu-gcc)" LD="$(which i686-linux-gnu-ld)" RANLIB="$(which i686-linux-gnu-ranlib)" + run: ./configure --host='i386-elf' ${{matrix.debug}} --enable-freestanding --with-libc CC="$(which i686-linux-gnu-gcc)" - name: make run: make diff --git a/README.md b/README.md index 4161746..508c649 100644 --- a/README.md +++ b/README.md @@ -212,11 +212,12 @@ without it in `$PATH`: --host='i386-elf' \ --enable-freestanding \ --with-libc \ - AR="$(which i386-elf-ar)" \ - CC="$(which i386-elf-gcc)" \ - RANLIB="$(which i386-elf-ranlib)" + CC="$(which i386-elf-gcc)" ``` +The variables include `AR`, `AS`, `CC`, `CCAS`, `LD`, `NM`, `OBJDUMP`, `RANLIB`, +`STRIP`. See the generated `config.log` for more information. + To install into specific directory use full path: `DESTDIR="$(pwd)/dest" make install` instead of `DESTDIR=dest make install`. diff --git a/configure.ac b/configure.ac index 393838e..ff4a35c 100644 --- a/configure.ac +++ b/configure.ac @@ -319,10 +319,8 @@ AS_IF([test "$enable_freestanding" = yes], ############## AC_LANG([C]) - -AM_PROG_AR AM_PROG_AS -AC_PROG_CC + AC_C_INLINE AC_HEADER_STDBOOL From f3f34e905731cf9ddf03f2585259bf2cd27bccab Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Tue, 13 Dec 2022 04:54:27 +0400 Subject: [PATCH 2/5] Fix FreeBSD builds (#142) --- .cirrus.yml | 5 +++-- ChangeLog | 4 ++++ tests/Makefile.am | 6 +++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 5aa5b9d..b05e32b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -8,10 +8,11 @@ main_freebsd_task: name: Main (FreeBSD) only_if: $CIRRUS_BRANCH == 'master' || $CIRRUS_BASE_BRANCH == 'master' dependencies_script: - - pkg install --yes autoconf automake libtool + - pkg install --yes autoconf automake cppcheck libtool libyaml py39-pip py39-wheel python3 + - pip install --user Jinja2 PyYAML main_build_script: - ./autogen.sh - - ./configure --enable-debug --enable-checks --enable-checks-pthreads CFLAGS='-O3' + - ./configure --enable-debug --enable-checks-all CFLAGS='-O3' - make - sudo make install main_test_script: diff --git a/ChangeLog b/ChangeLog index e02ea03..e3d9e3d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2022-12-13 Alex Kotov + + * tests/Makefile.am: Fix FreeBSD builds + 2022-12-12 Alex Kotov * examples/Makefile.am: Fix builds without pthreads diff --git a/tests/Makefile.am b/tests/Makefile.am index d027201..841ab08 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -104,7 +104,7 @@ endif CLEANFILES += test_cmdline_gen.c test_cmdline_gen.c: $(top_srcdir)/tests/cmdline_gen.py $(top_srcdir)/tests/cmdline_gen.jinja $(top_srcdir)/common/cmdline.yml - $(PYTHON) $+ $@ + $(PYTHON) $(top_srcdir)/tests/cmdline_gen.py $(top_srcdir)/tests/cmdline_gen.jinja $(top_srcdir)/common/cmdline.yml test_cmdline_gen.c ############ # test_elf # @@ -338,7 +338,7 @@ endif CLEANFILES += test_printf_fmt_gen.c test_printf_fmt_gen.c: $(top_srcdir)/tests/printf_fmt_gen.py $(top_srcdir)/tests/printf_fmt_gen.jinja $(top_srcdir)/common/printf_fmt.yml - $(PYTHON) $+ $@ + $(PYTHON) $(top_srcdir)/tests/printf_fmt_gen.py $(top_srcdir)/tests/printf_fmt_gen.jinja $(top_srcdir)/common/printf_fmt.yml test_printf_fmt_gen.c ################### # test_printf_gen # @@ -361,7 +361,7 @@ endif CLEANFILES += test_printf_gen.c test_printf_gen.c: $(top_srcdir)/tests/printf_gen.py $(top_srcdir)/tests/printf_gen.jinja $(top_srcdir)/common/printf.yml $(top_srcdir)/common/printf_orig.yml - $(PYTHON) $+ $@ + $(PYTHON) $(top_srcdir)/tests/printf_gen.py $(top_srcdir)/tests/printf_gen.jinja $(top_srcdir)/common/printf.yml $(top_srcdir)/common/printf_orig.yml test_printf_gen.c #################### # test_units_human # From 0d8bdec847e917be6b49f70e7e1b006ce879af66 Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Tue, 13 Dec 2022 06:55:09 +0400 Subject: [PATCH 3/5] OpenBSD CI (#143) --- .openbsd.yml | 39 +++++++++++++++++++++++++++++++++++++++ make/checks.am | 8 +++++--- 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 .openbsd.yml diff --git a/.openbsd.yml b/.openbsd.yml new file mode 100644 index 0000000..cfd6ca3 --- /dev/null +++ b/.openbsd.yml @@ -0,0 +1,39 @@ +# We use sourcehut CI (https://builds.sr.ht) to test on OpenBSD. +# For GNU/Linux CI see GitHub Actions. + +image: openbsd/7.2 +arch: amd64 +sources: + - https://github.com/tailix/libkernaux.git +packages: + - autoconf-2.71 + - automake-1.16.5 + - cppcheck + - libyaml + - m4 + - py3-pip + - py3-wheel + - python3 + - wget +environment: + AUTOCONF_VERSION: '2.71' + AUTOMAKE_VERSION: '1.16' +tasks: + - libtool: | + wget https://ftpmirror.gnu.org/libtool/libtool-2.4.7.tar.gz + tar -xzf libtool-2.4.7.tar.gz + cd libtool-2.4.7 + ./configure + make + doas make install + - dependencies: | + pip3 install --user Jinja2 PyYAML + - build: | + cd libkernaux + ./autogen.sh + ./configure --enable-debug --enable-checks-all CFLAGS='-O3' + make + doas make install + - test: | + cd libkernaux + make check diff --git a/make/checks.am b/make/checks.am index cd68bd1..0424b4a 100644 --- a/make/checks.am +++ b/make/checks.am @@ -20,9 +20,11 @@ CPPCHECK_INC = \ -I$(top_srcdir)/include CPPCHECK_SUPPRESS = \ - --suppress='constArgument:$(top_srcdir)/examples/macro_cast.c' \ - --suppress='unusedStructMember:$(top_srcdir)/examples/*.c' \ - --suppress='unusedStructMember:$(top_srcdir)/tests/test_multiboot2_info_*.c' + --suppress='constArgument:$(top_srcdir)/examples/macro_cast.c' \ + --suppress='constParameter:$(top_srcdir)/examples/printf_file*.c' \ + --suppress='unusedStructMember:$(top_srcdir)/examples/*.c' \ + --suppress='unusedStructMember:$(top_srcdir)/tests/test_multiboot2_info_*.c' \ + --suppress='unusedVariable' CPPCHECK_PATHS = \ $(top_builddir)/examples \ From be761bd9f7c98ef12e967a46b605efbe44103cfb Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Wed, 14 Dec 2022 11:03:47 +0400 Subject: [PATCH 4/5] Shared library (#144) Closes #42 --- .github/workflows/mruby.yml | 2 ++ .github/workflows/ruby.yml | 2 ++ .github/workflows/rust.yml | 2 ++ ChangeLog | 4 ++++ Makefile.am | 1 + README.md | 2 ++ VERSION_SO | 1 + configure.ac | 4 +++- 8 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 VERSION_SO diff --git a/.github/workflows/mruby.yml b/.github/workflows/mruby.yml index 64c44cc..4e08365 100644 --- a/.github/workflows/mruby.yml +++ b/.github/workflows/mruby.yml @@ -35,6 +35,8 @@ jobs: run: make - name: install run: sudo make install + - name: ldconfig + run: sudo ldconfig - working-directory: vendor/mruby name: test run: MRUBY_CONFIG=../../bindings/mruby/build_config.rb rake test diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 5f7ab12..2ea62d7 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -33,6 +33,8 @@ jobs: run: make - name: install run: sudo make install + - name: ldconfig + run: sudo ldconfig - working-directory: bindings/ruby name: setup run: ./bin/setup diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3fb6f01..875ba18 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -32,6 +32,8 @@ jobs: run: make - name: install run: sudo make install + - name: ldconfig + run: sudo ldconfig - working-directory: bindings/rust name: test run: cargo test ${{matrix.packages.cargo}} diff --git a/ChangeLog b/ChangeLog index e3d9e3d..50013cf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2022-12-14 Alex Kotov + + * configure.ac: Enable shared library + 2022-12-13 Alex Kotov * tests/Makefile.am: Fix FreeBSD builds diff --git a/Makefile.am b/Makefile.am index b3aea18..3822d08 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,6 +28,7 @@ lib_LTLIBRARIES = libkernaux.la # Required files # ################## +libkernaux_la_LDFLAGS = -version-info @PACKAGE_VERSION_SO@ libkernaux_la_LIBADD = libkernaux_la_SOURCES = \ src/assert.c \ diff --git a/README.md b/README.md index 508c649..030ceba 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ Tips ./configure make sudo make install +sudo ldconfig # on GNU/Linux ``` This is just a usual library. You can use most of it's APIs in hosted @@ -210,6 +211,7 @@ without it in `$PATH`: ``` ./configure \ --host='i386-elf' \ + --disable-shared \ --enable-freestanding \ --with-libc \ CC="$(which i386-elf-gcc)" diff --git a/VERSION_SO b/VERSION_SO new file mode 100644 index 0000000..f730b58 --- /dev/null +++ b/VERSION_SO @@ -0,0 +1 @@ +0:0:0 diff --git a/configure.ac b/configure.ac index ff4a35c..7e71f46 100644 --- a/configure.ac +++ b/configure.ac @@ -35,6 +35,8 @@ AC_CONFIG_FILES([ AM_INIT_AUTOMAKE([1.16 subdir-objects]) +AC_SUBST([PACKAGE_VERSION_SO], m4_normalize(m4_include([VERSION_SO]))) + ############### @@ -378,7 +380,7 @@ AS_IF([test "$enable_checks_python" = yes -a "$enable_checks" = yes -a "$PYTHON_ # Initialize Libtool # ###################### -LT_INIT([disable-shared]) +LT_INIT From 18f3c70c1b26c2803ee2f892cbca7363112481cc Mon Sep 17 00:00:00 2001 From: Alex Kotov Date: Thu, 15 Dec 2022 03:23:58 +0400 Subject: [PATCH 5/5] Improve printf bindings for Ruby & mruby (#145) --- bindings/mruby/.rubocop.yml | 6 +- bindings/mruby/build_config.rb | 1 + bindings/mruby/mrblib/kernaux.rb | 18 -- bindings/mruby/src/printf.c | 216 +++++++------- bindings/mruby/test/printf.rb | 82 ----- bindings/mruby/test/sprintf.rb | 113 +++++++ bindings/ruby/ext/default/printf.c | 260 ++++++---------- bindings/ruby/lib/kernaux.rb | 1 - bindings/ruby/lib/kernaux/printf.rb | 70 ----- .../spec/lib/kernaux/printf/snprintf1_spec.rb | 280 ------------------ .../spec/lib/kernaux/printf/sprintf1_spec.rb | 26 -- .../spec/lib/kernaux/printf/sprintf_spec.rb | 59 ---- .../ruby/spec/lib/kernaux/sprintf_spec.rb | 85 ++++++ 13 files changed, 404 insertions(+), 813 deletions(-) delete mode 100644 bindings/mruby/test/printf.rb create mode 100644 bindings/mruby/test/sprintf.rb delete mode 100644 bindings/ruby/lib/kernaux/printf.rb delete mode 100644 bindings/ruby/spec/lib/kernaux/printf/snprintf1_spec.rb delete mode 100644 bindings/ruby/spec/lib/kernaux/printf/sprintf1_spec.rb delete mode 100644 bindings/ruby/spec/lib/kernaux/printf/sprintf_spec.rb create mode 100644 bindings/ruby/spec/lib/kernaux/sprintf_spec.rb diff --git a/bindings/mruby/.rubocop.yml b/bindings/mruby/.rubocop.yml index 084bd23..7d92103 100644 --- a/bindings/mruby/.rubocop.yml +++ b/bindings/mruby/.rubocop.yml @@ -27,7 +27,11 @@ Metrics/BlockLength: Metrics/BlockNesting: Exclude: - - 'test/printf.rb' + - 'test/sprintf.rb' + +Performance/CollectionLiteralInLoop: + Exclude: + - 'test/**/*.rb' Security/Eval: Exclude: diff --git a/bindings/mruby/build_config.rb b/bindings/mruby/build_config.rb index a2ae5ef..2c4a639 100644 --- a/bindings/mruby/build_config.rb +++ b/bindings/mruby/build_config.rb @@ -2,4 +2,5 @@ MRuby::Build.new do |conf| conf.toolchain conf.enable_test conf.gem '.' + conf.gem core: 'mruby-bin-mirb' end diff --git a/bindings/mruby/mrblib/kernaux.rb b/bindings/mruby/mrblib/kernaux.rb index 8dd38e7..456de38 100644 --- a/bindings/mruby/mrblib/kernaux.rb +++ b/bindings/mruby/mrblib/kernaux.rb @@ -7,24 +7,6 @@ module KernAux 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 AssertError < Error; end class CmdlineError < Error; end diff --git a/bindings/mruby/src/printf.c b/bindings/mruby/src/printf.c index 85fdbd0..02b05f0 100644 --- a/bindings/mruby/src/printf.c +++ b/bindings/mruby/src/printf.c @@ -1,155 +1,147 @@ #include "main.h" - #include "dynarg.h" -#include -#include - #include #include #include #include +#define BUFFER_SIZE 4096 + #ifdef KERNAUX_VERSION_WITH_PRINTF -struct snprintf1_userdata { - 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); +static mrb_value rb_KernAux_sprintf(mrb_state *mrb, mrb_value self); void init_printf(mrb_state *const mrb) { 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_ARGS_REQ(2) | MRB_ARGS_OPT(2)); + mrb_define_class_method(mrb, rb_KernAux, "sprintf", rb_KernAux_sprintf, + 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; - 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_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; + // FIXME: const + char *format; + mrb_value *args; + mrb_int argc; + mrb_get_args(mrb, "z*", &format, &args, &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++])); - } + mrb_value result = mrb_str_new_lit(mrb, ""); - struct DynArg dynarg = DynArg_create(); - if (argc > arg_index) { - mrb_value arg_rb = rest[arg_index]; + while (*format) { + if (*format != '%') { + 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) { + TAKE_ARG; + mrb_ensure_int_type(mrb, arg_rb); 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); + TAKE_ARG; + mrb_ensure_int_type(mrb, arg_rb); + DynArg_use_unsigned_long_long(&dynarg, mrb_integer(arg_rb)); } 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)); + 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) { - 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) { + TAKE_ARG; + mrb_ensure_string_type(mrb, arg_rb); 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"); + char buffer[BUFFER_SIZE]; + int slen; - struct snprintf1_userdata userdata = { - .spec = &spec, - .dynarg = &dynarg, - .size = size, - .format = format, - .str = str, - }; - mrb_bool error; - mrb_value result = mrb_protect_error(mrb, snprintf1_protect, &userdata, &error); + // FIXME: it's a hack + // TODO: convert printf format spec to string + const char tmp = *format; + *format = '\0'; - free(str); - - if (error) { - mrb_exc_raise(mrb, result); - } else { - return result; - } -} - -mrb_value snprintf1_protect(mrb_state *const mrb, void *const userdata_raw) -{ - const struct snprintf1_userdata *const userdata = userdata_raw; - - 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); + if (spec.set_width) { + if (spec.set_precision) { + if (dynarg.use_dbl) { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.width, spec.precision, + dynarg.dbl); + } else { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.width, spec.precision, + dynarg.arg); + } + } else { + if (dynarg.use_dbl) { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.width, dynarg.dbl); + } else { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.width, dynarg.arg); + } + } } else { - slen = userdata->dynarg->use_dbl - ? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->dynarg->dbl) - : kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->dynarg->arg); - } - } else { - if (userdata->spec->set_precision) { - slen = userdata->dynarg->use_dbl - ? 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 { - slen = userdata->dynarg->use_dbl - ? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->dynarg->dbl) - : kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->dynarg->arg); + if (spec.set_precision) { + if (dynarg.use_dbl) { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.precision, dynarg.dbl); + } else { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.precision, dynarg.arg); + } + } else { + if (dynarg.use_dbl) { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, dynarg.dbl); + } else { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, dynarg.arg); + } + } } + + *format = tmp; + mrb_str_cat(mrb, result, buffer, slen); } - mrb_value output_rb = - mrb_obj_freeze(mrb, mrb_str_cat_cstr(mrb, mrb_str_new_lit(mrb, ""), userdata->str)); + if (arg_index < argc) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "too many arguments"); + } - mrb_value values[2]; - values[0] = output_rb; - values[1] = mrb_fixnum_value(slen); - return mrb_obj_freeze(mrb, mrb_ary_new_from_values(mrb, 2, values)); + return mrb_obj_freeze(mrb, result); } #endif // KERNAUX_VERSION_WITH_PRINTF diff --git a/bindings/mruby/test/printf.rb b/bindings/mruby/test/printf.rb deleted file mode 100644 index 13c311d..0000000 --- a/bindings/mruby/test/printf.rb +++ /dev/null @@ -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 diff --git a/bindings/mruby/test/sprintf.rb b/bindings/mruby/test/sprintf.rb new file mode 100644 index 0000000..d656b5a --- /dev/null +++ b/bindings/mruby/test/sprintf.rb @@ -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 diff --git a/bindings/ruby/ext/default/printf.c b/bindings/ruby/ext/default/printf.c index f638188..b2785b6 100644 --- a/bindings/ruby/ext/default/printf.c +++ b/bindings/ruby/ext/default/printf.c @@ -1,208 +1,140 @@ #include "main.h" - #include "dynarg.h" -#include -#include +#define BUFFER_SIZE 4096 #ifdef KERNAUX_VERSION_WITH_PRINTF -/************* - * ::KernAux * - *************/ - -static VALUE rb_KernAux_snprintf1(int argc, const VALUE *argv, VALUE self); - -static VALUE rb_KernAux_snprintf1_PROTECT(VALUE userdata); - -/************************ - * ::KernAux::Snprintf1 * - ************************/ - -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 * - ********/ +/** + * Typical `printf`. + * + * @param format [String] format string + * @return [String] formatted output + * + * @example + * KernAux.sprintf 'foo%*scar%d', 5, 'bar', 123 + * #=> "foo barcar123" + */ +static VALUE rb_KernAux_sprintf(int argc, VALUE *argv, VALUE self); void init_printf() { - rb_gc_register_mark_object( - 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); + rb_define_singleton_method(rb_KernAux, "sprintf", rb_KernAux_sprintf, -1); } -/************* - * ::KernAux * - *************/ +#define TAKE_ARG \ + if (arg_index >= argc) rb_raise(rb_eArgError, "too few arguments"); \ + VALUE arg_rb = argv[arg_index++]; \ + do {} while (0) -VALUE rb_KernAux_snprintf1( - const int argc, - const VALUE *const argv_rb, - const VALUE self KERNAUX_UNUSED -) { - if (argc < 2 || argc > 5) rb_raise(rb_eArgError, "expected 2, 3, 4 or 5 args"); +VALUE rb_KernAux_sprintf(const int argc, VALUE *const argv, VALUE self) +{ + if (argc == 0) rb_raise(rb_eArgError, "too few arguments"); - const VALUE size_rb = argv_rb[0]; - VALUE format_rb = argv_rb[1]; + // FIXME: const + char *format = StringValueCStr(argv[0]); + int arg_index = 1; + VALUE result = rb_str_new_literal(""); - const int size = NUM2INT(size_rb); - const char *const format = StringValueCStr(format_rb); + while (*format) { + 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; - 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]; + struct DynArg dynarg = DynArg_create(); if (spec.type == KERNAUX_PRINTF_FMT_TYPE_INT) { + TAKE_ARG; DynArg_use_long_long(&dynarg, NUM2LL(arg_rb)); } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_UINT) { + TAKE_ARG; DynArg_use_unsigned_long_long(&dynarg, NUM2ULL(arg_rb)); } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_FLOAT || spec.type == KERNAUX_PRINTF_FMT_TYPE_EXP) { + TAKE_ARG; DynArg_use_double(&dynarg, NUM2DBL(arg_rb)); } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_CHAR) { + TAKE_ARG; Check_Type(arg_rb, T_STRING); DynArg_use_char(&dynarg, *StringValuePtr(arg_rb)); } else if (spec.type == KERNAUX_PRINTF_FMT_TYPE_STR) { + TAKE_ARG; Check_Type(arg_rb, T_STRING); DynArg_use_str(&dynarg, StringValueCStr(arg_rb)); } - } - char *const str = malloc(size); - if (!str) rb_raise(rb_eNoMemError, "snprintf1 buffer malloc"); + char buffer[BUFFER_SIZE]; + int slen; - struct rb_KernAux_Snprintf1_DATA *userdata; - VALUE userdata_rb = TypedData_Make_Struct( - rb_KernAux_Snprintf1, - struct rb_KernAux_Snprintf1_DATA, - &rb_KernAux_Snprintf1_DTYPE, - userdata - ); - if (NIL_P(userdata_rb) || userdata == NULL) { - free(str); - rb_raise(rb_eNoMemError, "snprintf1 userdata alloc"); - } + // FIXME: it's a hack + // TODO: convert printf format spec to string + const char tmp = *format; + *format = '\0'; - userdata->spec = &spec; - userdata->dynarg = &dynarg; - userdata->size = size; - userdata->format = format; - userdata->str = str; - - int state = 0; - VALUE result = - rb_protect(rb_KernAux_snprintf1_PROTECT, userdata_rb, &state); - - free(str); - - if (state == 0) { - return result; - } else { - rb_jump_tag(state); - } -} - -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); + if (spec.set_width) { + if (spec.set_precision) { + if (dynarg.use_dbl) { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.width, spec.precision, + dynarg.dbl); + } else { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.width, spec.precision, + dynarg.arg); + } + } else { + if (dynarg.use_dbl) { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.width, dynarg.dbl); + } else { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.width, dynarg.arg); + } + } } else { - slen = userdata->dynarg->use_dbl - ? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->dynarg->dbl) - : kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->spec->width, userdata->dynarg->arg); - } - } else { - if (userdata->spec->set_precision) { - slen = userdata->dynarg->use_dbl - ? 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 { - slen = userdata->dynarg->use_dbl - ? kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->dynarg->dbl) - : kernaux_snprintf(userdata->str, userdata->size, userdata->format, userdata->dynarg->arg); + if (spec.set_precision) { + if (dynarg.use_dbl) { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.precision, dynarg.dbl); + } else { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, + spec.precision, dynarg.arg); + } + } else { + if (dynarg.use_dbl) { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, dynarg.dbl); + } else { + slen = kernaux_snprintf(buffer, BUFFER_SIZE, old_format, dynarg.arg); + } + } } + + *format = tmp; + rb_str_cat(result, buffer, slen); } - const VALUE output_rb = - rb_funcall(rb_str_new2(userdata->str), rb_intern_freeze, 0); + if (arg_index < argc) rb_raise(rb_eArgError, "too many arguments"); - const VALUE result_rb = rb_ary_new2(2); - 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); + return rb_funcall(result, rb_intern_freeze, 0); } #endif // KERNAUX_VERSION_WITH_PRINTF diff --git a/bindings/ruby/lib/kernaux.rb b/bindings/ruby/lib/kernaux.rb index 62b0c7b..9b356c9 100644 --- a/bindings/ruby/lib/kernaux.rb +++ b/bindings/ruby/lib/kernaux.rb @@ -17,4 +17,3 @@ require_relative 'kernaux/assert' require_relative 'kernaux/cmdline' require_relative 'kernaux/errors' require_relative 'kernaux/ntoa' -require_relative 'kernaux/printf' diff --git a/bindings/ruby/lib/kernaux/printf.rb b/bindings/ruby/lib/kernaux/printf.rb deleted file mode 100644 index a52d006..0000000 --- a/bindings/ruby/lib/kernaux/printf.rb +++ /dev/null @@ -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, - # 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 diff --git a/bindings/ruby/spec/lib/kernaux/printf/snprintf1_spec.rb b/bindings/ruby/spec/lib/kernaux/printf/snprintf1_spec.rb deleted file mode 100644 index 2887405..0000000 --- a/bindings/ruby/spec/lib/kernaux/printf/snprintf1_spec.rb +++ /dev/null @@ -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 diff --git a/bindings/ruby/spec/lib/kernaux/printf/sprintf1_spec.rb b/bindings/ruby/spec/lib/kernaux/printf/sprintf1_spec.rb deleted file mode 100644 index 6e57f45..0000000 --- a/bindings/ruby/spec/lib/kernaux/printf/sprintf1_spec.rb +++ /dev/null @@ -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 diff --git a/bindings/ruby/spec/lib/kernaux/printf/sprintf_spec.rb b/bindings/ruby/spec/lib/kernaux/printf/sprintf_spec.rb deleted file mode 100644 index ba11072..0000000 --- a/bindings/ruby/spec/lib/kernaux/printf/sprintf_spec.rb +++ /dev/null @@ -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 diff --git a/bindings/ruby/spec/lib/kernaux/sprintf_spec.rb b/bindings/ruby/spec/lib/kernaux/sprintf_spec.rb new file mode 100644 index 0000000..c4593cb --- /dev/null +++ b/bindings/ruby/spec/lib/kernaux/sprintf_spec.rb @@ -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