package r;
import java.util.regex.*;
import r.data.*;
import r.data.RComplex.Complex;
import r.errors.*;
import r.nodes.ast.*;
// FIXME: will have to support printing doubles to a given number of complex digits (Double.toString uses a different algorithm)
public class Convert {
// NOTE: conversion functions do not clear naIntroduced, they only set it if non-null and NA has been introduced
// indeed this could also be a static field, but hopefully this is faster when no NAs are introduced
// outOfRange is implied by naIntroduced (not set in that case)
public static class ConversionStatus {
public boolean naIntroduced;
public boolean outOfRange; // when converting to raw
public boolean imagDiscarded; // when converting from complex
}
static final Pattern numberSplit = Pattern.compile("^[+-]?[^e+-]+(?:e[+-]?[\\d]+)?");
public static Complex string2complex(String v) {
return string2complex(v, null);
}
// FIXME: this is slow and only an approximation of R semantics
public static Complex string2complex(String v, ConversionStatus warn) {
if (v != RString.NA) {
String input = v.trim();
Matcher m = numberSplit.matcher(input);
if (m.find()) {
int realEnd = m.end();
String sreal = input.substring(m.start(), realEnd);
double real = string2double(sreal, warn);
double imag = 0;
int imagEnd = input.length();
if (realEnd != imagEnd) {
if (input.charAt(imagEnd - 1) == 'i') {
String simag = input.substring(realEnd, imagEnd - 1);
imag = string2double(simag, warn);
} else {
if (warn != null) {
warn.naIntroduced = true;
}
return Complex.NA;
}
}
return new Complex(real, imag);
}
}
if (warn != null) {
warn.naIntroduced = true;
}
return Complex.NA;
}
public static double string2double(String v) {
return string2double(v, null);
}
public static double string2double(String v, ConversionStatus warn) {
if (v != RString.NA) {
// FIXME use R rules
try {
return Double.parseDouble(v);
} catch (NumberFormatException e) {
// FIXME: slow
if (v.equals("Inf") || v.equals("+Inf")) {
return Double.POSITIVE_INFINITY;
}
if (v.equals("-Inf")) {
return Double.NEGATIVE_INFINITY;
}
if (v.startsWith("0x")) {
try {
return int2double(Integer.decode(v));
} catch (NumberFormatException ein) {
}
}
if (warn != null) {
warn.naIntroduced = true;
}
}
}
return RDouble.NA;
}
public static String double2string(double d) {
if (!RDouble.RDoubleUtils.isNA(d)) {
// FIXME use R rules
if (!RContext.debuggingFormat()) {
if (RDouble.RDoubleUtils.fitsRInt(d) && Math.ceil(d) == d) {
return int2string((int) d); // a hack to get rid of ".0" in "1.0"
}
}
return Double.toString(d);
}
return RString.NA;
}
public static int string2int(String s) {
return string2int(s, null);
}
public static int string2int(String s, ConversionStatus warn) {
if (s != RString.NA) {
// FIXME use R rules
try {
return Integer.decode(s); // decode supports hex constants
} catch (NumberFormatException e) {
if (warn != null) {
warn.naIntroduced = true;
}
}
}
return RInt.NA;
}
public static String int2string(int i) {
if (i == RInt.NA) {
return RString.NA;
}
// FIXME use R rules
if (!RContext.debuggingFormat()) {
return Integer.toString(i);
} else {
return Integer.toString(i) + "L";
}
}
public static int string2logical(String s) {
return string2logical(s, null);
}
public static int string2logical(String s, ConversionStatus warn) {
if (s != RString.NA) {
if (s.equals("TRUE") || s.equals("T")) {
return RLogical.TRUE;
}
if (s.equals("FALSE") || s.equals("F")) {
return RLogical.FALSE;
}
if (s.equals("True") || s.equals("true")) {
return RLogical.TRUE;
}
if (s.equals("False") || s.equals("false")) {
return RLogical.FALSE;
}
if (warn != null) {
warn.naIntroduced = true;
}
}
return RLogical.NA;
}
public static String logical2string(int i) {
switch (i) {
case RLogical.FALSE:
return "FALSE";
case RLogical.NA:
return RString.NA;
default:
return "TRUE";
}
}
public static byte string2raw(String s) {
return string2raw(s, null);
}
public static byte string2raw(String s, ConversionStatus warn) {
if (s != RString.NA) {
// FIXME use R rules
int intVal;
int len = s.length();
String str;
if (len > 0 && s.charAt(0) == '0') {
// Java would treat '0' prefix as octal code, but R just ignores it
int nzeros = 1;
while (nzeros < len && s.charAt(nzeros) == '0') {
nzeros++;
}
str = s.substring(nzeros);
} else {
str = s;
}
try {
intVal = Integer.decode(str); // decode supports hex constants
} catch (NumberFormatException e) {
if (warn != null) {
warn.naIntroduced = true;
}
return 0;
}
if (intVal >= 0 && intVal <= 255) {
return (byte) intVal;
}
}
if (warn != null) {
warn.outOfRange = true;
}
return RRaw.ZERO;
}
public static String raw2string(byte v) {
return rawStrings[byteToUnsigned(v)];
}
public static String complex2string(double real, double imag) {
if (!RComplex.RComplexUtils.eitherIsNA(real, imag)) {
String sgn = (imag >= 0 || Double.isNaN(imag)) ? "+" : "";
return double2string(real) + sgn + double2string(imag) + "i"; // FIXME: could elide some NA checks through hand-inlining
}
return RString.NA;
}
public static double complex2double(double real, double imag) {
return complex2double(real, imag, null);
}
public static double complex2double(double real, double imag, ConversionStatus warn) {
if (!RComplex.RComplexUtils.eitherIsNAorNaN(real, imag)) {
if (imag != 0 && warn != null) {
warn.imagDiscarded = true;
}
return real;
}
return RDouble.NA;
}
public static Complex double2complex(double d) {
return RDouble.RDoubleUtils.isNAorNaN(d) ? RComplex.COMPLEX_BOXED_NA : new Complex(d, 0);
}
public static int complex2int(double real, double imag) {
return complex2int(real, imag, null);
}
public static int complex2int(double real, double imag, ConversionStatus warn) {
if (!RComplex.RComplexUtils.eitherIsNAorNaN(real, imag)) {
if (RDouble.RDoubleUtils.fitsRInt(real)) {
if (imag != 0 && warn != null) {
warn.imagDiscarded = true;
}
return (int) real;
} else {
if (warn != null) {
warn.naIntroduced = true;
}
}
}
return RInt.NA;
}
public static Complex int2complex(int i) {
return i == RInt.NA ? RComplex.COMPLEX_BOXED_NA : new Complex(i, 0);
}
public static int complex2logical(double real, double imag) {
if (!RComplex.RComplexUtils.eitherIsNAorNaN(real, imag)) {
boolean v = (real != 0 || imag != 0);
return v ? RLogical.TRUE : RLogical.FALSE;
} else {
return RLogical.NA;
}
}
public static Complex logical2complex(int l) {
return l == RLogical.NA ? RComplex.COMPLEX_BOXED_NA : new Complex(l, 0);
}
public static byte complex2raw(double real, double imag) {
return complex2raw(real, imag, null);
}
public static byte complex2raw(double real, double imag, ConversionStatus warn) {
if (!RComplex.RComplexUtils.eitherIsNAorNaN(real, imag)) {
if (real >= 0 && real < 256) {
if (imag != 0 && warn != null) {
warn.imagDiscarded = true;
}
return (byte) real;
}
if (!RDouble.RDoubleUtils.fitsRInt(real)) {
if (warn != null) {
warn.naIntroduced = true;
// warn.outOfRange = true; -- implied
}
return RRaw.ZERO;
}
// fits an integer, but not raw - out of range
if (imag != 0 && warn != null) {
warn.imagDiscarded = true;
}
}
if (warn != null) {
warn.outOfRange = true;
}
return RRaw.ZERO;
}
public static Complex raw2complex(byte r) {
return new Complex(r, 0);
}
public static int double2int(double d) {
return double2int(d, null);
}
public static int double2int(double d, ConversionStatus warn) {
if (!RDouble.RDoubleUtils.isNAorNaN(d)) {
if (RDouble.RDoubleUtils.fitsRInt(d)) {
return (int) d;
} else {
if (warn != null) {
warn.naIntroduced = true;
}
}
}
return RInt.NA;
}
public static double int2double(int i) {
return i == RInt.NA ? RDouble.NA : i;
}
public static int double2logical(double d) {
if (RDouble.RDoubleUtils.isNAorNaN(d)) {
return RLogical.NA;
}
return d != 0 ? RLogical.TRUE : RLogical.FALSE;
}
public static double logical2double(int l) {
return l == RLogical.NA ? RDouble.NA : l;
}
public static byte double2raw(double d) {
return double2raw(d, null);
}
public static byte double2raw(double d, ConversionStatus warn) {
if (!RDouble.RDoubleUtils.isNAorNaN(d)) {
if (d >= 0 && d < 256) {
return (byte) d;
}
if (!RDouble.RDoubleUtils.fitsRInt(d)) {
if (warn != null) {
warn.naIntroduced = true;
}
return RRaw.ZERO;
}
}
if (warn != null) {
warn.outOfRange = true;
}
return RRaw.ZERO;
}
public static double raw2double(byte v) {
return byteToUnsigned(v);
}
public static int int2logical(int i) {
if (i == RInt.NA) {
return RLogical.NA;
}
return i != 0 ? RLogical.TRUE : RLogical.FALSE;
}
public static int logical2int(int l) {
return l;
}
public static byte int2raw(int v) {
return int2raw(v, null);
}
public static byte int2raw(int v, ConversionStatus warn) {
if (v >= 0 && v < 256) { // note: RInt.NA < 0
return (byte) v;
}
if (warn != null) {
warn.outOfRange = true;
}
return RRaw.ZERO;
}
public static int raw2int(byte v) {
return byteToUnsigned(v);
}
public static byte logical2raw(int v) {
return logical2raw(v, null);
}
public static byte logical2raw(int v, ConversionStatus warn) {
if (v != RLogical.NA) {
return (byte) v;
}
if (warn != null) {
warn.outOfRange = true;
}
return RRaw.ZERO;
}
public static int raw2logical(byte v) {
return byteToUnsigned(v) != 0 ? RLogical.TRUE : RLogical.FALSE;
}
public static String prettyNA(String s) {
if (s != RString.NA) {
return s;
} else {
return "NA";
}
}
public static String prettyGTNALT(String s) {
if (s != RString.NA) {
return s;
} else {
return "<NA>";
}
}
public static void prettyGTNALT(String[] s) { // re-uses array !
for (int i = 0; i < s.length; i++) {
if (s[i] == RString.NA) {
s[i] = "<NA>";
}
}
}
public static final ConversionStatus globalConversionStatus = new ConversionStatus();
public static RString coerceToStringError(RAny arg, ASTNode ast) { // WARNING: non-reentrant
globalConversionStatus.naIntroduced = false;
RString res = arg.asString(globalConversionStatus);
if (!globalConversionStatus.naIntroduced) {
return res;
} else {
throw RError.getCannotCoerce(ast, arg.typeOf(), RString.TYPE_STRING);
}
}
public static RString coerceToStringWarning(RAny arg, ASTNode ast) { // WARNING: non-reentrant
globalConversionStatus.naIntroduced = false;
RString res = arg.asString(globalConversionStatus);
if (!globalConversionStatus.naIntroduced) {
return res;
} else {
RContext.warning(ast, RError.NA_INTRODUCED_COERCION);
return res;
}
}
public static RComplex coerceToComplexWarning(RAny arg, ASTNode ast) { // WARNING: non-reentrant
globalConversionStatus.naIntroduced = false;
RComplex res = arg.asComplex(globalConversionStatus);
if (!globalConversionStatus.naIntroduced) {
return res;
} else {
RContext.warning(ast, RError.NA_INTRODUCED_COERCION);
return res;
}
}
public static RDouble coerceToDoubleWarning(RAny arg, ASTNode ast) { // WARNING: non-reentrant
globalConversionStatus.naIntroduced = false;
RDouble res = arg.asDouble(globalConversionStatus);
if (!globalConversionStatus.naIntroduced) {
return res;
} else {
RContext.warning(ast, RError.NA_INTRODUCED_COERCION);
return res;
}
}
public static RDouble coerceToDoubleError(RAny arg, ASTNode ast) { // WARNING: non-reentrant
globalConversionStatus.naIntroduced = false;
RDouble res = arg.asDouble(globalConversionStatus);
if (!globalConversionStatus.naIntroduced) {
return res;
} else {
throw RError.getCannotCoerce(ast, arg.typeOf(), RDouble.TYPE_STRING);
}
}
public static RInt coerceToIntWarning(RAny arg, ASTNode ast) { // WARNING: non-reentrant
globalConversionStatus.naIntroduced = false;
RInt res = arg.asInt(globalConversionStatus);
if (!globalConversionStatus.naIntroduced) {
return res;
} else {
RContext.warning(ast, RError.NA_INTRODUCED_COERCION);
return res;
}
}
public static RRaw coerceToRawWarning(RAny arg, ASTNode ast) { // WARNING: non-reentrant
globalConversionStatus.naIntroduced = false;
globalConversionStatus.outOfRange = false;
RRaw res = arg.asRaw(globalConversionStatus);
if (!globalConversionStatus.naIntroduced) {
if (!globalConversionStatus.outOfRange) {
// nothing
} else {
RContext.warning(ast, RError.OUT_OF_RANGE);
}
} else {
RContext.warning(ast, RError.NA_INTRODUCED_COERCION);
RContext.warning(ast, RError.OUT_OF_RANGE);
}
return res;
}
public static boolean checkFirstLogical(RAny arg, int value) {
RLogical l = arg.asLogical();
if (l.size() == 0) {
return false;
}
return l.getLogical(0) == value;
}
public static int scalar2int(RAny v) { // FIXME: rewrite to scalar impl types if we have reliable scalarization, or remove
if (v instanceof RInt) {
return ((RInt) v).getInt(0);
}
if (v instanceof RDouble) {
return double2int(((RDouble) v).getDouble(0));
}
if (v instanceof RLogical) {
return logical2int(((RLogical) v).getLogical(0));
}
Utils.nyi("unsupported type");
return -1;
}
public static double scalar2double(RAny v) { // FIXME: rewrite to scalar impl types if we have reliable scalarization, or remove
if (v instanceof RInt) {
return double2int(((RInt) v).getInt(0));
}
if (v instanceof RDouble) {
return ((RDouble) v).getDouble(0);
}
if (v instanceof RLogical) {
return logical2double(((RLogical) v).getLogical(0));
}
Utils.nyi("unsupported type");
return -1;
}
public static String[] symbols2strings(RSymbol[] symbols) {
int size = symbols.length;
String[] res = new String[size];
for (int i = 0; i < size; i++) {
res[i] = symbols[i].name();
}
return res;
}
static final String[] rawStrings = generateRawStrings();
public static String[] generateRawStrings() {
String[] res = new String[256];
for (int i = 0; i < 256; i++) {
res[i] = Integer.toHexString(i / 16) + Integer.toHexString(i % 16);
}
return res;
}
public static int byteToUnsigned(byte v) {
return v & 0xFF;
}
}