Ruby: implement width for printf

This commit is contained in:
Alex Kotov 2022-01-23 01:04:41 +05:00
parent f7476784ce
commit 3b2da38480
Signed by: kotovalexarian
GPG Key ID: 553C0EBBEB5D5F08
4 changed files with 92 additions and 20 deletions

View File

@ -26,7 +26,7 @@ VALUE rb_KernAux_snprintf1(
const VALUE *const argv_rb,
const VALUE self __attribute__((unused))
) {
if (argc != 2 && argc != 3) rb_raise(rb_eArgError, "expected 2 or 3 args");
if (argc < 2 || argc > 4) rb_raise(rb_eArgError, "expected 2, 3 or 4 args");
const VALUE size_rb = argv_rb[0];
VALUE format_rb = argv_rb[1];
@ -41,6 +41,8 @@ VALUE rb_KernAux_snprintf1(
while (*fmt && *fmt != '%') ++fmt;
if (*(fmt++) != '%') rb_raise(rb_eArgError, "invalid format");
bool has_width = false;
// Mimic printf behavior.
{
bool flags;
@ -59,6 +61,7 @@ VALUE rb_KernAux_snprintf1(
while (*fmt >= '0' && *fmt <= '9') ++fmt;
} else if (*fmt == '*') {
++fmt;
has_width = true;
}
if (*fmt == '.') {
++fmt;
@ -85,6 +88,9 @@ VALUE rb_KernAux_snprintf1(
if (*(fmt++) == '%') rb_raise(rb_eArgError, "invalid format");
}
int width = 0;
if (has_width && argc > 2) width = NUM2INT(argv_rb[2]);
bool use_dbl = false;
double dbl;
union {
@ -94,8 +100,8 @@ VALUE rb_KernAux_snprintf1(
char chr;
} __attribute__((packed)) arg = { .str = "" };
if (argc == 3) {
VALUE arg_rb = argv_rb[2];
if (argc == (has_width ? 4 : 3)) {
VALUE arg_rb = argv_rb[has_width ? 3 : 2];
if (c == 'd' || c == 'i') {
RB_INTEGER_TYPE_P(arg_rb);
@ -121,9 +127,18 @@ VALUE rb_KernAux_snprintf1(
char *const str = malloc(size);
if (!str) rb_raise(rb_eNoMemError, "snprintf1 buffer malloc");
const int slen = use_dbl
? kernaux_snprintf(str, size, format, dbl)
: kernaux_snprintf(str, size, format, arg);
int slen;
if (has_width) {
slen = use_dbl
? kernaux_snprintf(str, size, format, width, dbl)
: kernaux_snprintf(str, size, format, width, arg);
} else {
slen = use_dbl
? kernaux_snprintf(str, size, format, dbl)
: kernaux_snprintf(str, size, format, arg);
}
const VALUE output_rb =
rb_funcall(rb_str_new2(str), rb_intern_freeze, 0);
free(str);

View File

@ -33,13 +33,7 @@ module KernAux
end.join.freeze
end
def self.sprintf1(format, arg = nil)
if arg.nil?
snprintf1(SPRINTF1_BUFFER_SIZE, format).first
else
snprintf1(SPRINTF1_BUFFER_SIZE, format, arg).first
end
end
def self.sprintf1(...) = snprintf1(SPRINTF1_BUFFER_SIZE, ...).first
##
# Our base class for runtime errors.

View File

@ -33,7 +33,7 @@ RSpec.describe KernAux, '.snprintf1' do
end
context 'when size has invalid type' do
let(:size) { '10_000' }
let(:size) { '10000' }
specify { expect { snprintf1 }.to raise_error TypeError }
end
@ -93,7 +93,68 @@ RSpec.describe KernAux, '.snprintf1' do
end
context 'when size has invalid type' do
let(:size) { '10_000' }
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
@ -127,21 +188,21 @@ RSpec.describe KernAux, '.snprintf1' do
context 'with 0 arguments' do
specify do
expect { described_class.snprintf1 }.to \
raise_error ArgumentError, 'expected 2 or 3 args'
raise_error ArgumentError, 'expected 2, 3 or 4 args'
end
end
context 'with 1 argument' do
specify do
expect { described_class.snprintf1 size }.to \
raise_error ArgumentError, 'expected 2 or 3 args'
raise_error ArgumentError, 'expected 2, 3 or 4 args'
end
end
context 'with 4 arguments' do
context 'with 5 arguments' do
specify do
expect { described_class.snprintf1 size, '%s', 'foo', 'bar' }.to \
raise_error ArgumentError, 'expected 2 or 3 args'
expect { described_class.snprintf1 size, '%*s', 20, 'foo', 'bar' }.to \
raise_error ArgumentError, 'expected 2, 3 or 4 args'
end
end
end

View File

@ -106,6 +106,8 @@ int main()
test("123.456789", "%f", 123.456789);
#endif
test("%", "%*%", 20);
// - flag
// ...
test("42", "%0-d", 42);