|
66 | 66 | import static org.truffleruby.core.rope.CodeRange.CR_BROKEN;
|
67 | 67 | import static org.truffleruby.core.rope.CodeRange.CR_UNKNOWN;
|
68 | 68 | import static org.truffleruby.core.rope.RopeConstants.EMPTY_ASCII_8BIT_ROPE;
|
| 69 | +import static org.truffleruby.core.string.StringOperations.createString; |
69 | 70 | import static org.truffleruby.core.string.StringOperations.encoding;
|
70 | 71 | import static org.truffleruby.core.string.StringOperations.rope;
|
71 | 72 | import static org.truffleruby.core.string.StringSupport.MBCLEN_CHARFOUND_LEN;
|
@@ -449,25 +450,69 @@ protected int compare(DynamicObject a, DynamicObject b,
|
449 | 450 |
|
450 | 451 | }
|
451 | 452 |
|
452 |
| - @CoreMethod(names = { "<<", "concat" }, required = 1, taintFrom = 1, raiseIfFrozenSelf = true) |
| 453 | + @CoreMethod(names = { "<<", "concat" }, optional = 1, rest = true, taintFrom = 1, raiseIfFrozenSelf = true) |
453 | 454 | @ImportStatic(StringGuards.class)
|
454 | 455 | public abstract static class ConcatNode extends CoreMethodArrayArgumentsNode {
|
455 | 456 |
|
456 |
| - @Specialization(guards = "isRubyString(other)") |
457 |
| - protected DynamicObject concat(DynamicObject string, DynamicObject other, |
| 457 | + public static ConcatNode create() { |
| 458 | + return StringNodesFactory.ConcatNodeFactory.create(null); |
| 459 | + } |
| 460 | + |
| 461 | + public abstract Object executeConcat(DynamicObject string, Object first, Object[] rest); |
| 462 | + |
| 463 | + @Specialization(guards = "rest.length == 0") |
| 464 | + protected DynamicObject concatZero(DynamicObject string, NotProvided first, Object[] rest) { |
| 465 | + return string; |
| 466 | + } |
| 467 | + |
| 468 | + @Specialization(guards = { "rest.length == 0", "isRubyString(first)" }) |
| 469 | + protected DynamicObject concat(DynamicObject string, DynamicObject first, Object[] rest, |
458 | 470 | @Cached StringAppendPrimitiveNode stringAppendNode) {
|
459 |
| - return stringAppendNode.executeStringAppend(string, other); |
| 471 | + return stringAppendNode.executeStringAppend(string, first); |
460 | 472 | }
|
461 | 473 |
|
462 |
| - @Specialization(guards = "!isRubyString(other)") |
463 |
| - protected Object concatGeneric( |
464 |
| - VirtualFrame frame, |
465 |
| - DynamicObject string, |
466 |
| - Object other, |
| 474 | + @Specialization(guards = { "rest.length == 0", "wasProvided(first)", "!isRubyString(first)" }) |
| 475 | + protected Object concatGeneric(DynamicObject string, Object first, Object[] rest, |
467 | 476 | @Cached("createPrivate()") CallDispatchHeadNode callNode) {
|
468 |
| - return callNode.call(string, "concat_internal", other); |
| 477 | + return callNode.call(string, "concat_internal", first); |
469 | 478 | }
|
470 | 479 |
|
| 480 | + @ExplodeLoop |
| 481 | + @Specialization(guards = { "wasProvided(first)", "rest.length > 0", "rest.length == cachedLength" }) |
| 482 | + protected Object concatMany(DynamicObject string, Object first, Object[] rest, |
| 483 | + @Cached("rest.length") int cachedLength, |
| 484 | + @Cached ConcatNode argConcatNode, |
| 485 | + @Cached("createBinaryProfile()") ConditionProfile selfArgProfile) { |
| 486 | + Rope rope = StringOperations.rope(string); |
| 487 | + Object result = argConcatNode.executeConcat(string, first, EMPTY_ARGUMENTS); |
| 488 | + for (int i = 0; i < cachedLength; ++i) { |
| 489 | + if (selfArgProfile.profile(rest[i] == string)) { |
| 490 | + Object copy = createString(getContext(), rope); |
| 491 | + result = argConcatNode.executeConcat(string, copy, EMPTY_ARGUMENTS); |
| 492 | + } else { |
| 493 | + result = argConcatNode.executeConcat(string, rest[i], EMPTY_ARGUMENTS); |
| 494 | + } |
| 495 | + } |
| 496 | + return result; |
| 497 | + } |
| 498 | + |
| 499 | + /** Same implementation as {@link #concatMany}, safe for the use of {@code cachedLength} */ |
| 500 | + @Specialization(guards = { "wasProvided(first)", "rest.length > 0" }, replaces = "concatMany") |
| 501 | + protected Object concatManyGeneral(DynamicObject string, Object first, Object[] rest, |
| 502 | + @Cached ConcatNode argConcatNode, |
| 503 | + @Cached("createBinaryProfile()") ConditionProfile selfArgProfile) { |
| 504 | + Rope rope = StringOperations.rope(string); |
| 505 | + Object result = argConcatNode.executeConcat(string, first, EMPTY_ARGUMENTS); |
| 506 | + for (Object arg : rest) { |
| 507 | + if (selfArgProfile.profile(arg == string)) { |
| 508 | + Object copy = createString(getContext(), rope); |
| 509 | + result = argConcatNode.executeConcat(string, copy, EMPTY_ARGUMENTS); |
| 510 | + } else { |
| 511 | + result = argConcatNode.executeConcat(string, arg, EMPTY_ARGUMENTS); |
| 512 | + } |
| 513 | + } |
| 514 | + return result; |
| 515 | + } |
471 | 516 | }
|
472 | 517 |
|
473 | 518 | @CoreMethod(
|
|
0 commit comments