From 0bb1bca8ff5f8d801dd44ff94c0abad31190fa58 Mon Sep 17 00:00:00 2001 From: stepan <stepan.sindelar@oracle.com> Date: Tue, 14 Aug 2018 11:48:51 +0200 Subject: [PATCH] Support reading/writing graphical parameters via "par" --- .../r/library/fastrGrid/GridContext.java | 25 ++- .../r/library/fastrGrid/GridState.java | 98 ++++++++++++ .../r/library/fastrGrid/LInitGrid.java | 3 + .../r/library/fastrGrid/graphics/CPar.java | 149 +++++++++++------- .../com/oracle/truffle/r/runtime/RError.java | 4 +- 5 files changed, 214 insertions(+), 65 deletions(-) diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridContext.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridContext.java index 412a8be751..7776efd084 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridContext.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridContext.java @@ -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(); } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java index 8b212ea87b..f999e2f469 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridState.java @@ -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); + } } diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java index 05517de478..44cf75023e 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LInitGrid.java @@ -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); diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/graphics/CPar.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/graphics/CPar.java index 601b7cfe32..3e44b48f27 100644 --- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/graphics/CPar.java +++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/graphics/CPar.java @@ -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; + } } diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java index 96abf2391a..6da3dabd39 100644 --- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java +++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java @@ -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; -- GitLab