Skip to content
Snippets Groups Projects
Commit 4e4fc0d0 authored by Stepan Sindelar's avatar Stepan Sindelar
Browse files

[GR-10974] Support reading/writing graphical parameters via "par".

PullRequest: fastr/1653
parents 6c1fd191 236acb81
No related branches found
No related tags found
No related merge requests found
......@@ -70,15 +70,32 @@ public final class GridContext {
devices.add(new DeviceAndState(null, null, null));
}
/**
* Retrieves current {@link GridContext} from given {@link RContext}, if necessary initializes
* the {@link GridContext}. If the {@link RContext} is a child of existing context, the parent
* {@link GridContext} is used to initialize this {@link GridContext}, which is problematic if
* the parent did not load grid package, so this scenario is not supported yet. The grid package
* must be either loaded by the master context or not used.
*/
public static GridContext getContext(RContext rCtx) {
return getContext(rCtx, true);
}
private static GridContext getContext(RContext rCtx, boolean initialize) {
if (rCtx.gridContext == null) {
RContext parentRCtx = rCtx.getParent();
boolean doInitialize = initialize;
if (parentRCtx != null) {
assert parentRCtx != rCtx; // would cause infinite recursion
rCtx.gridContext = new GridContext(getContext(parentRCtx));
// re-initialize local copies of .Device, .Devices etc.
RGridGraphicsAdapter.initialize(rCtx);
} else {
GridContext parentGridCtx = getContext(parentRCtx, false);
if (parentGridCtx != null) {
rCtx.gridContext = new GridContext(parentGridCtx);
// re-initialize local copies of .Device, .Devices etc.
RGridGraphicsAdapter.initialize(rCtx);
doInitialize = false;
}
}
if (doInitialize) {
rCtx.gridContext = new GridContext();
}
}
......
......@@ -19,13 +19,19 @@
*/
package com.oracle.truffle.r.library.fastrGrid;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import com.oracle.truffle.r.library.fastrGrid.device.GridColor;
import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
import com.oracle.truffle.r.library.fastrGrid.grDevices.FileDevUtils;
import com.oracle.truffle.r.runtime.RRuntime;
import com.oracle.truffle.r.runtime.data.RDataFactory;
import com.oracle.truffle.r.runtime.data.RList;
import com.oracle.truffle.r.runtime.data.RNull;
import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
import com.oracle.truffle.r.runtime.env.REnvironment;
......@@ -134,6 +140,14 @@ public final class GridState {
devState.gpar = gpar;
}
public Map<String, Object> getGraphicsPars() {
if (devState.pars == null) {
devState.pars = new HashMap<>();
initGraphicsPars(devState.pars);
}
return devState.pars;
}
/**
* Has the current device been initialized for use by grid? Note: the null device should never
* get initialized. The code initializing device should check if any device is open and if not,
......@@ -200,9 +214,93 @@ public final class GridState {
private int displayListIndex = 0;
private int pageIndex = 2;
private String filenamePattern;
private Map<String, Object> pars;
GridDeviceState(String filenamePattern) {
this.filenamePattern = filenamePattern;
}
}
private static void initGraphicsPars(Map<String, Object> values) {
values.put("xlog", RRuntime.LOGICAL_FALSE);
values.put("ylog", RRuntime.LOGICAL_FALSE);
values.put("adj", doubleVec(0.5));
values.put("ann", RRuntime.LOGICAL_TRUE);
values.put("ask", RRuntime.LOGICAL_FALSE);
values.put("bg", "transparent");
values.put("bty", "o");
values.put("cex", 1.0);
values.put("cex.axis", 1.0);
values.put("cex.lab", 1.0);
values.put("cex.main", 1.2);
values.put("cex.sub", 1.0);
values.put("cin", doubleVec(0.15, 0.2));
values.put("col", "black");
values.put("col.axis", "black");
values.put("col.lab", "black");
values.put("col.main", "black");
values.put("col.sub", "black");
values.put("cra", doubleVec(14.4113475177305, 19.2100840336134));
values.put("crt", 0.0);
values.put("csi", 0.2);
values.put("cxy", doubleVec(0.0260666101091924, 0.0387873111536425));
values.put("din", doubleVec(6.99448818897638, 6.99632545931758));
values.put("err", 0);
values.put("family", "");
values.put("fg", "black");
values.put("fig", doubleVec(0, 1, 0, 1));
values.put("fin", doubleVec(6.99448818897638, 6.99632545931758));
values.put("font", 1);
values.put("font.axis", 1);
values.put("font.lab", 1);
values.put("font.main", 2);
values.put("font.sub", 1);
values.put("lab", intVec(5, 5, 7));
values.put("las", 0);
values.put("lend", "round");
values.put("lheight", 1.0);
values.put("ljoin", "round");
values.put("lmitre", 10.0);
values.put("lty", "solid");
values.put("lwd", 1.0);
values.put("mai", doubleVec(1.02, 0.82, 0.82, 0.42));
values.put("mar", doubleVec(5.1, 4.1, 4.1, 2.1));
values.put("mex", 1.0);
values.put("mfcol", intVec(1, 1));
values.put("mfg", intVec(1, 1, 1, 1));
values.put("mfrow", intVec(1, 1));
values.put("mgp", doubleVec(3, 1, 0));
values.put("mkh", 0.001);
values.put("new", RRuntime.LOGICAL_FALSE);
values.put("oma", doubleVec(0, 0, 0, 0));
values.put("omd", doubleVec(0, 1, 0, 1));
values.put("omi", doubleVec(0, 0, 0, 0));
values.put("page", RRuntime.LOGICAL_TRUE);
values.put("pch", 1);
values.put("pin", doubleVec(5.75448818897638, 5.15632545931759));
values.put("plt", doubleVec(0.117235168298998, 0.939952718676123, 0.145790816326531, 0.882795618247299));
values.put("ps", 12);
values.put("pty", "m");
values.put("smo", 1.0);
values.put("srt", 0.0);
values.put("tck", RRuntime.DOUBLE_NA);
values.put("tcl", doubleVec(-0.5));
values.put("usr", doubleVec(0, 1, 0, 1));
values.put("xaxp", doubleVec(0, 1, 5));
values.put("xaxs", "r");
values.put("xaxt", "s");
values.put("xpd", RRuntime.LOGICAL_FALSE);
values.put("yaxp", doubleVec(0, 1, 5));
values.put("yaxs", "r");
values.put("yaxt", "s");
values.put("ylbias", 0.2);
}
private static RAbstractIntVector intVec(int... values) {
return RDataFactory.createIntVector(values, RDataFactory.COMPLETE_VECTOR);
}
private static RAbstractDoubleVector doubleVec(double... values) {
return RDataFactory.createDoubleVector(values, RDataFactory.COMPLETE_VECTOR);
}
}
......@@ -25,6 +25,9 @@ import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
import com.oracle.truffle.r.runtime.data.RNull;
import com.oracle.truffle.r.runtime.env.REnvironment;
/**
* Gets called when the grid package is loaded.
*/
public abstract class LInitGrid extends RExternalBuiltinNode.Arg1 {
static {
Casts casts = new Casts(LInitGrid.class);
......
......@@ -48,6 +48,10 @@ find.viewport <- function(path, name, strict, currPath, pvp, depth) {
}
}
growPathFixed <- function(pathsofar, name) {
if (is.null(pathsofar)) name else grid:::growPath(pathsofar, name)
}
find.in.children <- function(path, name, strict, currPath, children, depth) {
cpvps <- ls(env=children)
ncpvp <- length(cpvps)
......@@ -55,7 +59,7 @@ find.in.children <- function(path, name, strict, currPath, children, depth) {
found <- FALSE
while (count < ncpvp && !found) {
child <- get(cpvps[count + 1L], env=children)
nextCurrPath <- if (path == 0L) NULL else grid:::growPath(currPath, child$name)
nextCurrPath <- if (path == 0L) NULL else growPathFixed(currPath, child$name)
result <- find.viewport(path, name, strict, nextCurrPath, child, depth)
if (result[[1L]]) {
return(result);
......
......@@ -24,6 +24,8 @@ package com.oracle.truffle.r.library.fastrGrid.graphics;
import static com.oracle.truffle.r.library.fastrGrid.device.DrawingContext.INCH_TO_POINTS_FACTOR;
import java.util.Map;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.VirtualFrame;
......@@ -38,8 +40,6 @@ import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
import com.oracle.truffle.r.nodes.function.call.RExplicitCallNode;
import com.oracle.truffle.r.runtime.ArgumentsSignature;
import com.oracle.truffle.r.runtime.FastROptions;
import com.oracle.truffle.r.runtime.RError;
import com.oracle.truffle.r.runtime.RError.Message;
import com.oracle.truffle.r.runtime.RRuntime;
import com.oracle.truffle.r.runtime.context.RContext;
......@@ -48,8 +48,15 @@ import com.oracle.truffle.r.runtime.data.RDataFactory;
import com.oracle.truffle.r.runtime.data.RFunction;
import com.oracle.truffle.r.runtime.data.RList;
import com.oracle.truffle.r.runtime.data.RNull;
import com.oracle.truffle.r.runtime.data.RStringVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractListVector;
import com.oracle.truffle.r.runtime.env.REnvironment;
/**
* Retrieves and optionally sets the graphical parameters. Some of them are read-only. This function
* has issues with shared search path, see {@link GridContext#getContext()} for details, therefore
* we do not have any unit tests for it for now.
*/
public final class CPar extends RExternalBuiltinNode {
static {
Casts.noCasts(CPar.class);
......@@ -75,55 +82,74 @@ public final class CPar extends RExternalBuiltinNode {
return getPar(args);
}
private void initGridDevice(VirtualFrame frame) {
RContext rCtx = RContext.getInstance();
GridState gridState = GridContext.getContext(rCtx).getGridState();
if (!gridState.isDeviceInitialized()) {
CompilerDirectives.transferToInterpreter();
gridDirty.call(getGridEnv(frame, rCtx).getFrame(), RArgsValuesAndNames.EMPTY);
}
}
private REnvironment getGridEnv(VirtualFrame frame, RContext rCtx) {
REnvironment gridEnv = REnvironment.getRegisteredNamespace(rCtx, "grid");
if (gridEnv != null) {
return gridEnv;
}
// evaluate "library(grid)"
RFunction libFun = (RFunction) readLibraryFun.execute(frame);
ArgumentsSignature libFunSig = ArgumentsSignature.get(null, "character.only");
callNode.call(frame, libFun, new RArgsValuesAndNames(new Object[]{"grid", RRuntime.LOGICAL_TRUE}, libFunSig));
gridEnv = REnvironment.getRegisteredNamespace(rCtx, "grid");
assert gridEnv != null : "grid should have been just loaded";
return gridEnv;
}
@TruffleBoundary
private static Object getPar(RArgsValuesAndNames args) {
GridDevice device = GridContext.getContext().getCurrentDevice();
RList names = RDataFactory.createList(args.getArguments());
private Object getPar(RArgsValuesAndNames args) {
GridContext gridCtx = GridContext.getContext();
GridDevice device = gridCtx.getCurrentDevice();
Map<String, Object> graphicsPars = gridCtx.getGridState().getGraphicsPars();
String[] names = null;
RAbstractListVector values = null;
// unwrap list if it is the first argument
if (names.getLength() == 1) {
if (args.getLength() == 1 && args.getSignature().getName(0) == null) {
Object first = args.getArgument(0);
if (first instanceof RList) {
names = (RList) first;
values = (RList) first;
RStringVector namesVec = values.getNames();
names = namesVec == null ? null : namesVec.getReadonlyStringData();
}
}
if (values == null) {
names = args.getSignature().getNames();
values = RDataFactory.createList(args.getArguments());
}
Object[] result = new Object[names.getLength()];
String[] resultNames = new String[names.getLength()];
for (int i = 0; i < names.getLength(); i++) {
resultNames[i] = RRuntime.asString(names.getDataAt(i));
result[i] = getParam(resultNames[i], device);
Object[] result = new Object[values.getLength()];
String[] resultNames = new String[values.getLength()];
for (int i = 0; i < values.getLength(); i++) {
String paramName = names == null ? null : names[i];
Object newValue = null;
if (paramName == null) {
// the value of the parameter itself is the name and we are only getting the value
// of the graphical par
paramName = RRuntime.asString(values.getDataAt(i));
if (paramName == null) {
// if the name is not String, GNU-R just puts NULL into the result
result[i] = RNull.instance;
resultNames[i] = "";
continue;
}
} else {
newValue = values.getDataAt(i);
}
resultNames[i] = paramName;
result[i] = handleParam(paramName, newValue, graphicsPars, device);
}
return RDataFactory.createList(result, RDataFactory.createStringVector(resultNames, RDataFactory.COMPLETE_VECTOR));
}
private static Object getParam(String name, GridDevice device) {
if (name == null) {
// TODO: a hot-fix to enable package tests (e.g. cluster)
return RNull.instance;
private Object handleParam(String name, Object newValue, Map<String, Object> graphicsPars, GridDevice device) {
// Note: some parameters which are readonly in GNU-R are writeable in FastR
Object result = getComputedParam(name, device);
if (result != null && newValue != null) {
throw error(Message.GRAPHICAL_PAR_CANNOT_BE_SET, name);
}
if (result == null) {
result = graphicsPars.get(name);
if (result == null) {
throw error(Message.IS_NOT_GRAPHICAL_PAR, name);
}
// TODO: we are not validating and coercing the values, e.g. "oma" should be integer
// vector of size 4. This can be maybe based on the previous value: the type and size
// must match
if (newValue != null) {
graphicsPars.put(name, newValue);
}
}
return result;
}
private static Object getComputedParam(String name, GridDevice device) {
switch (name) {
case "din":
return RDataFactory.createDoubleVector(new double[]{device.getWidth(), device.getHeight()}, RDataFactory.COMPLETE_VECTOR);
......@@ -146,32 +172,35 @@ public final class CPar extends RExternalBuiltinNode {
*/
double cra = getCurrentDrawingContext().getFontSize();
return RDataFactory.createDoubleVector(new double[]{cra, cra}, RDataFactory.COMPLETE_VECTOR);
case "usr":
// TODO:
return RDataFactory.createDoubleVector(new double[]{0, 1, 0, 1}, RDataFactory.COMPLETE_VECTOR);
case "xlog":
// TODO:
return RDataFactory.createLogicalVectorFromScalar(false);
case "ylog":
// TODO:
return RDataFactory.createLogicalVectorFromScalar(false);
case "page":
// TODO:
return RDataFactory.createLogicalVectorFromScalar(false);
case "xaxp":
case "yaxp":
// TODO:
return RDataFactory.createDoubleVector(new double[]{0., 1., 5.}, RDataFactory.COMPLETE_VECTOR);
default:
if (!FastROptions.IgnoreGraphicsCalls.getBooleanValue()) {
throw RError.nyi(RError.NO_CALLER, "C_Par parameter '" + name + "'");
} else {
return RNull.instance;
}
return null;
}
}
private static DrawingContext getCurrentDrawingContext() {
return GPar.create(GridContext.getContext().getGridState().getGpar()).getDrawingContext(0);
}
private void initGridDevice(VirtualFrame frame) {
RContext rCtx = RContext.getInstance();
GridState gridState = GridContext.getContext(rCtx).getGridState();
if (!gridState.isDeviceInitialized()) {
CompilerDirectives.transferToInterpreter();
gridDirty.call(getGridEnv(frame, rCtx).getFrame(), RArgsValuesAndNames.EMPTY);
}
}
private REnvironment getGridEnv(VirtualFrame frame, RContext rCtx) {
REnvironment gridEnv = REnvironment.getRegisteredNamespace(rCtx, "grid");
if (gridEnv != null) {
return gridEnv;
}
// evaluate "library(grid)"
RFunction libFun = (RFunction) readLibraryFun.execute(frame);
ArgumentsSignature libFunSig = ArgumentsSignature.get(null, "character.only");
callNode.call(frame, libFun, new RArgsValuesAndNames(new Object[]{"grid", RRuntime.LOGICAL_TRUE}, libFunSig));
gridEnv = REnvironment.getRegisteredNamespace(rCtx, "grid");
assert gridEnv != null : "grid should have been just loaded";
return gridEnv;
}
}
......@@ -984,7 +984,9 @@ public final class RError extends RuntimeException implements TruffleException {
NA_INF_REPLACED("-Inf replaced by maximally negative value"),
MINUS_INF_REPLACED("NA/Inf replaced by maximum positive value"),
INVALID_FUNCTION_VALUE("invalid function value in '%s'"),
LINE_MALFORMED("Line starting '%s ...' is malformed!");
LINE_MALFORMED("Line starting '%s ...' is malformed!"),
IS_NOT_GRAPHICAL_PAR("\"%s\" is not a graphical parameter"),
GRAPHICAL_PAR_CANNOT_BE_SET("graphical parameter \"%s\" cannot be set");
public final String message;
final boolean hasArgs;
......
......@@ -7,7 +7,7 @@ suite = {
{
"name" : "truffle",
"subdir" : True,
"version" : "ddbf8fef5b00fe4344d6f2e260c590622b2030bd",
"version" : "37d8e6b1807043e90499ecbdc9c286233058ff14",
"urls" : [
{"url" : "https://github.com/graalvm/graal", "kind" : "git"},
{"url" : "https://curio.ssw.jku.at/nexus/content/repositories/snapshots", "kind" : "binary"},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment