Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: aec88447efc9
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: a55543bfb106
Choose a head ref
  • 4 commits
  • 4 files changed
  • 1 contributor

Commits on Dec 24, 2014

  1. Copy the full SHA
    7ad68ac View commit details
  2. Copy the full SHA
    e475681 View commit details
  3. Copy the full SHA
    afc4edd View commit details
  4. Copy the full SHA
    a55543b View commit details
20 changes: 20 additions & 0 deletions core/src/main/java/org/jruby/truffle/nodes/core/RegexpNodes.java
Original file line number Diff line number Diff line change
@@ -43,6 +43,26 @@ protected RubyString escape(VirtualFrame frame, RubyString string) {
}
}

public abstract static class EscapingYieldingNode extends YieldingCoreMethodNode {
@Child protected EscapeNode escapeNode;

public EscapingYieldingNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public EscapingYieldingNode(EscapingYieldingNode prev) {
super(prev);
}

protected RubyString escape(VirtualFrame frame, RubyString string) {
if (escapeNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
escapeNode = insert(RegexpNodesFactory.EscapeNodeFactory.create(getContext(), getSourceSection(), new RubyNode[]{null}));
}
return escapeNode.executeEscape(frame, string);
}
}

@CoreMethod(names = "==", required = 1)
public abstract static class EqualNode extends CoreMethodNode {

93 changes: 88 additions & 5 deletions core/src/main/java/org/jruby/truffle/nodes/core/StringNodes.java
Original file line number Diff line number Diff line change
@@ -15,8 +15,11 @@
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.utilities.BranchProfile;
import org.jcodings.Encoding;
import org.jcodings.specific.ASCIIEncoding;
import org.joni.Matcher;
import org.joni.Option;
import org.joni.Region;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.runtime.Visibility;
@@ -27,8 +30,11 @@
import org.jruby.truffle.runtime.core.RubyRange;
import org.jruby.util.ByteList;
import org.jruby.util.Pack;
import org.jruby.util.StringSupport;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -588,31 +594,108 @@ public RubyString forceEncoding(RubyString string, RubyEncoding encoding) {

}

@CoreMethod(names = "gsub", required = 2)
public abstract static class GsubNode extends RegexpNodes.EscapingNode {
@CoreMethod(names = "gsub", required = 1, optional = 1, needsBlock = true)
public abstract static class GsubNode extends RegexpNodes.EscapingYieldingNode {

@Child protected DispatchHeadNode toS;

public GsubNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
toS = new DispatchHeadNode(context);
}

public GsubNode(GsubNode prev) {
super(prev);
toS = prev.toS;
}

@Specialization
public RubyString gsub(VirtualFrame frame, RubyString string, RubyString regexpString, RubyString replacement) {
public RubyString gsub(VirtualFrame frame, RubyString string, RubyString regexpString, RubyString replacement, UndefinedPlaceholder block) {
notDesignedForCompilation();

final RubyRegexp regexp = new RubyRegexp(this, getContext().getCoreLibrary().getRegexpClass(), escape(frame, regexpString).getBytes(), Option.DEFAULT);
return gsub(string, regexp, replacement);
return gsub(string, regexp, replacement, block);
}

@Specialization
public RubyString gsub(RubyString string, RubyRegexp regexp, RubyString replacement) {
public RubyString gsub(RubyString string, RubyRegexp regexp, RubyString replacement, @SuppressWarnings("unused") UndefinedPlaceholder block) {
notDesignedForCompilation();

return regexp.gsub(string, replacement.toString());
}

@Specialization
public RubyString gsub(VirtualFrame frame, RubyString string, RubyString regexpString, RubyString replacement, RubyProc block) {
notDesignedForCompilation();

final RubyRegexp regexp = new RubyRegexp(this, getContext().getCoreLibrary().getRegexpClass(), escape(frame, regexpString).getBytes(), Option.DEFAULT);
return gsub(string, regexp, replacement, block);
}

@Specialization
public RubyString gsub(RubyString string, RubyRegexp regexp, RubyString replacement, @SuppressWarnings("unused") RubyProc block) {
notDesignedForCompilation();

return regexp.gsub(string, replacement.toString());
}

@Specialization
public RubyString gsub(VirtualFrame frame, RubyString string, RubyString regexpString, @SuppressWarnings("unused") UndefinedPlaceholder replacement, RubyProc block) {
notDesignedForCompilation();

final RubyRegexp regexp = new RubyRegexp(this, getContext().getCoreLibrary().getRegexpClass(), escape(frame, regexpString).getBytes(), Option.DEFAULT);
return gsub(frame, string, regexp, replacement, block);
}

@Specialization
public RubyString gsub(VirtualFrame frame, RubyString string, RubyRegexp regexp, @SuppressWarnings("unused") UndefinedPlaceholder replacement, RubyProc block) {
notDesignedForCompilation();

final RubyContext context = getContext();

final byte[] stringBytes = string.getBytes().bytes();
final Encoding encoding = string.getBytes().getEncoding();
final Matcher matcher = regexp.getRegex().matcher(stringBytes);

int p = string.getBytes().getBegin();
int end = 0;
int range = p + string.getBytes().getRealSize();
int lastMatchEnd = 0;

// We only ever care about the entire matched string, not each of the matched parts, so we can hard-code the index.
int matchedStringIndex = 0;

final StringBuilder builder = new StringBuilder();

while (true) {
Object matchData = regexp.matchCommon(string.getBytes(), false, true, matcher, p + end, range);

if (matchData == context.getCoreLibrary().getNilObject()) {
builder.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(stringBytes, lastMatchEnd, range - lastMatchEnd)));

break;
}

Region region = matcher.getEagerRegion();

RubyMatchData md = (RubyMatchData) matchData;
Object[] values = md.getValues();

int regionStart = region.beg[matchedStringIndex];
int regionEnd = region.end[matchedStringIndex];

// TODO (nirvdrum Dec. 24, 2014): There's probably a better way of doing this than converting back and forth between String and RubyString.
builder.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(stringBytes, lastMatchEnd, regionStart - lastMatchEnd)));

Object yieldResult = yield(frame, block, values[matchedStringIndex]);
builder.append(toS.call(frame, yieldResult, "to_s", null).toString());

lastMatchEnd = regionEnd;
end = StringSupport.positionEndForScan(string.getBytes(), matcher, encoding, p, range);
}

return context.makeString(builder.toString());
}
}

@CoreMethod(names = "getbyte", required = 1)
Original file line number Diff line number Diff line change
@@ -264,6 +264,7 @@ public RubyString gsub(RubyString string, String replacement) {
final RubyContext context = getContext();

final byte[] stringBytes = string.getBytes().bytes();

final Encoding encoding = string.getBytes().getEncoding();
final Matcher matcher = regex.matcher(stringBytes);

@@ -272,6 +273,9 @@ public RubyString gsub(RubyString string, String replacement) {
int range = p + string.getBytes().getRealSize();
int lastMatchEnd = 0;

// We only ever care about the entire matched string, not each of the matched parts, so we can hard-code the index.
int matchedStringIndex = 0;

final StringBuilder builder = new StringBuilder();

while (true) {
@@ -285,16 +289,13 @@ public RubyString gsub(RubyString string, String replacement) {

Region region = matcher.getEagerRegion();

for (int i = 0; i < region.numRegs; i++) {
int regionStart = region.beg[i];
int regionEnd = region.end[i];

builder.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(stringBytes, lastMatchEnd, regionStart - lastMatchEnd)));
builder.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(replacement.getBytes(StandardCharsets.UTF_8))));
int regionStart = region.beg[matchedStringIndex];
int regionEnd = region.end[matchedStringIndex];

lastMatchEnd = regionEnd;
}
builder.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(stringBytes, lastMatchEnd, regionStart - lastMatchEnd)));
builder.append(StandardCharsets.UTF_8.decode(ByteBuffer.wrap(replacement.getBytes(StandardCharsets.UTF_8))));

lastMatchEnd = regionEnd;
end = StringSupport.positionEndForScan(string.getBytes(), matcher, encoding, p, range);
}

3 changes: 1 addition & 2 deletions spec/truffle/tags/core/string/gsub_tags.txt
Original file line number Diff line number Diff line change
@@ -47,8 +47,7 @@ fails:String#gsub with pattern and block returns a copy of self with all occurre
fails:String#gsub with pattern and block sets $~ for access from the block
fails:String#gsub with pattern and block restores $~ after leaving the block
fails:String#gsub with pattern and block sets $~ to MatchData of last match and nil when there's none for access from outside
fails:String#gsub with pattern and block doesn't interpolate special sequences like \1 for the block's return value
fails:String#gsub with pattern and block converts the block's return value to a string using to_s
passes:String#gsub with pattern and block converts the block's return value to a string using to_s
fails:String#gsub with pattern and block untrusts the result if the original string or replacement is untrusted
fails:String#gsub with pattern and block uses the compatible encoding if they are compatible
fails:String#gsub with pattern and block raises an Encoding::CompatibilityError if the encodings are not compatible