Skip to content

Commit

Permalink
Showing 3 changed files with 578 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -110,6 +110,12 @@ public DynamicObject argumentErrorWrongArgumentType(Object object, String expect
return argumentError(String.format("wrong argument type %s (expected %s)", badClassName, expectedType), currentNode);
}

@TruffleBoundary
public DynamicObject argumentErrorInvalidValue(Object object, String expectedType, Node currentNode) {
String badClassName = Layouts.MODULE.getFields(context.getCoreLibrary().getLogicalClass(object)).getName();
return argumentError(String.format("invalid value for %s(): %s", badClassName, expectedType), currentNode);
}

@TruffleBoundary
public DynamicObject argumentError(String message, Node currentNode, Throwable javaThrowable) {
return argumentError(StringOperations.encodeRope(message, UTF8Encoding.INSTANCE), currentNode, javaThrowable);
562 changes: 562 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/string/ConvertBytes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,562 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*
* This is a port of org.jruby.util.ConvertBytes to work with the JRuby+Truffle backend.
* The original class is licensed under the same EPL1.0/GPL 2.0/LGPL 2.1 used throughout.
*/

package org.jruby.truffle.core.string;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.CoreLibrary;
import org.jruby.truffle.core.numeric.FixnumOrBignumNode;
import org.jruby.truffle.core.rope.Rope;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.util.ByteList;

import java.math.BigInteger;
import java.util.Arrays;

public class ConvertBytes {
private final RubyContext context;
private final Node node;
private final FixnumOrBignumNode fixnumOrBignumNode;
private final DynamicObject _str;
private int str;
private int end;
private byte[] data;
private int base;
private final boolean badcheck;

public ConvertBytes(RubyContext context, Node node, FixnumOrBignumNode fixnumOrBignumNode, DynamicObject _str, int base, boolean badcheck) {
final Rope rope = StringOperations.rope(_str);

this.context = context;
this.node = node;
this.fixnumOrBignumNode = fixnumOrBignumNode;
this._str = _str;
this.str = 0;
this.data = rope.getBytes();
this.end = str + rope.byteLength();
this.badcheck = badcheck;
this.base = base;
}

private static final byte[][] MIN_VALUE_BYTES;
static {
MIN_VALUE_BYTES = new byte[37][];
for (int i = 2; i <= 36; i++) {
MIN_VALUE_BYTES[i] = ByteList.plain(Long.toString(Long.MIN_VALUE, i));
}
}

/** rb_cstr_to_inum
*
*/

public static Object byteListToInum19(RubyContext context, Node node, FixnumOrBignumNode fixnumOrBignumNode, DynamicObject str, int base, boolean badcheck) {
return new ConvertBytes(context, node, fixnumOrBignumNode, str, base, badcheck).byteListToInum();
}

private final static byte[] conv_digit = new byte[128];
private final static boolean[] digit = new boolean[128];
private final static boolean[] space = new boolean[128];
private final static boolean[] spaceOrUnderscore = new boolean[128];

static {
Arrays.fill(conv_digit, (byte)-1);
Arrays.fill(digit, false);
for(char c = '0'; c <= '9'; c++) {
conv_digit[c] = (byte)(c - '0');
digit[c] = true;
}

for(char c = 'a'; c <= 'z'; c++) {
conv_digit[c] = (byte)(c - 'a' + 10);
}

for(char c = 'A'; c <= 'Z'; c++) {
conv_digit[c] = (byte)(c - 'A' + 10);
}

Arrays.fill(space, false);
space['\t'] = true;
space['\n'] = true;
space[11] = true; // \v
space['\f'] = true;
space['\r'] = true;
space[' '] = true;

Arrays.fill(spaceOrUnderscore, false);
spaceOrUnderscore['\t'] = true;
spaceOrUnderscore['\n'] = true;
spaceOrUnderscore[11] = true; // \v
spaceOrUnderscore['\f'] = true;
spaceOrUnderscore['\r'] = true;
spaceOrUnderscore[' '] = true;
spaceOrUnderscore['_'] = true;
}

/** conv_digit
*
*/
private byte convertDigit(byte c) {
if(c < 0) {
return -1;
}
return conv_digit[c];
}

/** ISSPACE
*
*/
private boolean isSpace(int str) {
byte c;
if(str == end || (c = data[str]) < 0) {
return false;
}
return space[c];
}

/** ISSPACE || *str == '_'
*
*/
private boolean isSpaceOrUnderscore(int str) {
byte c;
if(str == end || (c = data[str]) < 0) {
return false;
}
return spaceOrUnderscore[c];
}

private boolean getSign() {
boolean sign = true;
if(str < end) {
if(data[str] == '+') {
str++;
} else if(data[str] == '-') {
str++;
sign = false;
}
}

return sign;
}

private void ignoreLeadingWhitespace() {
while(isSpace(str)) {
str++;
}
}

private void figureOutBase() {
if(base <= 0) {
if(str < end && data[str] == '0') {
if(str + 1 < end) {
switch(data[str+1]) {
case 'x':
case 'X':
base = 16;
break;
case 'b':
case 'B':
base = 2;
break;
case 'o':
case 'O':
base = 8;
break;
case 'd':
case 'D':
base = 10;
break;
default:
base = 8;
}
} else {
base = 8;
}
} else if(base < -1) {
base = -base;
} else {
base = 10;
}
}
}

private int calculateLength() {
int len = 0;
byte second = ((str+1 < end) && data[str] == '0') ? data[str+1] : (byte)0;

switch(base) {
case 2:
len = 1;
if(second == 'b' || second == 'B') {
str+=2;
}
break;
case 3:
len = 2;
break;
case 8:
if(second == 'o' || second == 'O') {
str+=2;
}
case 4: case 5: case 6: case 7:
len = 3;
break;
case 10:
if(second == 'd' || second == 'D') {
str+=2;
}
case 9: case 11: case 12:
case 13: case 14: case 15:
len = 4;
break;
case 16:
len = 4;
if(second == 'x' || second == 'X') {
str+=2;
}
break;
default:
if(base < 2 || 36 < base) {
throw new RaiseException(
context.getCoreExceptions().argumentErrorInvalidRadix(base, node));
}
if(base <= 32) {
len = 5;
} else {
len = 6;
}
break;
}

return len;
}

private void squeezeZeroes() {
byte c;
if(str < end && data[str] == '0') {
str++;
int us = 0;
while((str < end) && ((c = data[str]) == '0' || c == '_')) {
if(c == '_') {
if(++us >= 2) {
break;
}
} else {
us += 0;
}
str++;
}
if(str == end || isSpace(str)) {
str--;
}
}
}

private long stringToLong(int nptr, int[] endptr, int base) {
if(base < 0 || base == 1 || base > 36) {
return 0;
}
int save = nptr;
int s = nptr;
boolean overflow = false;

while(isSpace(s)) {
s++;
}

if(s != end) {
boolean negative = false;
if(data[s] == '-') {
negative = true;
s++;
} else if(data[s] == '+') {
negative = false;
s++;
}

save = s;
byte c;
long i = 0;

final long cutoff = Long.MAX_VALUE / (long)base;
final long cutlim = Long.MAX_VALUE % (long)base;

while(s < end) {
c = convertDigit(data[s]);

if(c == -1 || c >= base) {
break;
}
s++;

if(i > cutoff || (i == cutoff && c > cutlim)) {
overflow = true;
} else {
i *= base;
i += c;
}
}

if(s != save) {
if(endptr != null) {
endptr[0] = s;
}

if(overflow) {
throw new ConvertBytes.ERange(negative ? ConvertBytes.ERange.Kind.Underflow : ConvertBytes.ERange.Kind.Overflow);
}

if(negative) {
return -i;
} else {
return i;
}
}
}

if(endptr != null) {
if(save - nptr >= 2 && (data[save-1] == 'x' || data[save-1] == 'X') && data[save-2] == '0') {
endptr[0] = save-1;
} else {
endptr[0] = nptr;
}
}
return 0;
}

@TruffleBoundary
public Object byteListToInum() {
if(_str == null) {
if(badcheck) {
invalidString("Integer");
}
return 0;
}

ignoreLeadingWhitespace();

boolean sign = getSign();

if(str < end) {
if(data[str] == '+' || data[str] == '-') {
if(badcheck) {
invalidString("Integer");
}
return 0;
}
}

figureOutBase();

int len = calculateLength();

squeezeZeroes();

byte c = 0;
if(str < end) {
c = data[str];
}
c = convertDigit(c);
if(c < 0 || c >= base) {
if(badcheck) {
invalidString("Integer");
}
return 0;
}

if (base <= 10) {
len *= (trailingLength());
} else {
len *= (end-str);
}

if(len < Long.SIZE-1) {
int[] endPlace = new int[]{str};
long val = stringToLong(str, endPlace, base);

if(endPlace[0] < end && data[endPlace[0]] == '_') {
return bigParse(len, sign);
}
if(badcheck) {
if(endPlace[0] == str) {
invalidString("Integer"); // no number
}

while(isSpace(endPlace[0])) {
endPlace[0]++;
}

if(endPlace[0] < end) {
invalidString("Integer"); // trailing garbage
}
}

if(sign) {
if (CoreLibrary.fitsIntoInteger(val)) {
return (int) val;
} else {
return val;
}
} else {
if (CoreLibrary.fitsIntoInteger(-val)) {
return (int) -val;
} else {
return -val;
}
}
}
return bigParse(len, sign);
}

private int trailingLength() {
int newLen = 0;
for (int i=str; i < end; i++) {
if (Character.isDigit(data[i])) newLen++;
else return newLen;
}
return newLen;
}

private Object bigParse(int len, boolean sign) {
if(badcheck && str < end && data[str] == '_') {
invalidString("Integer");
}

char[] result = new char[end-str];
int resultIndex = 0;

byte nondigit = -1;

// str2big_scan_digits
{
while(str < end) {
byte c = data[str++];
byte cx = c;
if(c == '_') {
if(nondigit != -1) {
if(badcheck) {
invalidString("Integer");
}
break;
}
nondigit = c;
continue;
} else if((c = convertDigit(c)) < 0) {
break;
}
if(c >= base) {
break;
}
nondigit = -1;
result[resultIndex++] = (char)cx;
}

if(resultIndex == 0) { return 0; }

int tmpStr = str;
if (badcheck) {
// no str-- here because we don't null-terminate strings
if (1 < tmpStr && data[tmpStr-1] == '_') invalidString("Integer");
while (tmpStr < end && Character.isWhitespace(data[tmpStr])) tmpStr++;
if (tmpStr < end) {
invalidString("Integer");
}

}
}

String s = new String(result, 0, resultIndex);
BigInteger z = (base == 10) ? stringToBig(s) : new BigInteger(s, base);
if(!sign) { z = z.negate(); }

if(badcheck) {
if(1 < str && data[str-1] == '_') {
invalidString("Integer");
}
while(str < end && isSpace(str)) {
str++;
}
if(str < end) {
invalidString("Integer");
}
}

return fixnumOrBignumNode.fixnumOrBignum(z);
}

private BigInteger stringToBig(String str) {
str = str.replaceAll("_", "");
int size = str.length();
int nDigits = 512;
if (size < nDigits) { nDigits = size; }

int j = size - 1;
int i = j - nDigits + 1;

BigInteger digits[] = new BigInteger[j / nDigits + 1];

for(int z = 0; j >= 0; z++) {
digits[z] = new BigInteger(str.substring(i, j + 1).trim());
j = i - 1;
i = j - nDigits + 1;
if(i < 0) { i = 0; }
}

BigInteger b10x = BigInteger.TEN.pow(nDigits);
int n = digits.length;
while(n > 1) {
i = 0;
j = 0;
while(i < n / 2) {
digits[i] = digits[j].add(digits[j + 1].multiply(b10x));
i += 1;
j += 2;
}
if(j == n-1) {
digits[i] = digits[j];
i += 1;
}
n = i;
b10x = b10x.multiply(b10x);
}

return digits[0];
}

public static class ERange extends RuntimeException {
public static enum Kind {Overflow, Underflow};
private ConvertBytes.ERange.Kind kind;
public ERange() {
super();
}
public ERange(ConvertBytes.ERange.Kind kind) {
super();
this.kind = kind;
}
public ConvertBytes.ERange.Kind getKind() {
return kind;
}
}

/** rb_invalid_str
*
*/
@TruffleBoundary
private void invalidString(String type) {
throw new RaiseException(
context.getCoreExceptions().argumentErrorInvalidValue(_str, type, node));
}
}
Original file line number Diff line number Diff line change
@@ -112,7 +112,9 @@
import org.jruby.truffle.core.format.unpack.UnpackCompiler;
import org.jruby.truffle.core.kernel.KernelNodes;
import org.jruby.truffle.core.kernel.KernelNodesFactory;
import org.jruby.truffle.core.numeric.FixnumLowerNode;
import org.jruby.truffle.core.numeric.FixnumLowerNodeGen;
import org.jruby.truffle.core.numeric.FixnumOrBignumNode;
import org.jruby.truffle.core.rope.CodeRange;
import org.jruby.truffle.core.rope.ConcatRope;
import org.jruby.truffle.core.rope.LeafRope;
@@ -144,7 +146,6 @@
import org.jruby.truffle.platform.posix.TrufflePosix;
import org.jruby.util.ByteList;
import org.jruby.util.CodeRangeable;
import org.jruby.util.ConvertBytes;
import org.jruby.util.ConvertDouble;
import org.jruby.util.StringSupport;
import java.io.UnsupportedEncodingException;
@@ -4090,34 +4091,14 @@ public static abstract class StringToInumPrimitiveNode extends PrimitiveArrayArg

@TruffleBoundary
@Specialization
public Object stringToInum(DynamicObject string, int fixBase, boolean strict) {
try {
final org.jruby.RubyInteger result = ConvertBytes.byteListToInum19(getContext().getJRubyRuntime(),
StringOperations.getByteListReadOnly(string),
fixBase,
strict);

return toTruffle(result);
} catch (org.jruby.exceptions.RaiseException e) {
throw new RaiseException(getContext().getJRubyInterop().toTruffle(e.getException(), this));
}
}

private Object toTruffle(IRubyObject object) {
if (object instanceof org.jruby.RubyFixnum) {
final long value = ((org.jruby.RubyFixnum) object).getLongValue();

if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
return value;
}

return (int) value;
} else if (object instanceof org.jruby.RubyBignum) {
final BigInteger value = ((org.jruby.RubyBignum) object).getBigIntegerValue();
return createBignum(value);
} else {
throw new UnsupportedOperationException();
}
public Object stringToInum(DynamicObject string, int fixBase, boolean strict,
@Cached("create(getContext(), getSourceSection())") FixnumOrBignumNode fixnumOrBignumNode) {
return ConvertBytes.byteListToInum19(getContext(),
this,
fixnumOrBignumNode,
string,
fixBase,
strict);
}

}

0 comments on commit e591b57

Please sign in to comment.