Skip to content

Commit 81c9fab

Browse files
committed
Clear Kernel#BigDecimal specs
1 parent b037449 commit 81c9fab

File tree

7 files changed

+78
-49
lines changed

7 files changed

+78
-49
lines changed

lib/mri/bigdecimal/util.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,7 @@ class String
6868
#
6969
# TruffleRuby: MRI defines this method in C. We define it in Ruby for simplicity & clarity.
7070
def to_d
71-
begin
72-
BigDecimal(self)
73-
rescue ArgumentError
74-
BigDecimal(0)
75-
end
71+
Truffle.invoke_primitive :bigdecimal_new, self, Truffle::UNDEFINED, false
7672
end
7773
end
7874

lib/truffle/bigdecimal.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ def self.new(*args)
321321

322322
module Kernel
323323
def BigDecimal(value, precision = Truffle::UNDEFINED)
324-
Truffle.invoke_primitive :bigdecimal_new, value, precision
324+
Truffle.invoke_primitive :bigdecimal_new, value, precision, true
325325
end
326326
end
327327

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
11
slow:BigDecimal is not defined unless it is required
2-
fails:Kernel#BigDecimal raises ArgumentError for invalid strings
3-
fails:Kernel#BigDecimal does not ignores trailing garbage
4-
fails:Kernel#BigDecimal process underscores as Float()

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ public DynamicObject argumentError(Rope message, Node currentNode, Throwable jav
198198
return ExceptionOperations.createRubyException(context, exceptionClass, StringOperations.createString(context, message), currentNode, javaThrowable);
199199
}
200200

201+
@TruffleBoundary
202+
public DynamicObject argumentErrorInvalidBigDecimal(String string, Node currentNode) {
203+
return argumentError(StringUtils.format("invalid value for BigDecimal(): \"%s\"", string), currentNode);
204+
}
205+
201206
// FrozenError
202207

203208
@TruffleBoundary

src/main/java/org/truffleruby/stdlib/bigdecimal/BigDecimalCoreMethodNode.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,15 @@ public static boolean isNan(DynamicObject value) {
5151
}
5252

5353
protected DynamicObject createBigDecimal(Object value) {
54-
return getCreateBigDecimal().executeCreate(value, NotProvided.INSTANCE);
54+
return createBigDecimal(value, true);
5555
}
5656

57-
protected DynamicObject createBigDecimal(Object value, int digits) {
58-
return getCreateBigDecimal().executeCreate(value, digits);
57+
protected DynamicObject createBigDecimal(Object value, boolean strict) {
58+
return getCreateBigDecimal().executeCreate(value, NotProvided.INSTANCE, strict);
59+
}
60+
61+
protected DynamicObject createBigDecimal(Object value, int digits, boolean strict) {
62+
return getCreateBigDecimal().executeCreate(value, digits, strict);
5963
}
6064

6165
protected RoundingMode getRoundMode() {
@@ -110,7 +114,7 @@ protected int getLimit() {
110114
private CreateBigDecimalNode getCreateBigDecimal() {
111115
if (createBigDecimal == null) {
112116
CompilerDirectives.transferToInterpreterAndInvalidate();
113-
createBigDecimal = insert(CreateBigDecimalNodeFactory.create(null, null));
117+
createBigDecimal = insert(CreateBigDecimalNodeFactory.create(null, null, null));
114118
}
115119

116120
return createBigDecimal;

src/main/java/org/truffleruby/stdlib/bigdecimal/BigDecimalNodes.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ public abstract class BigDecimalNodes {
5252
public abstract static class NewNode extends BigDecimalCoreMethodArrayArgumentsNode {
5353

5454
@Specialization
55-
public Object newBigDecimal(Object value, NotProvided digits) {
56-
return createBigDecimal(value);
55+
public Object newBigDecimal(Object value, NotProvided notProvided, boolean strict) {
56+
return createBigDecimal(value, strict);
5757
}
5858

5959
@Specialization
60-
public Object newBigDecimal(Object value, int digits) {
61-
return createBigDecimal(value, digits);
60+
public Object newBigDecimal(Object value, int digits, boolean strict) {
61+
return createBigDecimal(value, digits, strict);
6262
}
6363

6464
}

src/main/java/org/truffleruby/stdlib/bigdecimal/CreateBigDecimalNode.java

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,18 @@
3636

3737
@NodeChild(value = "value", type = RubyNode.class)
3838
@NodeChild(value = "digits", type = RubyNode.class)
39+
@NodeChild(value = "strict", type = RubyNode.class)
3940
@ImportStatic(BigDecimalType.class)
4041
public abstract class CreateBigDecimalNode extends BigDecimalCoreMethodNode {
4142

4243
private static final String EXPONENT = "([eE][+-]?)?(\\d*)";
43-
private static final Pattern NUMBER_PATTERN = Pattern.compile("^([+-]?\\d*\\.?\\d*" + EXPONENT + ").*");
44-
private static final Pattern ZERO_PATTERN = Pattern.compile("^[+-]?0*\\.?0*" + EXPONENT);
44+
private static final Pattern NUMBER_PATTERN_STRICT = Pattern.compile("^([+-]?\\d*\\.?\\d*" + EXPONENT + ")$");
45+
private static final Pattern NUMBER_PATTERN_NON_STRICT = Pattern.compile("^([+-]?\\d*\\.?\\d*" + EXPONENT + ").*");
46+
private static final Pattern ZERO_PATTERN = Pattern.compile("^[+-]?0*\\.?0*" + EXPONENT + "$");
4547

4648
@Child private AllocateObjectNode allocateNode = AllocateObjectNode.create();
4749

48-
public abstract DynamicObject executeCreate(Object value, Object digits);
50+
public abstract DynamicObject executeCreate(Object value, Object digits, boolean strict);
4951

5052
private DynamicObject createNormalBigDecimal(BigDecimal value) {
5153
return allocateNode.allocate(getBigDecimalClass(), Layouts.BIG_DECIMAL.build(value, BigDecimalType.NORMAL));
@@ -56,20 +58,20 @@ private DynamicObject createSpecialBigDecimal(BigDecimalType type) {
5658
}
5759

5860
@Specialization
59-
public DynamicObject create(long value, NotProvided digits) {
60-
return executeCreate(value, 0);
61+
public DynamicObject create(long value, NotProvided digits, boolean strict) {
62+
return executeCreate(value, 0, strict);
6163
}
6264

6365
@Specialization
64-
public DynamicObject create(long value, int digits,
66+
public DynamicObject create(long value, int digits, boolean strict,
6567
@Cached("create()") BigDecimalCastNode bigDecimalCastNode) {
6668
BigDecimal bigDecimal = round((BigDecimal) bigDecimalCastNode.execute(value, getRoundMode()),
6769
new MathContext(digits, getRoundMode()));
6870
return createNormalBigDecimal(bigDecimal);
6971
}
7072

7173
@Specialization
72-
public DynamicObject create(double value, NotProvided digits,
74+
public DynamicObject create(double value, NotProvided digits, boolean strict,
7375
@Cached("createBinaryProfile()") ConditionProfile finiteValueProfile,
7476
@Cached("create()") BranchProfile nanProfile,
7577
@Cached("create()") BranchProfile positiveInfinityProfile,
@@ -82,7 +84,7 @@ public DynamicObject create(double value, NotProvided digits,
8284
}
8385

8486
@Specialization
85-
public DynamicObject create(double value, int digits,
87+
public DynamicObject create(double value, int digits, boolean strict,
8688
@Cached("create()") BigDecimalCastNode bigDecimalCastNode,
8789
@Cached("createBinaryProfile()") ConditionProfile finiteValueProfile,
8890
@Cached("create()") BranchProfile nanProfile,
@@ -99,7 +101,7 @@ public DynamicObject create(double value, int digits,
99101
}
100102

101103
@Specialization(guards = "type == NEGATIVE_INFINITY || type == POSITIVE_INFINITY")
102-
public DynamicObject createInfinity(BigDecimalType type, Object digits,
104+
public DynamicObject createInfinity(BigDecimalType type, Object digits, boolean strict,
103105
@Cached("create()") BooleanCastNode booleanCastNode,
104106
@Cached("create()") GetIntegerConstantNode getIntegerConstantNode,
105107
@Cached("createPrivate()") CallDispatchHeadNode modeCallNode,
@@ -120,7 +122,7 @@ public DynamicObject createInfinity(BigDecimalType type, Object digits,
120122
}
121123

122124
@Specialization(guards = "type == NAN")
123-
public DynamicObject createNaN(BigDecimalType type, Object digits,
125+
public DynamicObject createNaN(BigDecimalType type, Object digits, boolean strict,
124126
@Cached("create()") BooleanCastNode booleanCastNode,
125127
@Cached("create()") GetIntegerConstantNode getIntegerConstantNode,
126128
@Cached("createPrivate()") CallDispatchHeadNode modeCallNode,
@@ -141,59 +143,59 @@ public DynamicObject createNaN(BigDecimalType type, Object digits,
141143
}
142144

143145
@Specialization(guards = "type == NEGATIVE_ZERO")
144-
public DynamicObject createNegativeZero(BigDecimalType type, Object digits) {
146+
public DynamicObject createNegativeZero(BigDecimalType type, Object digits, boolean strict) {
145147
return createSpecialBigDecimal(type);
146148
}
147149

148150
@Specialization
149-
public DynamicObject create(BigDecimal value, NotProvided digits) {
150-
return create(value, 0);
151+
public DynamicObject create(BigDecimal value, NotProvided digits, boolean strict) {
152+
return create(value, 0, strict);
151153
}
152154

153155
@Specialization
154-
public DynamicObject create(BigDecimal value, int digits) {
156+
public DynamicObject create(BigDecimal value, int digits, boolean strict) {
155157
return createNormalBigDecimal(round(value, new MathContext(digits, getRoundMode())));
156158
}
157159

158160
@Specialization(guards = "isRubyBignum(value)")
159-
public DynamicObject createBignum(DynamicObject value, NotProvided digits) {
160-
return createBignum(value, 0);
161+
public DynamicObject createBignum(DynamicObject value, NotProvided digits, boolean strict) {
162+
return createBignum(value, 0, strict);
161163
}
162164

163165
@Specialization(guards = "isRubyBignum(value)")
164-
public DynamicObject createBignum(DynamicObject value, int digits) {
166+
public DynamicObject createBignum(DynamicObject value, int digits, boolean strict) {
165167
return createNormalBigDecimal(
166168
round(new BigDecimal(Layouts.BIGNUM.getValue(value)), new MathContext(digits, getRoundMode())));
167169
}
168170

169171
@Specialization(guards = "isRubyBigDecimal(value)")
170-
public DynamicObject createBigDecimal(DynamicObject value, NotProvided digits) {
171-
return createBigDecimal(value, 0);
172+
public DynamicObject createBigDecimal(DynamicObject value, NotProvided digits, boolean strict) {
173+
return createBigDecimal(value, 0, strict);
172174
}
173175

174176
@Specialization(guards = "isRubyBigDecimal(value)")
175-
public DynamicObject createBigDecimal(DynamicObject value, int digits) {
177+
public DynamicObject createBigDecimal(DynamicObject value, int digits, boolean strict) {
176178
return createNormalBigDecimal(
177179
round(Layouts.BIG_DECIMAL.getValue(value), new MathContext(digits, getRoundMode())));
178180
}
179181

180182
@Specialization(guards = "isRubyString(value)")
181-
public DynamicObject createString(DynamicObject value, NotProvided digits) {
182-
return createString(value, 0);
183+
public DynamicObject createString(DynamicObject value, NotProvided digits, boolean strict) {
184+
return createString(value, 0, strict);
183185
}
184186

185187
@TruffleBoundary
186188
@Specialization(guards = "isRubyString(value)")
187-
public DynamicObject createString(DynamicObject value, int digits) {
188-
return executeCreate(getValueFromString(StringOperations.getString(value), digits), digits);
189+
public DynamicObject createString(DynamicObject value, int digits, boolean strict) {
190+
return executeCreate(getValueFromString(StringOperations.getString(value), digits, strict), digits, strict);
189191
}
190192

191193
@Specialization(guards = {
192194
"!isRubyBignum(value)",
193195
"!isRubyBigDecimal(value)",
194196
"!isRubyString(value)"
195197
})
196-
public DynamicObject create(DynamicObject value, int digits,
198+
public DynamicObject create(DynamicObject value, int digits, boolean strict,
197199
@Cached("create()") BigDecimalCastNode bigDecimalCastNode,
198200
@Cached("createBinaryProfile()") ConditionProfile castProfile) {
199201
final Object castedValue = bigDecimalCastNode.execute(value, getRoundMode());
@@ -211,12 +213,14 @@ private BigDecimal round(BigDecimal bigDecimal, MathContext context) {
211213
}
212214

213215
@TruffleBoundary
214-
private Object getValueFromString(String string, int digits) {
215-
String strValue = string.trim();
216+
private Object getValueFromString(String string, int digits, boolean strict) {
217+
String strValue = string;
218+
219+
strValue = strValue.replaceFirst("^\\s+", "");
216220

217221
// TODO (pitr 26-May-2015): create specialization without trims and other cleanups, use rewriteOn
218222

219-
switch (strValue) {
223+
switch (strValue.trim()) {
220224
case "NaN":
221225
return BigDecimalType.NAN;
222226
case "Infinity":
@@ -230,11 +234,33 @@ private Object getValueFromString(String string, int digits) {
230234

231235
// Convert String to Java understandable format (for BigDecimal).
232236
strValue = strValue.replaceFirst("[dD]", "E"); // 1. MRI allows d and D as exponent separators
233-
strValue = strValue.replaceAll("_", ""); // 2. MRI allows underscores anywhere
234237

235-
final Matcher matcher = NUMBER_PATTERN.matcher(strValue);
236-
strValue = matcher.replaceFirst("$1"); // 3. MRI ignores the trailing junk
238+
// 1. MRI allows _ before the decimal place
239+
if (strValue.indexOf('_') != -1) {
240+
final StringBuilder builder = new StringBuilder(strValue.length());
241+
242+
for (int n = 0; n < strValue.length(); n++) {
243+
final char c = strValue.charAt(n);
244+
245+
if (c == '.') {
246+
builder.append(strValue.substring(n));
247+
break;
248+
} else if (c != '_') {
249+
builder.append(c);
250+
}
251+
}
252+
253+
strValue = builder.toString();
254+
}
255+
256+
final Matcher matcher = (strict ? NUMBER_PATTERN_STRICT : NUMBER_PATTERN_NON_STRICT).matcher(strValue);
257+
258+
if (!matcher.matches()) {
259+
throw new RaiseException(getContext(), coreExceptions().argumentErrorInvalidBigDecimal(string, this));
260+
}
261+
237262
final MatchResult result = matcher.toMatchResult();
263+
strValue = matcher.replaceFirst("$1");
238264

239265
try {
240266
final BigDecimal value = new BigDecimal(strValue, new MathContext(digits));
@@ -248,7 +274,8 @@ private Object getValueFromString(String string, int digits) {
248274
return BigDecimal.ZERO;
249275
}
250276

251-
final BigInteger exponent = new BigInteger(result.group(3));
277+
final String exponentPart = result.group(3);
278+
final BigInteger exponent = new BigInteger(exponentPart);
252279

253280
if (exponent.signum() == 1) {
254281
return BigDecimalType.POSITIVE_INFINITY;

0 commit comments

Comments
 (0)