Skip to content
Snippets Groups Projects
Commit 2f38c61b authored by Lukas Stadler's avatar Lukas Stadler
Browse files

implement "diag" builtin correctly

parent 4876d418
Branches
No related tags found
No related merge requests found
/*
* Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This material is distributed under the GNU General Public License
* Version 2. You may review the terms of this license at
* http://www.gnu.org/licenses/gpl-2.0.html
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
* Copyright (c) 1995, 1996, 1997 Robert Gentleman and Ross Ihaka
* Copyright (c) 1998-2013, The R Core Team
* Copyright (c) 2003-2015, The R Foundation
* Copyright (c) 2013, 2016, Oracle and/or its affiliates
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
* All rights reserved.
*/
package com.oracle.truffle.r.nodes.builtin.base;
import static com.oracle.truffle.r.runtime.RBuiltinKind.*;
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.r.nodes.*;
import com.oracle.truffle.r.nodes.access.*;
import com.oracle.truffle.r.nodes.builtin.*;
import com.oracle.truffle.r.nodes.unary.*;
import com.oracle.truffle.r.runtime.*;
import com.oracle.truffle.r.runtime.data.*;
import com.oracle.truffle.r.runtime.ops.na.*;
@RBuiltin(name = "diag", kind = SUBSTITUTE, parameterNames = {"x", "nrow", "ncol"})
// TODO INTERNAL
@SuppressWarnings("unused")
import static com.oracle.truffle.r.runtime.RBuiltinKind.INTERNAL;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.r.nodes.builtin.CastBuilder;
import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
import com.oracle.truffle.r.nodes.unary.CastDoubleNode;
import com.oracle.truffle.r.runtime.RBuiltin;
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.data.RComplex;
import com.oracle.truffle.r.runtime.data.RDataFactory;
import com.oracle.truffle.r.runtime.data.RNull;
import com.oracle.truffle.r.runtime.data.model.RAbstractComplexVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
@RBuiltin(name = "diag", kind = INTERNAL, parameterNames = {"x", "nrow", "ncol"})
public abstract class Diag extends RBuiltinNode {
private NACheck check = NACheck.create();
@Override
public Object[] getDefaultParameterValues() {
return new Object[]{1, RMissing.instance, RMissing.instance};
}
@Override
protected void createCasts(CastBuilder casts) {
casts.toInteger(1);
casts.toInteger(2);
casts.firstIntegerWithError(1, Message.INVALID_LARGE_NA_VALUE, "nrow");
casts.firstIntegerWithError(2, Message.INVALID_LARGE_NA_VALUE, "ncol");
}
@Specialization
protected RNull dim(RNull vector, int rows, int cols) {
controlVisibility();
return RNull.instance;
}
@Specialization
protected RIntVector dim(int val, int rows, int cols) {
controlVisibility();
int[] data = new int[rows * cols];
for (int i = 0; i < Math.min(cols, rows); i++) {
data[i * rows + i] = val;
private static void checkParams(int nrow, int ncol) {
if (RRuntime.isNA(nrow)) {
CompilerDirectives.transferToInterpreter();
throw RError.error(RError.SHOW_CALLER2, Message.INVALID_LARGE_NA_VALUE, "nrow");
}
return RDataFactory.createIntVector(data, RDataFactory.COMPLETE_VECTOR, new int[]{rows, cols});
}
@Specialization
protected RIntVector dim(int val, int rows, RMissing cols) {
return dim(val, rows, rows);
}
@Specialization
protected RDoubleVector dim(double val, int rows, int cols) {
controlVisibility();
double[] data = new double[rows * cols];
for (int i = 0; i < Math.min(cols, rows); i++) {
data[i * rows + i] = val;
if (nrow < 0) {
CompilerDirectives.transferToInterpreter();
throw RError.error(RError.SHOW_CALLER2, Message.INVALID_NEGATIVE_VALUE, "nrow");
}
if (RRuntime.isNA(ncol)) {
CompilerDirectives.transferToInterpreter();
throw RError.error(RError.SHOW_CALLER2, Message.INVALID_LARGE_NA_VALUE, "ncol");
}
if (ncol < 0) {
CompilerDirectives.transferToInterpreter();
throw RError.error(RError.SHOW_CALLER2, Message.INVALID_NEGATIVE_VALUE, "ncol");
}
return RDataFactory.createDoubleVector(data, RDataFactory.COMPLETE_VECTOR, new int[]{rows, cols});
}
@Specialization
protected RDoubleVector dim(double val, int rows, RMissing cols) {
return dim(val, rows, rows);
private static int checkX(RAbstractVector x, int nrow, int ncol) {
int mn = (nrow < ncol) ? nrow : ncol;
if (mn > 0 && x.getLength() == 0) {
CompilerDirectives.transferToInterpreter();
throw RError.error(RError.SHOW_CALLER2, Message.POSITIVE_LENGTH, "x");
}
return mn;
}
@Specialization
protected RLogicalVector dim(byte val, int rows, int cols) {
protected Object diag(@SuppressWarnings("unused") RNull x, int nrow, int ncol) {
controlVisibility();
byte[] data = new byte[rows * cols];
for (int i = 0; i < Math.min(cols, rows); i++) {
data[i * rows + i] = val;
checkParams(nrow, ncol);
if (nrow == 0 && ncol == 0) {
return RDataFactory.createDoubleVector(new double[]{}, true, new int[]{0, 0});
} else {
throw RError.error(RError.SHOW_CALLER2, Message.X_NUMERIC);
}
return RDataFactory.createLogicalVector(data, RDataFactory.COMPLETE_VECTOR, new int[]{rows, cols});
}
@Specialization
protected RLogicalVector dim(byte val, int rows, RMissing cols) {
return dim(val, rows, rows);
}
@Specialization(guards = "isMatrix(vector)")
protected RIntVector dimWithDimensions(RIntVector vector, Object rows, Object cols) {
protected Object diag(RAbstractComplexVector x, int nrow, int ncol) {
controlVisibility();
int size = Math.min(vector.getDimensions()[0], vector.getDimensions()[1]);
int[] result = new int[size];
int nrow = vector.getDimensions()[0];
int pos = 0;
check.enable(vector);
for (int i = 0; i < size; i++) {
int value = vector.getDataAt(pos);
check.check(value);
result[i] = value;
pos += nrow + 1;
checkParams(nrow, ncol);
int mn = checkX(x, nrow, ncol);
double[] data = new double[nrow * ncol * 2];
int nx = x.getLength();
for (int j = 0; j < mn; j++) {
RComplex value = x.getDataAt(j % nx);
int index = j * (nrow + 1) * 2;
data[index] = value.getRealPart();
data[index + 1] = value.getImaginaryPart();
}
return RDataFactory.createIntVector(result, check.neverSeenNA());
return RDataFactory.createComplexVector(data, x.isComplete(), new int[]{nrow, ncol});
}
@Specialization(guards = "isMatrix(vector)")
protected RDoubleVector dimWithDimensions(RDoubleVector vector, Object rows, Object cols) {
@Specialization(guards = "!isRAbstractComplexVector(x)")
protected Object diag(RAbstractVector x, int nrow, int ncol, //
@Cached("create()") CastDoubleNode cast) {
controlVisibility();
int size = Math.min(vector.getDimensions()[0], vector.getDimensions()[1]);
double[] result = new double[size];
int nrow = vector.getDimensions()[0];
int pos = 0;
check.enable(vector);
for (int i = 0; i < size; i++) {
double value = vector.getDataAt(pos);
check.check(value);
result[i] = value;
pos += nrow + 1;
checkParams(nrow, ncol);
int mn = checkX(x, nrow, ncol);
RAbstractDoubleVector source = (RAbstractDoubleVector) cast.execute(x);
double[] data = new double[nrow * ncol];
int nx = source.getLength();
for (int j = 0; j < mn; j++) {
data[j * (nrow + 1)] = source.getDataAt(j % nx);
}
return RDataFactory.createDoubleVector(result, check.neverSeenNA());
}
public static boolean isMatrix(RIntVector vector) {
return vector.hasDimensions() && vector.getDimensions().length == 2;
}
public static boolean isMatrix(RDoubleVector vector) {
return vector.hasDimensions() && vector.getDimensions().length == 2;
return RDataFactory.createDoubleVector(data, source.isComplete(), new int[]{nrow, ncol});
}
}
......@@ -284,12 +284,6 @@ public final class RError extends RuntimeException {
NON_CONFORMABLE_ARGS("non-conformable arguments"),
DATA_VECTOR("'data' must be of a vector type"),
NON_NUMERIC_MATRIX_EXTENT("non-numeric matrix extent"),
// below: also can mean empty
INVALID_NCOL("invalid 'ncol' value (too large or NA)"),
// below: also can mean empty
INVALID_NROW("invalid 'nrow' value (too large or NA)"),
NEGATIVE_NCOL("invalid 'ncol' value (< 0)"),
NEGATIVE_NROW("invalid 'nrow' value (< 0)"),
NON_CONFORMABLE_ARRAYS("non-conformable arrays"),
UNKNOWN_UNNAMED_OBJECT("object not found"),
ONLY_MATRIX_DIAGONALS("only matrix diagonals can be replaced"),
......@@ -674,7 +668,10 @@ public final class RError extends RuntimeException {
NON_INTEGER_VALUE("non-integer value %s qualified with L; using numeric value"),
INTEGER_VALUE_DECIAML("integer literal %s contains decimal; using numeric value"),
INTEGER_VALUE_UNNECESARY_DECIMAL("integer literal %s contains unnecessary decimal point"),
NON_LANG_ASSIGNMENT_TARGET("target of assignment expands to non-language object");
NON_LANG_ASSIGNMENT_TARGET("target of assignment expands to non-language object"),
INVALID_LARGE_NA_VALUE("invalid '%s' value (too large or NA)"),
INVALID_NEGATIVE_VALUE("invalid '%s' value (< 0)"),
POSITIVE_LENGTH("'%s' must have positive length");
public final String message;
final boolean hasArgs;
......
......@@ -19,79 +19,73 @@ public class TestBuiltin_diag extends TestBase {
@Test
public void testdiag1() {
assertEval(Ignored.Unknown, "argv <- list(structure(c(1L, 2L, 3L, 4L, 1L), .Dim = 5L), 5L, 5L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(structure(c(1L, 2L, 3L, 4L, 1L), .Dim = 5L), 5L, 5L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag2() {
assertEval(Ignored.Unknown, "argv <- list(NULL, 0L, 0L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(NULL, 0L, 0L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag3() {
assertEval(Ignored.Unknown,
"argv <- list(structure(c(0.00258017518312032, 0.00371592854270272, 4.74358130918145e-05, 0.00490111130607204, 0.000101990092933588, 0.00674107947251412, 0.000239828967315095, 0.00980847069198632, 0.000617541923597065, 0.0155189333862593, 0.00178497855501229, 0.0281274123275302, 0.00633033372222146, 0.0642581517771313, 0.0351608933185668, 0.151097171670205, 0.967636582993474, 0.0809667077153405), .Names = c('Xr1', 'Xr2', 'Xr3', 'Xr4', 'Xr5', 'Xr6', 'Xr7', 'Xr8', 'Xr9', 'Xr10', 'Xr11', 'Xr12', 'Xr13', 'Xr14', 'Xr15', 'Xr16', 'Xr17', 'Xr18')), 18L, 18L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(structure(c(0.00258017518312032, 0.00371592854270272, 4.74358130918145e-05, 0.00490111130607204, 0.000101990092933588, 0.00674107947251412, 0.000239828967315095, 0.00980847069198632, 0.000617541923597065, 0.0155189333862593, 0.00178497855501229, 0.0281274123275302, 0.00633033372222146, 0.0642581517771313, 0.0351608933185668, 0.151097171670205, 0.967636582993474, 0.0809667077153405), .Names = c('Xr1', 'Xr2', 'Xr3', 'Xr4', 'Xr5', 'Xr6', 'Xr7', 'Xr8', 'Xr9', 'Xr10', 'Xr11', 'Xr12', 'Xr13', 'Xr14', 'Xr15', 'Xr16', 'Xr17', 'Xr18')), 18L, 18L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag4() {
assertEval(Ignored.Unknown, "argv <- list(c(FALSE, TRUE, TRUE, TRUE), 4L, 4L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(c(FALSE, TRUE, TRUE, TRUE), 4L, 4L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag5() {
assertEval(Ignored.Unknown,
assertEval(Ignored.OutputFormatting,
"argv <- list(c(-2.80063713410797-0i, 2.40432724210166-0i, -1.40502612938985+0i, 1.39344241164891+0i, 0.785422253492721+0i), 5L, 5L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag6() {
assertEval(Ignored.Unknown,
"argv <- list(structure(c(0.662193594830517, 0.883082096514931, 0.80211645621425, 0.806993241239092, 0.593615611337433, 0.55837479933202, 0.531727869384763, 0.696607367099979, 0.506321785494117, 0.489681023915914, 0.742249020738322, 0.65965217395585, 0.700437655250271, 0.80388520340336, 0.834325796707322, 0.741083805719802, 0.77320911661894, 0.76968452857621, 0.872531808824412, 0.769100148773066, 0.763385216756006, 0.775173380089108, 0.705125971098107, 0.706916424657676), .Names = c('VisualPerception', 'Cubes', 'PaperFormBoard', 'Flags', 'GeneralInformation', 'PargraphComprehension', 'SentenceCompletion', 'WordClassification', 'WordMeaning', 'Addition', 'Code', 'CountingDots', 'StraightCurvedCapitals', 'WordRecognition', 'NumberRecognition', 'FigureRecognition', 'ObjectNumber', 'NumberFigure', 'FigureWord', 'Deduction', 'NumericalPuzzles', 'ProblemReasoning', 'SeriesCompletion', 'ArithmeticProblems')), 24L, 24L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(structure(c(0.662193594830517, 0.883082096514931, 0.80211645621425, 0.806993241239092, 0.593615611337433, 0.55837479933202, 0.531727869384763, 0.696607367099979, 0.506321785494117, 0.489681023915914, 0.742249020738322, 0.65965217395585, 0.700437655250271, 0.80388520340336, 0.834325796707322, 0.741083805719802, 0.77320911661894, 0.76968452857621, 0.872531808824412, 0.769100148773066, 0.763385216756006, 0.775173380089108, 0.705125971098107, 0.706916424657676), .Names = c('VisualPerception', 'Cubes', 'PaperFormBoard', 'Flags', 'GeneralInformation', 'PargraphComprehension', 'SentenceCompletion', 'WordClassification', 'WordMeaning', 'Addition', 'Code', 'CountingDots', 'StraightCurvedCapitals', 'WordRecognition', 'NumberRecognition', 'FigureRecognition', 'ObjectNumber', 'NumberFigure', 'FigureWord', 'Deduction', 'NumericalPuzzles', 'ProblemReasoning', 'SeriesCompletion', 'ArithmeticProblems')), 24L, 24L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag7() {
assertEval(Ignored.Unknown, "argv <- list(1, 0L, 0L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(1, 0L, 0L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag8() {
assertEval(Ignored.Unknown,
"argv <- list(structure(c(0.553622032575332, 1.83583330034692, 0.540309168173204, 0.347171956892285), .Names = c('A', 'B', 'C', 'D')), 4L, 4L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(structure(c(0.553622032575332, 1.83583330034692, 0.540309168173204, 0.347171956892285), .Names = c('A', 'B', 'C', 'D')), 4L, 4L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag9() {
assertEval(Ignored.Unknown,
"argv <- list(structure(c(-875.251472917967, 12.8319913648351, -28.2155558559225, -27.6015982680416, -70.4377976184188, -98.9293825275015, 32.8291346503008, -20.6544753576079, 26.3486263439148, -42.5376299218819, -131.164911564755, -12.7775395276621, 3.34207338870892, -6.39516049903921, 5.97199502480298, 9.16451921253422, 4.70193189358059), .Names = c('(Intercept)', 'BII', 'BIII', 'BIV', 'BV', 'BVI', 'VMarvellous', 'VVictory', 'N0.2cwt', 'N0.4cwt', 'N0.6cwt', 'VMarvellous:N0.2cwt', 'VVictory:N0.2cwt', 'VMarvellous:N0.4cwt', 'VVictory:N0.4cwt', 'VMarvellous:N0.6cwt', 'VVictory:N0.6cwt')), 71L, 17L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(structure(c(-875.251472917967, 12.8319913648351, -28.2155558559225, -27.6015982680416, -70.4377976184188, -98.9293825275015, 32.8291346503008, -20.6544753576079, 26.3486263439148, -42.5376299218819, -131.164911564755, -12.7775395276621, 3.34207338870892, -6.39516049903921, 5.97199502480298, 9.16451921253422, 4.70193189358059), .Names = c('(Intercept)', 'BII', 'BIII', 'BIV', 'BV', 'BVI', 'VMarvellous', 'VVictory', 'N0.2cwt', 'N0.4cwt', 'N0.6cwt', 'VMarvellous:N0.2cwt', 'VVictory:N0.2cwt', 'VMarvellous:N0.4cwt', 'VVictory:N0.4cwt', 'VMarvellous:N0.6cwt', 'VVictory:N0.6cwt')), 71L, 17L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag10() {
assertEval(Ignored.Unknown,
"argv <- list(structure(c(-268.831499270454, 5.6415423423032, 14.3443760756611, -6.07661158322081, -7.61383061715105, 3.28804653251744, 13.7579673886322, 2.89856286229343, -9.75713414208632, 4.61320568224165), .Names = c('(Intercept)', 'block2', 'block3', 'block4', 'block5', 'block6', 'N1', 'P1', 'K1', 'N1:P1')), 24L, 10L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(structure(c(-268.831499270454, 5.6415423423032, 14.3443760756611, -6.07661158322081, -7.61383061715105, 3.28804653251744, 13.7579673886322, 2.89856286229343, -9.75713414208632, 4.61320568224165), .Names = c('(Intercept)', 'block2', 'block3', 'block4', 'block5', 'block6', 'N1', 'P1', 'K1', 'N1:P1')), 24L, 10L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag11() {
assertEval(Ignored.Unknown,
"argv <- list(structure(c(0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285), .Dim = 10L, .Dimnames = list(c('1', '2', '3', '4', '5', '6', '7', '8', '9', '10'))), 10L, 10L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(structure(c(0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285, 0.00284900284900285), .Dim = 10L, .Dimnames = list(c('1', '2', '3', '4', '5', '6', '7', '8', '9', '10'))), 10L, 10L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag12() {
assertEval(Ignored.Unknown, "argv <- list(list(1, 1, 1), 3L, 3L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(list(1, 1, 1), 3L, 3L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag13() {
assertEval(Ignored.Unknown, "argv <- list(list(), 0L, 0L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(list(), 0L, 0L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
public void testdiag14() {
assertEval(Ignored.Unknown, "argv <- list(character(0), 0L, 0L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
assertEval("argv <- list(character(0), 0L, 0L); .Internal(diag(argv[[1]], argv[[2]], argv[[3]]))");
}
@Test
......
......@@ -124,6 +124,7 @@ com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/C
com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Date.java,purdue.copyright
com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/DatePOSIXFunctions.java,gnu_r.copyright
com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Deparse.java,gnu_r.copyright
com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Diag.java,gnu_r_gentleman_ihaka.copyright
com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/DuplicatedFunctions.java,purdue.copyright
com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/EncodeString.java,purdue.copyright
com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FileFunctions.java,gnu_r.copyright
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment