36
36
37
37
@ NodeChild (value = "value" , type = RubyNode .class )
38
38
@ NodeChild (value = "digits" , type = RubyNode .class )
39
+ @ NodeChild (value = "strict" , type = RubyNode .class )
39
40
@ ImportStatic (BigDecimalType .class )
40
41
public abstract class CreateBigDecimalNode extends BigDecimalCoreMethodNode {
41
42
42
43
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 + "$" );
45
47
46
48
@ Child private AllocateObjectNode allocateNode = AllocateObjectNode .create ();
47
49
48
- public abstract DynamicObject executeCreate (Object value , Object digits );
50
+ public abstract DynamicObject executeCreate (Object value , Object digits , boolean strict );
49
51
50
52
private DynamicObject createNormalBigDecimal (BigDecimal value ) {
51
53
return allocateNode .allocate (getBigDecimalClass (), Layouts .BIG_DECIMAL .build (value , BigDecimalType .NORMAL ));
@@ -56,20 +58,20 @@ private DynamicObject createSpecialBigDecimal(BigDecimalType type) {
56
58
}
57
59
58
60
@ 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 );
61
63
}
62
64
63
65
@ Specialization
64
- public DynamicObject create (long value , int digits ,
66
+ public DynamicObject create (long value , int digits , boolean strict ,
65
67
@ Cached ("create()" ) BigDecimalCastNode bigDecimalCastNode ) {
66
68
BigDecimal bigDecimal = round ((BigDecimal ) bigDecimalCastNode .execute (value , getRoundMode ()),
67
69
new MathContext (digits , getRoundMode ()));
68
70
return createNormalBigDecimal (bigDecimal );
69
71
}
70
72
71
73
@ Specialization
72
- public DynamicObject create (double value , NotProvided digits ,
74
+ public DynamicObject create (double value , NotProvided digits , boolean strict ,
73
75
@ Cached ("createBinaryProfile()" ) ConditionProfile finiteValueProfile ,
74
76
@ Cached ("create()" ) BranchProfile nanProfile ,
75
77
@ Cached ("create()" ) BranchProfile positiveInfinityProfile ,
@@ -82,7 +84,7 @@ public DynamicObject create(double value, NotProvided digits,
82
84
}
83
85
84
86
@ Specialization
85
- public DynamicObject create (double value , int digits ,
87
+ public DynamicObject create (double value , int digits , boolean strict ,
86
88
@ Cached ("create()" ) BigDecimalCastNode bigDecimalCastNode ,
87
89
@ Cached ("createBinaryProfile()" ) ConditionProfile finiteValueProfile ,
88
90
@ Cached ("create()" ) BranchProfile nanProfile ,
@@ -99,7 +101,7 @@ public DynamicObject create(double value, int digits,
99
101
}
100
102
101
103
@ 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 ,
103
105
@ Cached ("create()" ) BooleanCastNode booleanCastNode ,
104
106
@ Cached ("create()" ) GetIntegerConstantNode getIntegerConstantNode ,
105
107
@ Cached ("createPrivate()" ) CallDispatchHeadNode modeCallNode ,
@@ -120,7 +122,7 @@ public DynamicObject createInfinity(BigDecimalType type, Object digits,
120
122
}
121
123
122
124
@ Specialization (guards = "type == NAN" )
123
- public DynamicObject createNaN (BigDecimalType type , Object digits ,
125
+ public DynamicObject createNaN (BigDecimalType type , Object digits , boolean strict ,
124
126
@ Cached ("create()" ) BooleanCastNode booleanCastNode ,
125
127
@ Cached ("create()" ) GetIntegerConstantNode getIntegerConstantNode ,
126
128
@ Cached ("createPrivate()" ) CallDispatchHeadNode modeCallNode ,
@@ -141,59 +143,59 @@ public DynamicObject createNaN(BigDecimalType type, Object digits,
141
143
}
142
144
143
145
@ Specialization (guards = "type == NEGATIVE_ZERO" )
144
- public DynamicObject createNegativeZero (BigDecimalType type , Object digits ) {
146
+ public DynamicObject createNegativeZero (BigDecimalType type , Object digits , boolean strict ) {
145
147
return createSpecialBigDecimal (type );
146
148
}
147
149
148
150
@ 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 );
151
153
}
152
154
153
155
@ Specialization
154
- public DynamicObject create (BigDecimal value , int digits ) {
156
+ public DynamicObject create (BigDecimal value , int digits , boolean strict ) {
155
157
return createNormalBigDecimal (round (value , new MathContext (digits , getRoundMode ())));
156
158
}
157
159
158
160
@ 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 );
161
163
}
162
164
163
165
@ Specialization (guards = "isRubyBignum(value)" )
164
- public DynamicObject createBignum (DynamicObject value , int digits ) {
166
+ public DynamicObject createBignum (DynamicObject value , int digits , boolean strict ) {
165
167
return createNormalBigDecimal (
166
168
round (new BigDecimal (Layouts .BIGNUM .getValue (value )), new MathContext (digits , getRoundMode ())));
167
169
}
168
170
169
171
@ 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 );
172
174
}
173
175
174
176
@ Specialization (guards = "isRubyBigDecimal(value)" )
175
- public DynamicObject createBigDecimal (DynamicObject value , int digits ) {
177
+ public DynamicObject createBigDecimal (DynamicObject value , int digits , boolean strict ) {
176
178
return createNormalBigDecimal (
177
179
round (Layouts .BIG_DECIMAL .getValue (value ), new MathContext (digits , getRoundMode ())));
178
180
}
179
181
180
182
@ 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 );
183
185
}
184
186
185
187
@ TruffleBoundary
186
188
@ 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 );
189
191
}
190
192
191
193
@ Specialization (guards = {
192
194
"!isRubyBignum(value)" ,
193
195
"!isRubyBigDecimal(value)" ,
194
196
"!isRubyString(value)"
195
197
})
196
- public DynamicObject create (DynamicObject value , int digits ,
198
+ public DynamicObject create (DynamicObject value , int digits , boolean strict ,
197
199
@ Cached ("create()" ) BigDecimalCastNode bigDecimalCastNode ,
198
200
@ Cached ("createBinaryProfile()" ) ConditionProfile castProfile ) {
199
201
final Object castedValue = bigDecimalCastNode .execute (value , getRoundMode ());
@@ -211,12 +213,14 @@ private BigDecimal round(BigDecimal bigDecimal, MathContext context) {
211
213
}
212
214
213
215
@ 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+" , "" );
216
220
217
221
// TODO (pitr 26-May-2015): create specialization without trims and other cleanups, use rewriteOn
218
222
219
- switch (strValue ) {
223
+ switch (strValue . trim () ) {
220
224
case "NaN" :
221
225
return BigDecimalType .NAN ;
222
226
case "Infinity" :
@@ -230,11 +234,33 @@ private Object getValueFromString(String string, int digits) {
230
234
231
235
// Convert String to Java understandable format (for BigDecimal).
232
236
strValue = strValue .replaceFirst ("[dD]" , "E" ); // 1. MRI allows d and D as exponent separators
233
- strValue = strValue .replaceAll ("_" , "" ); // 2. MRI allows underscores anywhere
234
237
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
+
237
262
final MatchResult result = matcher .toMatchResult ();
263
+ strValue = matcher .replaceFirst ("$1" );
238
264
239
265
try {
240
266
final BigDecimal value = new BigDecimal (strValue , new MathContext (digits ));
@@ -248,7 +274,8 @@ private Object getValueFromString(String string, int digits) {
248
274
return BigDecimal .ZERO ;
249
275
}
250
276
251
- final BigInteger exponent = new BigInteger (result .group (3 ));
277
+ final String exponentPart = result .group (3 );
278
+ final BigInteger exponent = new BigInteger (exponentPart );
252
279
253
280
if (exponent .signum () == 1 ) {
254
281
return BigDecimalType .POSITIVE_INFINITY ;
0 commit comments