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