/** * Copies all attributes from source to target including 'names', 'dimNames' and 'dim' (unlike * {@link CopyOfRegAttributesNode}), additionally removes the 'dim' from the result if it is not * present in the source. * * <p>TODO: this logic is duplicated in RVector#copyRegAttributesFrom and UnaryMapNode, but behind * TruffleBoundary, does it have a reason for TruffleBoundary? Can we replace it with this node? */ public abstract class UnaryCopyAttributesNode extends RBaseNode { protected final boolean copyAllAttributes; @Child protected HasFixedAttributeNode hasDimNode = HasFixedAttributeNode.createDim(); @Child protected GetDimNamesAttributeNode getDimNamesNode = GetDimNamesAttributeNode.create(); @Child protected GetNamesAttributeNode getNamesNode = GetNamesAttributeNode.create(); protected UnaryCopyAttributesNode(boolean copyAllAttributes) { this.copyAllAttributes = copyAllAttributes; } public static UnaryCopyAttributesNode create() { return UnaryCopyAttributesNodeGen.create(true); } public abstract RAbstractVector execute(RAbstractVector target, RAbstractVector left); protected boolean containsMetadata(RAbstractVector vector) { return vector instanceof RVector && hasDimNode.execute(vector) || (copyAllAttributes && vector.getAttributes() != null) || getNamesNode.getNames(vector) != null || getDimNamesNode.getDimNames(vector) != null; } @SuppressWarnings("unused") @Specialization(guards = "!containsMetadata(source)") protected RAbstractVector copyNoMetadata(RAbstractVector target, RAbstractVector source) { return target; } @SuppressWarnings("unused") @Specialization(guards = {"copyAllAttributes", "target == source"}) protected RAbstractVector copySameVector(RAbstractVector target, RAbstractVector source) { return target; } @Specialization(guards = {"!copyAllAttributes || target != source", "containsMetadata(source)"}) protected RAbstractVector copySameLength( RAbstractVector target, RAbstractVector source, // @Cached("create()") CopyOfRegAttributesNode copyOfReg, // @Cached("createDim()") RemoveFixedAttributeNode removeDim, // @Cached("createDimNames()") RemoveFixedAttributeNode removeDimNames, // @Cached("create()") InitAttributesNode initAttributes, // @Cached("createNames()") SetFixedAttributeNode putNames, // @Cached("createDim()") SetFixedAttributeNode putDim, // @Cached("createDimNames()") SetFixedAttributeNode putDimNames, // @Cached("createBinaryProfile()") ConditionProfile noDimensions, // @Cached("createBinaryProfile()") ConditionProfile hasNamesSource, // @Cached("createBinaryProfile()") ConditionProfile hasDimNames, @Cached("create()") GetDimAttributeNode getDimsNode) { RVector<?> result = target.materialize(); if (copyAllAttributes) { copyOfReg.execute(source, result); } int[] newDimensions = getDimsNode.getDimensions(source); if (noDimensions.profile(newDimensions == null)) { DynamicObject attributes = result.getAttributes(); if (attributes != null) { removeDim.execute(attributes); removeDimNames.execute(attributes); } RStringVector vecNames = getNamesNode.getNames(source); if (hasNamesSource.profile(vecNames != null)) { putNames.execute(initAttributes.execute(result), vecNames); return result; } return result; } putDim.execute( initAttributes.execute(result), RDataFactory.createIntVector(newDimensions, RDataFactory.COMPLETE_VECTOR)); RList newDimNames = getDimNamesNode.getDimNames(source); if (hasDimNames.profile(newDimNames != null)) { putDimNames.execute(result.getAttributes(), newDimNames); newDimNames.elementNamePrefix = RRuntime.DIMNAMES_LIST_ELEMENT_NAME_PREFIX; return result; } return result; } }
/** * The {@code rep} builtin works as follows. * * <ol> * <li>If {@code each} is greater than one, all elements of {@code x} are first replicated {@code * each} times. * <li>If {@code length.out} is given, the result of the first step is truncated or extended as * required. In this case, {@code times} is ignored. * <li>If {@code length.out} is not given, {@code times} is regarded: * <ul> * <li>If {@code times} is a one-element vector, the result of the first step is replicated * {@code times} times. * <li>If {@code times} is a vector longer than one, and {@code each} is greater than one, * an error is issued. * <li>If {@code times} is a vector longer than one, and {@code each} is one, and {@code * times} is as long as {@code x}, each element of {@code x} is given the number of * times indicated by the value at the same index of {@code times}. If {@code times} has * a different length, an error is issued. * </ul> * </ol> */ @RBuiltin( name = "rep", kind = PRIMITIVE, parameterNames = {"x", "times", "length.out", "each"}, dispatch = INTERNAL_GENERIC, behavior = PURE) public abstract class Repeat extends RBuiltinNode { protected abstract Object execute( RAbstractVector x, RAbstractIntVector times, int lengthOut, int each); private final ConditionProfile lengthOutOrTimes = ConditionProfile.createBinaryProfile(); private final BranchProfile errorBranch = BranchProfile.create(); private final ConditionProfile oneTimeGiven = ConditionProfile.createBinaryProfile(); private final ConditionProfile replicateOnce = ConditionProfile.createBinaryProfile(); @Child private GetNamesAttributeNode getNames = GetNamesAttributeNode.create(); @Override public Object[] getDefaultParameterValues() { return new Object[] {RMissing.instance, 1, RRuntime.INT_NA, 1}; } private String argType(Object arg) { return ((RTypedValue) arg).getRType().getName(); } @Override protected void createCasts(CastBuilder casts) { Function<Object, Object> argType = this::argType; casts.arg("x").mustBe(abstractVectorValue(), RError.Message.ATTEMPT_TO_REPLICATE, argType); casts .arg("times") .defaultError(RError.Message.INVALID_ARGUMENT, "times") .mustNotBeNull() .asIntegerVector(); casts .arg("length.out") .mustNotBeNull() .asIntegerVector() .shouldBe(size(1).or(size(0)), RError.Message.FIRST_ELEMENT_USED, "length.out") .findFirst(RRuntime.INT_NA, RError.Message.FIRST_ELEMENT_USED, "length.out") .mustBe(intNA().or(gte(0))); casts .arg("each") .asIntegerVector() .shouldBe(size(1).or(size(0)), RError.Message.FIRST_ELEMENT_USED, "each") .findFirst(1, RError.Message.FIRST_ELEMENT_USED, "each") .notNA(1) .mustBe(gte(0)); } protected boolean hasNames(RAbstractVector x) { return getNames.getNames(x) != null; } private RError invalidTimes() { throw RError.error(this, RError.Message.INVALID_ARGUMENT, "times"); } @Specialization( guards = {"x.getLength() == 1", "times.getLength() == 1", "each <= 1", "!hasNames(x)"}) protected RAbstractVector repNoEachNoNamesSimple( RAbstractDoubleVector x, RAbstractIntVector times, int lengthOut, @SuppressWarnings("unused") int each) { int t = times.getDataAt(0); if (t < 0) { errorBranch.enter(); throw invalidTimes(); } int length = lengthOutOrTimes.profile(!RRuntime.isNA(lengthOut)) ? lengthOut : t; double[] data = new double[length]; Arrays.fill(data, x.getDataAt(0)); return RDataFactory.createDoubleVector(data, !RRuntime.isNA(x.getDataAt(0))); } @Specialization(guards = {"each > 1", "!hasNames(x)"}) protected RAbstractVector repEachNoNames( RAbstractVector x, RAbstractIntVector times, int lengthOut, int each) { if (times.getLength() > 1) { errorBranch.enter(); throw invalidTimes(); } RAbstractVector input = handleEach(x, each); if (lengthOutOrTimes.profile(!RRuntime.isNA(lengthOut))) { return handleLengthOut(input, lengthOut, false); } else { return handleTimes(input, times, false); } } @Specialization(guards = {"each <= 1", "!hasNames(x)"}) protected RAbstractVector repNoEachNoNames( RAbstractVector x, RAbstractIntVector times, int lengthOut, @SuppressWarnings("unused") int each) { if (lengthOutOrTimes.profile(!RRuntime.isNA(lengthOut))) { return handleLengthOut(x, lengthOut, true); } else { return handleTimes(x, times, true); } } @Specialization(guards = {"each > 1", "hasNames(x)"}) protected RAbstractVector repEachNames( RAbstractVector x, RAbstractIntVector times, int lengthOut, int each, @Cached("create()") InitAttributesNode initAttributes, @Cached("createNames()") SetFixedAttributeNode putNames) { if (times.getLength() > 1) { errorBranch.enter(); throw invalidTimes(); } RAbstractVector input = handleEach(x, each); RStringVector names = (RStringVector) handleEach(getNames.getNames(x), each); RVector<?> r; if (lengthOutOrTimes.profile(!RRuntime.isNA(lengthOut))) { names = (RStringVector) handleLengthOut(names, lengthOut, false); r = handleLengthOut(input, lengthOut, false); } else { names = (RStringVector) handleTimes(names, times, false); r = handleTimes(input, times, false); } putNames.execute(initAttributes.execute(r), names); return r; } @Specialization(guards = {"each <= 1", "hasNames(x)"}) protected RAbstractVector repNoEachNames( RAbstractVector x, RAbstractIntVector times, int lengthOut, @SuppressWarnings("unused") int each, @Cached("create()") InitAttributesNode initAttributes, @Cached("createNames()") SetFixedAttributeNode putNames) { RStringVector names; RVector<?> r; if (lengthOutOrTimes.profile(!RRuntime.isNA(lengthOut))) { names = (RStringVector) handleLengthOut(getNames.getNames(x), lengthOut, true); r = handleLengthOut(x, lengthOut, true); } else { names = (RStringVector) handleTimes(getNames.getNames(x), times, true); r = handleTimes(x, times, true); } putNames.execute(initAttributes.execute(r), names); return r; } /** Prepare the input vector by replicating its elements. */ private static RVector<?> handleEach(RAbstractVector x, int each) { RVector<?> r = x.createEmptySameType(x.getLength() * each, x.isComplete()); for (int i = 0; i < x.getLength(); i++) { for (int j = i * each; j < (i + 1) * each; j++) { r.transferElementSameType(j, x, i); } } return r; } /** Extend or truncate the vector to a specified length. */ private static RVector<?> handleLengthOut( RAbstractVector x, int lengthOut, boolean copyIfSameSize) { if (x.getLength() == lengthOut) { return (RVector<?>) (copyIfSameSize ? x.copy() : x); } return x.copyResized(lengthOut, false); } /** Replicate the vector a given number of times. */ private RVector<?> handleTimes( RAbstractVector x, RAbstractIntVector times, boolean copyIfSameSize) { if (oneTimeGiven.profile(times.getLength() == 1)) { // only one times value is given final int howManyTimes = times.getDataAt(0); if (howManyTimes < 0) { errorBranch.enter(); throw invalidTimes(); } if (replicateOnce.profile(howManyTimes == 1)) { return (RVector<?>) (copyIfSameSize ? x.copy() : x); } else { return x.copyResized(x.getLength() * howManyTimes, false); } } else { // times is a vector with several elements if (x.getLength() != times.getLength()) { errorBranch.enter(); invalidTimes(); } // iterate once over the times vector to determine result vector size int resultLength = 0; for (int i = 0; i < times.getLength(); i++) { int t = times.getDataAt(i); if (t < 0) { errorBranch.enter(); throw invalidTimes(); } resultLength += t; } // create and populate result vector RVector<?> r = x.createEmptySameType(resultLength, x.isComplete()); int wp = 0; // write pointer for (int i = 0; i < x.getLength(); i++) { for (int j = 0; j < times.getDataAt(i); ++j, ++wp) { r.transferElementSameType(wp, x, i); } } return r; } } }