Skip to content

Commit 11cc3ed

Browse files
committed
Kernel#sprintf: fix %c - take into account argument encoding
1 parent f9ae7a9 commit 11cc3ed

File tree

14 files changed

+187
-170
lines changed

14 files changed

+187
-170
lines changed

spec/ruby/core/kernel/shared/sprintf.rb

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,18 +292,28 @@ def obj.to_i; 10; end
292292
it "raises ArgumentError if argument is a string of several characters" do
293293
-> {
294294
@method.call("%c", "abc")
295-
}.should raise_error(ArgumentError)
295+
}.should raise_error(ArgumentError, /%c requires a character/)
296296
end
297297

298298
it "raises ArgumentError if argument is an empty string" do
299299
-> {
300300
@method.call("%c", "")
301-
}.should raise_error(ArgumentError)
301+
}.should raise_error(ArgumentError, /%c requires a character/)
302302
end
303303

304-
it "supports Unicode characters" do
305-
@method.call("%c", 1286).should == "Ԇ"
306-
@method.call("%c", "ش").should == "ش"
304+
it "raises TypeError if argument is nil" do
305+
-> {
306+
@method.call("%c", nil)
307+
}.should raise_error(TypeError, /no implicit conversion from nil to integer/)
308+
end
309+
310+
it "tries to convert argument to String with to_str" do
311+
obj = BasicObject.new
312+
def obj.to_str
313+
"a"
314+
end
315+
316+
@method.call("%c", obj).should == "a"
307317
end
308318
end
309319

@@ -927,4 +937,8 @@ def obj.to_str; end
927937
}
928938
end
929939
end
940+
941+
it "does not raise error when passed more arguments than needed" do
942+
sprintf("%s %d %c", "string", 2, "c", []).should == "string 2 c"
943+
end
930944
end

spec/ruby/core/kernel/shared/sprintf_encoding.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Keep encoding-related specs in a separate shared example to be able to skip them in IO/File/StringIO specs.
2+
# It's difficult to check result's encoding in the test after writing to a file/io buffer.
13
describe :kernel_sprintf_encoding, shared: true do
24
it "can produce a string with valid encoding" do
35
string = @method.call("good day %{valid}", valid: "e")
@@ -33,4 +35,33 @@
3335
@method.call(string, argument)
3436
}.should raise_error(Encoding::CompatibilityError)
3537
end
38+
39+
describe "%c" do
40+
it "supports Unicode characters" do
41+
result = @method.call("%c", 1286)
42+
result.should == "Ԇ"
43+
result.bytes.should == [212, 134]
44+
45+
result = @method.call("%c", "ش")
46+
result.should == "ش"
47+
result.bytes.should == [216, 180]
48+
end
49+
50+
it "raises error when a codepoint isn't representable in an encoding of a format string" do
51+
format = "%c".encode("ASCII")
52+
53+
-> {
54+
@method.call(format, 1286)
55+
}.should raise_error(RangeError, /out of char range/)
56+
end
57+
58+
it "uses the encoding of the format string to interpret codepoints" do
59+
format = "%c".force_encoding("euc-jp")
60+
result = @method.call(format, 9415601)
61+
62+
result.encoding.should == Encoding::EUC_JP
63+
result.should == "é".encode(Encoding::EUC_JP)
64+
result.bytes.should == [143, 171, 177]
65+
end
66+
end
3667
end

spec/tags/core/kernel/printf_tags.txt

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,6 @@
1-
fails:Kernel.printf formatting io is specified other formats c raises ArgumentError if argument is an empty string
2-
fails:Kernel.printf formatting io is specified other formats c supports Unicode characters
31
fails:Kernel.printf formatting io is specified other formats s does not try to convert with to_str
42
fails:Kernel.printf formatting io is specified flags # applies to format o does nothing for negative argument
53
fails:Kernel.printf formatting io is specified flags # applies to formats bBxX does nothing for zero argument
6-
fails:Kernel.printf formatting io is specified flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow
7-
fails:Kernel.printf formatting io is specified flags # applies to gG does not remove trailing zeros
8-
fails:Kernel.printf formatting io is specified flags - left-justifies the result of conversion if width is specified
9-
fails:Kernel.printf formatting io is specified flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified uses radix-1 when displays negative argument as a two's complement
10-
fails:Kernel.printf formatting io is specified flags * left-justifies the result if width is negative
11-
fails:Kernel.printf formatting io is specified flags * left-justifies the result if specified with $ argument is negative
12-
fails:Kernel.printf formatting io is specified precision string formats determines the maximum number of characters to be copied from the string
13-
fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision
14-
fails:Kernel.printf formatting io is not specified other formats c raises ArgumentError if argument is an empty string
15-
fails:Kernel.printf formatting io is not specified other formats c supports Unicode characters
164
fails:Kernel.printf formatting io is not specified other formats s does not try to convert with to_str
175
fails:Kernel.printf formatting io is not specified flags # applies to format o does nothing for negative argument
18-
fails:Kernel.printf formatting io is not specified flags # applies to formats bBxX does nothing for zero argument
19-
fails:Kernel.printf formatting io is not specified flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow
20-
fails:Kernel.printf formatting io is not specified flags # applies to gG does not remove trailing zeros
21-
fails:Kernel.printf formatting io is not specified flags - left-justifies the result of conversion if width is specified
22-
fails:Kernel.printf formatting io is not specified flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified uses radix-1 when displays negative argument as a two's complement
23-
fails:Kernel.printf formatting io is not specified flags * left-justifies the result if width is negative
24-
fails:Kernel.printf formatting io is not specified flags * left-justifies the result if specified with $ argument is negative
25-
fails:Kernel.printf formatting io is not specified precision string formats determines the maximum number of characters to be copied from the string
26-
fails:Kernel.printf formatting io is not specified reference by name %{name} style supports flags, width and precision
27-
fails:Kernel.printf formatting io is specified other formats % alone raises an ArgumentError
28-
fails:Kernel.printf formatting io is not specified other formats % alone raises an ArgumentError
6+
fails:Kernel.printf formatting io is not specified flags # applies to formats bBxX does nothing for zero argument
Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
1-
fails:Kernel#sprintf returns a String in the same encoding as the format String if compatible
21
fails:Kernel#sprintf returns a String in the argument's encoding if format encoding is more restrictive
32
fails:Kernel#sprintf raises Encoding::CompatibilityError if both encodings are ASCII compatible and there ano not ASCII characters
4-
fails:Kernel#sprintf other formats c raises ArgumentError if argument is an empty string
5-
fails:Kernel#sprintf other formats c supports Unicode characters
63
fails:Kernel#sprintf other formats s does not try to convert with to_str
74
fails:Kernel#sprintf flags # applies to format o does nothing for negative argument
85
fails:Kernel#sprintf flags # applies to formats bBxX does nothing for zero argument
9-
fails:Kernel.sprintf returns a String in the same encoding as the format String if compatible
106
fails:Kernel.sprintf returns a String in the argument's encoding if format encoding is more restrictive
117
fails:Kernel.sprintf raises Encoding::CompatibilityError if both encodings are ASCII compatible and there ano not ASCII characters
12-
fails:Kernel.sprintf other formats c raises ArgumentError if argument is an empty string
13-
fails:Kernel.sprintf other formats c supports Unicode characters
148
fails:Kernel.sprintf other formats s does not try to convert with to_str
159
fails:Kernel.sprintf flags # applies to format o does nothing for negative argument
1610
fails:Kernel.sprintf flags # applies to formats bBxX does nothing for zero argument

spec/tags/core/string/modulo_tags.txt

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
1-
fails:String#% output's encoding is the same as the format string if passed value is encoding-compatible
21
fails:String#% output's encoding negotiates a compatible encoding if necessary
32
fails:String#% output's encoding raises if a compatible encoding can't be found
4-
fails:String#% raises an error if single % appears at the end
5-
fails:String#% returns a String in the same encoding as the format String if compatible
63
fails:String#% returns a String in the argument's encoding if format encoding is more restrictive
74
fails:String#% raises Encoding::CompatibilityError if both encodings are ASCII compatible and there ano not ASCII characters
8-
fails:String#% other formats c raises ArgumentError if argument is an empty string
9-
fails:String#% other formats c supports Unicode characters
105
fails:String#% other formats s does not try to convert with to_str
11-
fails:String#% other formats % alone raises an ArgumentError
126
fails:String#% flags # applies to format o does nothing for negative argument
13-
fails:String#% flags # applies to formats bBxX does nothing for zero argument
14-
fails:String#% flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow
15-
fails:String#% flags # applies to gG does not remove trailing zeros
16-
fails:String#% flags - left-justifies the result of conversion if width is specified
17-
fails:String#% flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified uses radix-1 when displays negative argument as a two's complement
18-
fails:String#% flags * left-justifies the result if width is negative
19-
fails:String#% flags * left-justifies the result if specified with $ argument is negative
20-
fails:String#% precision string formats determines the maximum number of characters to be copied from the string
21-
fails:String#% reference by name %{name} style supports flags, width and precision
7+
fails:String#% flags # applies to formats bBxX does nothing for zero argument
Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
1-
fails:StringIO#printf formatting other formats c raises ArgumentError if argument is an empty string
2-
fails:StringIO#printf formatting other formats c supports Unicode characters
31
fails:StringIO#printf formatting other formats s does not try to convert with to_str
42
fails:StringIO#printf formatting other formats % is escaped by %
53
fails:StringIO#printf formatting flags # applies to format o does nothing for negative argument
64
fails:StringIO#printf formatting flags # applies to formats bBxX does nothing for zero argument
7-
fails:StringIO#printf formatting flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow
8-
fails:StringIO#printf formatting flags # applies to gG does not remove trailing zeros
9-
fails:StringIO#printf formatting flags - left-justifies the result of conversion if width is specified
10-
fails:StringIO#printf formatting flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified uses radix-1 when displays negative argument as a two's complement
11-
fails:StringIO#printf formatting flags * left-justifies the result if width is negative
12-
fails:StringIO#printf formatting flags * left-justifies the result if specified with $ argument is negative
13-
fails:StringIO#printf formatting precision string formats determines the maximum number of characters to be copied from the string
14-
fails:StringIO#printf formatting reference by name %{name} style supports flags, width and precision
155
fails:StringIO#printf formatting other formats % alone raises an ArgumentError

src/main/java/org/truffleruby/core/exception/CoreExceptions.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.truffleruby.core.module.ModuleOperations;
2828
import org.truffleruby.core.module.RubyModule;
2929
import org.truffleruby.core.proc.RubyProc;
30-
import org.truffleruby.core.range.RubyIntRange;
3130
import org.truffleruby.core.string.CoreStrings;
3231
import org.truffleruby.core.string.RubyString;
3332
import org.truffleruby.core.string.StringOperations;
@@ -1099,12 +1098,8 @@ public RubyException rangeError(long code, RubyEncoding encoding, Node currentNo
10991098
}
11001099

11011100
@TruffleBoundary
1102-
public RubyException rangeError(RubyIntRange range, Node currentNode) {
1103-
return rangeError(StringUtils.format(
1104-
"%d..%s%d out of range",
1105-
range.begin,
1106-
range.excludedEnd ? "." : "",
1107-
range.end), currentNode);
1101+
public RubyException charRangeError(int codepoint, Node currentNode) {
1102+
return rangeError(StringUtils.format("%d out of char range", codepoint), currentNode);
11081103
}
11091104

11101105
@TruffleBoundary

src/main/java/org/truffleruby/core/format/FormatEncoding.java

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@
1717
import org.truffleruby.core.encoding.RubyEncoding;
1818
import org.truffleruby.language.control.RaiseException;
1919

20-
public enum FormatEncoding {
20+
public class FormatEncoding {
2121

22-
DEFAULT(Encodings.BINARY),
23-
ASCII_8BIT(Encodings.BINARY),
24-
US_ASCII(Encodings.US_ASCII),
25-
UTF_8(Encodings.UTF_8);
22+
public static final FormatEncoding DEFAULT = new FormatEncoding(Encodings.BINARY);
23+
public static final FormatEncoding ASCII_8BIT = new FormatEncoding(Encodings.BINARY);
24+
public static final FormatEncoding US_ASCII = new FormatEncoding(Encodings.US_ASCII);
25+
public static final FormatEncoding UTF_8 = new FormatEncoding(Encodings.UTF_8);
2626

2727
private final RubyEncoding encoding;
2828

29-
FormatEncoding(RubyEncoding encoding) {
29+
public FormatEncoding(RubyEncoding encoding) {
3030
this.encoding = encoding;
3131
}
3232

@@ -73,22 +73,18 @@ public FormatEncoding unifyWith(FormatEncoding other) {
7373
return this;
7474
}
7575

76-
switch (other) {
77-
case ASCII_8BIT:
78-
case US_ASCII:
76+
if (other == ASCII_8BIT || other == US_ASCII) {
77+
return ASCII_8BIT;
78+
} else if (other == UTF_8) {
79+
if (this == ASCII_8BIT || this == US_ASCII) {
7980
return ASCII_8BIT;
80-
case UTF_8:
81-
switch (this) {
82-
case ASCII_8BIT:
83-
case US_ASCII:
84-
return ASCII_8BIT;
85-
case UTF_8:
86-
return UTF_8;
87-
default:
88-
throw CompilerDirectives.shouldNotReachHere();
89-
}
90-
default:
81+
} else if (this == UTF_8) {
82+
return UTF_8;
83+
} else {
9184
throw CompilerDirectives.shouldNotReachHere();
85+
}
86+
} else {
87+
throw CompilerDirectives.shouldNotReachHere();
9288
}
9389
}
9490

0 commit comments

Comments
 (0)