diff --git a/.Rbuildignore b/.Rbuildignore index 47942fe0..e0033fd5 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -5,3 +5,4 @@ ^\.editorconfig$ ^\.git$ ^\.github$ +^tmp$ diff --git a/.gitignore b/.gitignore index f9cf80c0..139a360d 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,6 @@ _processedLockFile.lock R/jaspColumnEncoderVersion.R inst/include/Common + +# scratch space (python-interface work) +/tmp/ diff --git a/configure b/configure index 01b1e5ef..0ea38f67 100755 --- a/configure +++ b/configure @@ -95,9 +95,12 @@ location of the parent directory (e.g., the default is '-I\"../inst/include/jasp exit 1 fi -SRC_SOURCES="$(cd src/ && ls *.cpp | tr '\n' ' ')" +SRC_SOURCES="$(cd src/ && { ls *.cpp 2>/dev/null; ls core/*.cpp 2>/dev/null; ls adapters/rcpp/*.cpp 2>/dev/null; } | tr '\n' ' ')" JASPCOMMON_SOURCES="../${COMMON_DIR}/columntype.cpp" +# core/ is the R-free middle, adapters/rcpp the R binding layer (python-interface refactor) +PKG_CXXFLAGS="${PKG_CXXFLAGS} -I\".\" -I\"core\" -I\"adapters/rcpp\"" + if [ "${JASP_R_INTERFACE_LIBRARY}" != "" ]; then PKG_CXXFLAGS=-DJASP_R_INTERFACE_LIBRARY\ ${PKG_CXXFLAGS} fi diff --git a/configure.win b/configure.win index 1a64aa42..c0860da7 100755 --- a/configure.win +++ b/configure.win @@ -102,9 +102,12 @@ location of the parent directory (e.g., the default is '-I\"../inst/include/jasp exit 1 fi -SRC_SOURCES="$(cd src/ && ls *.cpp | tr '\n' ' ')" +SRC_SOURCES="$(cd src/ && { ls *.cpp 2>/dev/null; ls core/*.cpp 2>/dev/null; ls adapters/rcpp/*.cpp 2>/dev/null; } | tr '\n' ' ')" JASPCOMMON_SOURCES="../${COMMON_DIR}/columntype.cpp" +# core/ is the R-free middle, adapters/rcpp the R binding layer (python-interface refactor) +PKG_CXXFLAGS="${PKG_CXXFLAGS} -I\".\" -I\"core\" -I\"adapters/rcpp\"" + if [ "${JASP_R_INTERFACE_LIBRARY}" != "" ]; then PKG_CXXFLAGS=-DJASP_R_INTERFACE_LIBRARY\ ${PKG_CXXFLAGS} fi diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 00000000..4da5ed60 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,4 @@ +build/ +__pycache__/ +*.egg-info/ +.pytest_cache/ diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt new file mode 100644 index 00000000..4cdd5f96 --- /dev/null +++ b/python/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.20) +project(jaspresults_python CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# python/ lives in jasp-desktop/Engine/jaspBase/python +get_filename_component(JASPBASE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) +get_filename_component(JASP_DESKTOP_ROOT "${JASPBASE_ROOT}/../.." ABSOLUTE) + +set(COMMON_DIR "${JASP_DESKTOP_ROOT}/Common" CACHE PATH "location of jasp-desktop/Common") +message(STATUS "jaspresults python: using Common at ${COMMON_DIR}") +if(NOT EXISTS "${COMMON_DIR}/json/json_value.cpp") + message(FATAL_ERROR "Common/json not found at ${COMMON_DIR} — pass -DCOMMON_DIR=") +endif() + +# The R-free core (shared with the R engine build of jaspBase). +file(GLOB CORE_SOURCES CONFIGURE_DEPENDS "${JASPBASE_ROOT}/src/core/*.cpp") + +find_package(pybind11 CONFIG REQUIRED) +pybind11_add_module(_jaspresults + ${CORE_SOURCES} + src/_jaspresults/pyConversions.cpp + src/_jaspresults/pyModule.cpp) +target_include_directories(_jaspresults PRIVATE + "${JASPBASE_ROOT}/src/core" + "${COMMON_DIR}" + "src/_jaspresults") + +install(TARGETS _jaspresults DESTINATION jaspresults) diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 00000000..e5baaab8 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = ["scikit-build-core>=0.10", "pybind11>=2.13"] +build-backend = "scikit_build_core.build" + +[project] +name = "jaspresults" +version = "0.20.0" +description = "JASP results engine driven from Python: same R-free C++ core that backs the R engine (jaspBase)." +readme = { text = "Python interface for the jaspResults C++ core, see tmp/plan-python-interface.md in the jaspBase repo.", content-type = "text/plain" } +requires-python = ">=3.10" +license = { text = "GPL-2.0-or-later" } + +[project.optional-dependencies] +data = ["numpy", "pandas"] +test = ["pytest", "numpy", "pandas"] + +[tool.scikit-build] +cmake.version = ">=3.20" +wheel.packages = ["src/jaspresults"] diff --git a/python/src/_jaspresults/pyConversions.cpp b/python/src/_jaspresults/pyConversions.cpp new file mode 100644 index 00000000..742f9ed0 --- /dev/null +++ b/python/src/_jaspresults/pyConversions.cpp @@ -0,0 +1,649 @@ +#include "pyConversions.h" + +#include +#include +#include +#include "stringutils.h" // stringUtils::escapeHtmlStuff (Common, header-only) + +namespace py = pybind11; + +// --------------------------------------------------------------------------- +// optional numpy / pandas introspection. Both are optional dependencies; when +// absent the type checks simply return false so plain Python containers keep +// working. Cached module handles are leaked on purpose (destroying a static +// py::object at interpreter shutdown deadlocks). +// --------------------------------------------------------------------------- +static const py::object * pyModuleOrNull(const char * name) +{ + static std::map cache; + auto found = cache.find(name); + if(found != cache.end()) + return found->second; + + const py::object * mod = nullptr; + try + { + mod = new py::object(py::module_::import(name)); + } + catch(const py::error_already_set &) + { + mod = new py::object(py::none()); + } + cache.emplace(name, mod); + return mod; +} + +static bool pyIsInstanceOfModule(const py::handle & h, const char * modName, const char * clsName) +{ + if(h.is_none()) + return false; + + const py::object * mod = pyModuleOrNull(modName); + if(mod->is_none()) + return false; + + try + { + return py::isinstance(h, mod->attr(clsName)); + } + catch(const py::error_already_set &) + { + return false; + } +} + +bool pyIsDataFrame(const py::handle & h) { return pyIsInstanceOfModule(h, "pandas", "DataFrame"); } +bool pyIsSeries(const py::handle & h) { return pyIsInstanceOfModule(h, "pandas", "Series"); } +bool pyIsNdArray(const py::handle & h) { return pyIsInstanceOfModule(h, "numpy", "ndarray"); } +bool pyIsCategorical(const py::handle & h) { return pyIsInstanceOfModule(h, "pandas", "Categorical"); } + +static bool pyIsNA(const py::handle & h) +{ + if(h.is_none()) + return true; + + //pandas.isna covers pd.NA / pd.NaT / np.nan scalars. ndarrays return an + //array (not a bool), so the isinstance guard keeps them out here. + const py::object * pd = pyModuleOrNull("pandas"); + if(pd->is_none()) + return false; + + try + { + py::object res = pd->attr("isna")(h); + return py::isinstance(res) && res.cast(); + } + catch(const py::error_already_set &) + { + return false; + } +} + +// --------------------------------------------------------------------------- +// §3.1 scalar -> cell +// --------------------------------------------------------------------------- +Json::Value pyCellToJsonValue(const py::handle & h, bool escapeHtml) +{ + //None / pd.NA / pd.NaT -> empty cell (R NA -> "") + if(pyIsNA(h)) + return Json::Value(""); + + //explicit "NaN" marker (R's NaN cell; Python can't otherwise ask for it) + if(py::isinstance(h)) + return Json::Value("NaN"); + + //bool before int: in Python bools are ints + if(py::isinstance(h)) + return Json::Value(h.cast()); + + if(py::isinstance(h)) + return Json::Value(static_cast(h.cast())); + + if(py::isinstance(h)) + { + double v = h.cast(); + if(std::isnan(v)) return Json::Value(""); + if(v == std::numeric_limits::infinity()) return Json::Value("\u221E"); + if(v == -std::numeric_limits::infinity()) return Json::Value("-\u221E"); + return Json::Value(v); + } + + if(py::isinstance(h)) + { + std::string s = h.cast(); + return escapeHtml ? Json::Value(stringUtils::escapeHtmlStuff(s)) : Json::Value(s); + } + + //numpy scalars (np.int64, np.float64, np.bool_, np.str_, ...) -> unwrap to builtins + if(py::hasattr(h, "item") && !py::isinstance(h) && !py::isinstance(h)) + { + try + { + return pyCellToJsonValue(h.attr("item")(), escapeHtml); + } + catch(const py::error_already_set &) + { + //fall through to the generic error below + } + } + + throw std::runtime_error("jaspresults: cannot convert this Python value to a table cell"); +} + +Json::Value pyMixedPartToJsonValue(const py::handle & h, bool escapeHtml) +{ + if(h.is_none()) + return Json::nullValue; + + return pyCellToJsonValue(h, escapeHtml); +} + +///A mixed cell is a dict with (at least) value/type/format keys, mirroring the +///R `list(value=, type=, format=)` object with class "mixed". +static bool pyIsMixedDict(const py::dict & d) +{ + return d.contains("value") && d.contains("type") && d.contains("format"); +} + +///Mixed cells can appear inside pySequenceToCells, so handle dict there too. +///Generic dicts/lists become JSON objects/arrays as single cells, mirroring +///RObject_to_JsonValue on R lists. +Json::Value pyCellOrMixedToJsonValue(const py::handle & h, bool escapeHtml) +{ + if(py::isinstance(h)) + { + py::dict d = h.cast(); + if(pyIsMixedDict(d)) + { + Json::Value mixed(Json::objectValue); + mixed["value"] = pyMixedPartToJsonValue(d["value"], escapeHtml); + mixed["type"] = pyMixedPartToJsonValue(d["type"], escapeHtml); + mixed["format"] = pyMixedPartToJsonValue(d["format"], escapeHtml); + return mixed; + } + + Json::Value obj(Json::objectValue); + for(auto item : d) + obj[py::str(item.first).cast()] = pyCellOrMixedToJsonValue(item.second, escapeHtml); + return obj; + } + + if(py::isinstance(h) || py::isinstance(h)) + { + Json::Value arr(Json::arrayValue); + for(auto item : h) + arr.append(pyCellOrMixedToJsonValue(item, escapeHtml)); + return arr; + } + + return pyCellToJsonValue(h, escapeHtml); +} + +// --------------------------------------------------------------------------- +// materialise a Python sequence into a vector of cells +// --------------------------------------------------------------------------- +static bool pyIsSequenceLike(const py::handle & h) +{ + if(py::isinstance(h) || py::isinstance(h)) + return true; + if(pyIsNdArray(h) || pyIsSeries(h) || pyIsCategorical(h)) + return true; + if(py::isinstance(h) || py::isinstance(h) || py::isinstance(h)) + return false; + + //range, generators and other iterables + try + { + return py::isinstance(h, py::module_::import("collections.abc").attr("Iterable")); + } + catch(const py::error_already_set &) + { + return false; + } +} + +static py::object pyMaterialiseSequence(const py::handle & h) +{ + //numpy ndarray / pandas Series -> native Python list + if(pyIsNdArray(h) || pyIsSeries(h)) + return h.attr("tolist")(); + + //tuple / list are already fine + if(py::isinstance(h) || py::isinstance(h)) + return py::reinterpret_borrow(h); + + //range / generator / Categorical / other iterables + return py::reinterpret_steal(PySequence_List(h.ptr())); +} + +std::vector pySequenceToCells(const py::handle & h, bool escapeHtml) +{ + std::vector cells; + + if(h.is_none()) + return cells; + + //scalars (incl. strings, which must NOT be iterated char-by-char) become a + //single cell, mirroring R's length-1 columns + if(!pyIsSequenceLike(h)) + return {pyCellOrMixedToJsonValue(h, escapeHtml)}; + + py::object seq = pyMaterialiseSequence(h); + for(auto item : seq) + cells.push_back(pyCellOrMixedToJsonValue(item, escapeHtml)); + + return cells; +} + +std::vector pyToStringVector(const py::handle & h) +{ + std::vector out; + + if(h.is_none()) + return out; + + if(!pyIsSequenceLike(h)) + return {py::str(h).cast()}; + + py::object seq = pyMaterialiseSequence(h); + for(auto item : seq) + out.push_back(item.is_none() ? "" : py::str(item).cast()); + + return out; +} + +std::pair, std::map> pyToStringRowsAndFields(const py::handle & h) +{ + std::vector rows; + std::map fields; + + if(h.is_none()) + return {rows, fields}; + + if(py::isinstance(h)) + { + py::dict d = h.cast(); + for(auto item : d) + { + std::string key = py::str(item.first).cast(); + std::string value = item.second.is_none() ? "" : py::str(item.second).cast(); + rows.push_back(value); + if(key != "") + fields[key] = value; + } + return {rows, fields}; + } + + rows = pyToStringVector(h); + return {rows, fields}; +} + +std::pair, std::map> pyToBoolRowsAndFields(const py::handle & h) +{ + std::vector rows; + std::map fields; + + if(h.is_none()) + return {rows, fields}; + + if(py::isinstance(h)) + { + py::dict d = h.cast(); + for(auto item : d) + { + std::string key = py::str(item.first).cast(); + bool value = py::cast(item.second); + rows.push_back(value); + if(key != "") + fields[key] = value; + } + return {rows, fields}; + } + + py::object seq = pyMaterialiseSequence(h); + for(auto item : seq) + rows.push_back(py::cast(item)); + + return {rows, fields}; +} + +// --------------------------------------------------------------------------- +// §3.2 ingest dispatch +// +// Python rules (documented in the plan §3.2), which deliberately match R's +// data-orientation conventions: +// dict {str: sequence} / pandas DataFrame -> COLUMNS (like R data.frame) +// pandas Series -> one named COLUMN +// tuple/list of scalars -> one ROW (like R atomic vector) +// tuple/list of sequences -> ROWS (like R list of rows) +// 2-D np.ndarray -> COLUMNS (like R matrix) +// 1-D np.ndarray -> one ROW (like R atomic vector) +// --------------------------------------------------------------------------- + +/// dict {str: sequence} (or DataFrame) -> column-major cells + names. +static void pyMappingToColumns(jaspTable * table, const py::handle & data, std::vector> & columns, std::vector & colNames, bool escapeHtml) +{ + if(pyIsDataFrame(data)) + { + py::list cols = data.attr("columns").cast(); + for(auto c : cols) + { + std::string name = py::str(c).cast(); + colNames.push_back(name); + columns.push_back(pySequenceToCells(data[py::reinterpret_borrow(c)], escapeHtml)); + } + return; + } + + //plain dict + py::dict d = data.cast(); + for(auto item : d) + { + colNames.push_back(py::str(item.first).cast()); + columns.push_back(pySequenceToCells(item.second, escapeHtml)); + } +} + +void pyIngestSetData(jaspTable * table, py::object data, py::object colNamesObj, py::object rowNamesObj) +{ + bool escapeHtml = table->getEscapeHtml(); + + if(data.is_none()) + { + table->_data.clear(); + //still apply explicit names if given + if(!colNamesObj.is_none()) + table->setColNames(pyToStringVector(colNamesObj)); + if(!rowNamesObj.is_none()) + table->setRowNames(pyToStringVector(rowNamesObj)); + table->notifyParentOfChanges(); + return; + } + + jaspTableData d; + + if(pyIsDataFrame(data) || py::isinstance(data)) + { + pyMappingToColumns(table, data, d.columns, d.colNames, escapeHtml); + if(!colNamesObj.is_none()) + d.colNames = pyToStringVector(colNamesObj); + if(!rowNamesObj.is_none()) + d.rowNames = pyToStringVector(rowNamesObj); + table->setDataColumns(d); + table->notifyParentOfChanges(); + return; + } + + if(pyIsSeries(data)) + { + std::string name = ""; + if(!data.attr("name").is_none()) + name = py::str(data.attr("name")).cast(); + + d.colNames = {name}; + d.columns = {pySequenceToCells(data, escapeHtml)}; + if(!rowNamesObj.is_none()) + d.rowNames = pyToStringVector(rowNamesObj); + table->setDataColumns(d); + table->notifyParentOfChanges(); + return; + } + + //numpy ndarray: 2-D -> COLUMNS (like an R matrix), 1-D -> one ROW (like an + //R atomic vector). Optional col_names/row_names override the (absent) names. + if(pyIsNdArray(data)) + { + int ndim = data.attr("ndim").cast(); + + if(ndim >= 2) + { + //column-major: transpose the row-major tolist() output + py::list rowsList = data.attr("tolist")().cast(); + size_t nRows = rowsList.size(); + size_t nCols = nRows > 0 ? rowsList[0].cast().size() : 0; + + d.columns.assign(nCols, std::vector()); + for(size_t r = 0; r < nRows; r++) + { + py::list row = rowsList[r].cast(); + for(size_t c = 0; c < nCols && c < row.size(); c++) + d.columns[c].push_back(pyCellOrMixedToJsonValue(row[c], escapeHtml)); + } + + if(!colNamesObj.is_none()) + d.colNames = pyToStringVector(colNamesObj); + if(!rowNamesObj.is_none()) + d.rowNames = pyToStringVector(rowNamesObj); + table->setDataColumns(d); + table->notifyParentOfChanges(); + return; + } + //1-D falls through to the generic sequence handling below (one row) + } + + //a flat sequence of scalars == one ROW (like an R atomic vector via addRow) + //a sequence of sequences == ROWS + //We detect "is the first non-None element itself a sequence?" to decide. + py::object seq = pyMaterialiseSequence(data); + + bool isFirstElementScalar = true; + bool anyElementSequence = false; + for(auto item : seq) + { + if(py::isinstance(item) || py::isinstance(item) || pyIsNdArray(item) || pyIsSeries(item)) + { + isFirstElementScalar = false; + anyElementSequence = true; + break; + } + //only inspect the first non-None element to classify the shape + if(!item.is_none()) + { + isFirstElementScalar = true; + break; + } + } + + std::vector rowNames = rowNamesObj.is_none() ? std::vector() : pyToStringVector(rowNamesObj); + + if(!anyElementSequence) + { + //one row: each scalar becomes one column? No — R addRow semantics: the + //vector's cells map across existing columns. For a brand-new table this + //is a single row of N cells. Build it by appending to columns 0..N-1. + std::vector rowCells = pySequenceToCells(seq, escapeHtml); + + //optional explicit column names for the row's cells + std::vector cellColNames = colNamesObj.is_none() ? std::vector() : pyToStringVector(colNamesObj); + + table->_data.clear(); + for(size_t i = 0; i < rowCells.size(); i++) + { + std::string colName = (i < cellColNames.size() && cellColNames[i] != "") ? cellColNames[i] : ""; + table->addOrSetColumnInData(std::vector({rowCells[i]}), colName); + } + + if(rowNames.size() > 0) + table->_rowNames[0] = rowNames[0]; + + table->notifyParentOfChanges(); + return; + } + + //ROWS: sequence of sequences (or dicts). Mirror addRows for a fresh table. + int equalizedColumnsLength = table->equalizeColumnsLengths(); + int previouslyAddedUnnamed = 0; + + for(size_t row = 0; row < rowNames.size(); row++) + table->_rowNames[row + equalizedColumnsLength] = rowNames[row]; + + std::vector cellColNames = colNamesObj.is_none() ? std::vector() : pyToStringVector(colNamesObj); + + for(auto sub : seq) + { + if(py::isinstance(sub)) + { + //dict row: keys name the columns, values are single cells + //(like R addRows(list(list(a=1, b=2)))) + py::dict d = sub.cast(); + for(auto item : d) + { + std::string colName = py::str(item.first).cast(); + previouslyAddedUnnamed = table->pushbackToColumnInData(std::vector({pyCellOrMixedToJsonValue(item.second, escapeHtml)}), colName, equalizedColumnsLength, previouslyAddedUnnamed); + } + } + else + { + std::vector rowCells = pySequenceToCells(sub, escapeHtml); + for(size_t i = 0; i < rowCells.size(); i++) + { + std::string colName = (i < cellColNames.size() && cellColNames[i] != "") ? cellColNames[i] : ""; + previouslyAddedUnnamed = table->pushbackToColumnInData(std::vector({rowCells[i]}), colName, equalizedColumnsLength, previouslyAddedUnnamed); + } + } + + equalizedColumnsLength = table->equalizeColumnsLengths(); + } + + table->notifyParentOfChanges(); +} + +void pyIngestSetColumn(jaspTable * table, std::string columnName, py::object column) +{ + int colIndex = table->getDesiredColumnIndexFromNameForColumnAdding(columnName); + + std::vector cells = pySequenceToCells(column, table->getEscapeHtml()); + table->setColumnCellsAt(cells, colIndex); + + table->notifyParentOfChanges(); +} + +void pyIngestAddColumns(jaspTable * table, py::object data) +{ + if(data.is_none()) + return; + + bool escapeHtml = table->getEscapeHtml(); + + if(pyIsDataFrame(data) || py::isinstance(data)) + { + std::vector> columns; + std::vector colNames; + pyMappingToColumns(table, data, columns, colNames, escapeHtml); + + for(size_t col = 0; col < columns.size(); col++) + table->addOrSetColumnInData(columns[col], col < colNames.size() ? colNames[col] : ""); + } + else + { + //single sequence -> one column + table->_data.push_back(pySequenceToCells(data, escapeHtml)); + } + + table->notifyParentOfChanges(); +} + +void pyIngestAddRows(jaspTable * table, py::object data, std::vector rowNames) +{ + if(data.is_none()) + return; + + bool escapeHtml = table->getEscapeHtml(); + + int equalizedColumnsLength = table->equalizeColumnsLengths(); + int previouslyAddedUnnamed = 0; + + for(size_t row = 0; row < rowNames.size(); row++) + table->_rowNames[row + equalizedColumnsLength] = rowNames[row]; + + if(pyIsDataFrame(data) || py::isinstance(data)) + { + //DataFrame/dict interpreted as rows: each mapping entry contributes one + //row of cells across the columns identified by its key. + std::vector> columns; + std::vector colNames; + pyMappingToColumns(table, data, columns, colNames, escapeHtml); + + for(size_t col = 0; col < columns.size(); col++) + previouslyAddedUnnamed = table->pushbackToColumnInData(columns[col], col < colNames.size() ? colNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamed); + } + else + { + py::object seq = pyMaterialiseSequence(data); + for(auto sub : seq) + { + std::vector rowCells = pySequenceToCells(sub, escapeHtml); + for(size_t i = 0; i < rowCells.size(); i++) + previouslyAddedUnnamed = table->pushbackToColumnInData(std::vector({rowCells[i]}), "", equalizedColumnsLength, previouslyAddedUnnamed); + + equalizedColumnsLength = table->equalizeColumnsLengths(); + } + } + + table->notifyParentOfChanges(); +} + +void pyIngestAddRow(jaspTable * table, py::object row, std::string rowName) +{ + if(row.is_none()) + return; + + bool escapeHtml = table->getEscapeHtml(); + + int equalizedColumnsLength = table->equalizeColumnsLengths(); + int previouslyAddedUnnamed = 0; + + if(rowName != "") + table->_rowNames[equalizedColumnsLength] = rowName; + + if(py::isinstance(row)) + { + //dict row: keys name the columns, each value is a single cell + //(mirrors R addRow(named list)) + py::dict d = row.cast(); + for(auto item : d) + { + std::string colName = py::str(item.first).cast(); + Json::Value cell = pyCellOrMixedToJsonValue(item.second, escapeHtml); + previouslyAddedUnnamed = table->pushbackToColumnInData(std::vector({cell}), colName, equalizedColumnsLength, previouslyAddedUnnamed); + } + } + else + { + std::vector cells = pySequenceToCells(row, escapeHtml); + for(size_t i = 0; i < cells.size(); i++) + previouslyAddedUnnamed = table->pushbackToColumnInData(std::vector({cells[i]}), "", equalizedColumnsLength, previouslyAddedUnnamed); + } + + table->notifyParentOfChanges(); +} + +void pyIngestAddColumnInfo(jaspTable * table, py::object name, py::object title, py::object type, py::object format, py::object combine, py::object overtitle) +{ + std::string colName = name.is_none() ? table->defaultColName(table->_colNames.rowCount()) : py::str(name).cast(); + table->_specifiedColumns.insert(colName); + + table->_colNames.add(colName); + + std::string lastAddedColName = table->getColName(table->_colNames.rowCount() - 1); + + if(!title.is_none()) table->_colTitles[ lastAddedColName ] = py::str(title) .cast(); + if(!type.is_none()) table->_colTypes[ lastAddedColName ] = py::str(type) .cast(); + if(!format.is_none()) table->_colFormats[ lastAddedColName ] = py::str(format) .cast(); + if(!overtitle.is_none()) table->_colOvertitles[ lastAddedColName ] = py::str(overtitle) .cast(); + if(!combine.is_none()) table->_colCombines[ lastAddedColName ] = py::cast(combine); +} + +void pyIngestAddFootnote(jaspTable * table, std::string message, py::object symbol, py::object colNames, py::object rowNames) +{ + if(message == "") + throw std::runtime_error("One would expect a footnote to at least contain a message.."); + + std::string strSymbol = symbol.is_none() ? "" : py::str(symbol).cast(); + + bool escapeHtml = table->getEscapeHtml(); + std::vector cols = colNames.is_none() ? std::vector() : pySequenceToCells(colNames, escapeHtml); + std::vector rows = rowNames.is_none() ? std::vector() : pySequenceToCells(rowNames, escapeHtml); + + table->addFootnote(message, strSymbol, cols, rows); +} diff --git a/python/src/_jaspresults/pyConversions.h b/python/src/_jaspresults/pyConversions.h new file mode 100644 index 00000000..f823e498 --- /dev/null +++ b/python/src/_jaspresults/pyConversions.h @@ -0,0 +1,61 @@ +#pragma once + +// Python -> jaspTable conversions: the §3.1 cell contract and §3.2 ingest +// dispatch from tmp/plan-python-interface.md. Mirrors the R adapters in +// src/adapters/rcpp (rcppConversions + rcppTableIngest) behaviour-for-behaviour +// so that the same table built from R and from Python produces byte-identical +// JSON. Documented deviations (Python can't express R's NA/NaN split etc.) +// live in the plan's §3.1/§3.2 tables. + +#include +#include +#include +#include +#include +#include "jaspTable.h" + +namespace py = pybind11; + +///Marker object exported as jaspresults.NaNString: the only way to get R's +///`NaN` cell ("NaN") from Python, where NaN and NA are indistinguishable +///(Python's nan maps to R's NA -> ""). +struct pyNaNString {}; + +///§3.1 scalar -> cell. Unknown types raise. +Json::Value pyCellToJsonValue(const py::handle & h, bool escapeHtml); + +///Like pyCellToJsonValue, but mixed-cell dicts become {value,type,format} +///objects and generic dicts/lists become JSON objects/arrays (mirrors +///RObject_to_JsonValue on R lists). +Json::Value pyCellOrMixedToJsonValue(const py::handle & h, bool escapeHtml); + +///mixed-cell parts: None -> JSON null (like R NULL), scalars like pyCellToJsonValue. +Json::Value pyMixedPartToJsonValue(const py::handle & h, bool escapeHtml); + +///Any Python sequence (list/tuple/ndarray/Series/Categorical/range/generator) +///-> column of cells. +std::vector pySequenceToCells(const py::handle & h, bool escapeHtml); + +///Names helper: materialises the sequence and stringifies each element. +std::vector pyToStringVector(const py::handle & h); + +///§3.2 ingest paths (dispatch documented in the plan). esc comes from the +///table (jaspObject::getEscapeHtml). +void pyIngestSetData( jaspTable * table, py::object data, py::object colNames, py::object rowNames); +void pyIngestSetColumn( jaspTable * table, std::string columnName, py::object column); +void pyIngestAddColumns( jaspTable * table, py::object data); +void pyIngestAddRows( jaspTable * table, py::object data, std::vector rowNames); +void pyIngestAddRow( jaspTable * table, py::object row, std::string rowName); +void pyIngestAddColumnInfo( jaspTable * table, py::object name, py::object title, py::object type, py::object format, py::object combine, py::object overtitle); +void pyIngestAddFootnote( jaspTable * table, std::string message, py::object symbol, py::object colNames, py::object rowNames); + +///setColNames/setColTypes/... accept a list (positional) or a dict +///(positional + fieldnames), mirroring the R named-list -> rows+fields split. +std::pair, std::map> pyToStringRowsAndFields(const py::handle & h); +std::pair, std::map> pyToBoolRowsAndFields(const py::handle & h); + +///Type checks (numpy/pandas optional; false when the module is missing). +bool pyIsDataFrame(const py::handle & h); +bool pyIsSeries(const py::handle & h); +bool pyIsNdArray(const py::handle & h); +bool pyIsCategorical(const py::handle & h); diff --git a/python/src/_jaspresults/pyModule.cpp b/python/src/_jaspresults/pyModule.cpp new file mode 100644 index 00000000..eb7de407 --- /dev/null +++ b/python/src/_jaspresults/pyModule.cpp @@ -0,0 +1,234 @@ +// pybind11 bindings for the R-free jaspResults core (Phase 2 preview). +// +// This is the seed of the planned `python/` package. It exposes the core +// object tree and the Python-native table ingest defined in pyConversions.cpp +// (§3.2 of tmp/plan-python-interface.md). Ownership mirrors the R engine: +// objects are owned by jaspObject::allocatedObjects and released via +// destroyAllAllocatedObjects(), so Python holders use py::nodelete. + +#include +#include +#include + +#include +#include + +#include "jaspResults.h" +#include "jaspContainer.h" +#include "jaspHtml.h" +#include "jaspReport.h" +#include "jaspQmlSource.h" +#include "jaspTable.h" +#include "jaspPlot.h" +#include "jaspState.h" +#include "jaspColumn.h" +#include "jaspHost.h" +#include "pyConversions.h" + +namespace py = pybind11; + +// Keep a Python callable alive across C++ invocations. Deliberately leaked: +// destroying a static py::function after interpreter shutdown deadlocks. +static py::function * g_sendFunc = nullptr; +static void sendFuncTrampoline(const char * json) +{ + if(g_sendFunc) + (*g_sendFunc)(std::string(json ? json : "")); +} + +static py::function * g_logFunc = nullptr; +static void logFuncTrampoline(const std::string & msg) +{ + if(g_logFunc) + (*g_logFunc)(msg); + else + fprintf(stdout, "%s", msg.c_str()); +} + +PYBIND11_MODULE(_jaspresults, m) +{ + m.doc() = "Python bindings for the R-free jaspResults core"; + + // Explicit "NaN" cell marker (R distinguishes NA and NaN; Python cannot). + py::class_(m, "NaNString") + .def(py::init<>()); + m.attr("NaNString") = py::cast(pyNaNString()); + + // ---- jaspObject ---- + py::class_>(m, "jaspObject") + .def_property("title", [](jaspObject & o){ return o._title; }, + [](jaspObject & o, std::string t){ o._title = t; }) + .def_property("info", [](jaspObject & o){ return o._info; }, + [](jaspObject & o, std::string i){ o._info = i; }) + .def_property("position", [](jaspObject & o){ return o._position; }, + [](jaspObject & o, int p){ o._position = p; }) + .def("type", &jaspObject::type) + .def("addMessage", [](jaspObject & o, std::string msg){ o.addMessage(msg); }) + .def("addCitation", [](jaspObject & o, std::string c){ o.addCitation(c); }) + .def("setError", [](jaspObject & o, std::string msg){ o.setError(msg); }) + .def("getError", &jaspObject::getError) + .def("dependOnOptions", &jaspObject::dependOnOptions) + .def("setOptionMustBeDependency", [](jaspObject & o, std::string name, py::object val) + { + o.setOptionMustBeDependency(name, pyCellOrMixedToJsonValue(val, o.getEscapeHtml())); + }) + .def("toHtml", &jaspObject::toHtml) + ; + + // ---- jaspHtml ---- + py::class_>(m, "jaspHtml") + .def(py::init(), py::arg("text") = "") + .def_property("text", &jaspHtml::getText, &jaspHtml::setText) + .def_property("elementType", [](jaspHtml & h){ return h._elementType; }, + [](jaspHtml & h, std::string t){ h._elementType = t; }) + ; + + // ---- jaspReport ---- + py::class_>(m, "jaspReport") + .def(py::init(), py::arg("text") = "", py::arg("report") = false) + .def_property("text", &jaspReport::getText, &jaspReport::setText) + .def_property_readonly("isReport", [](jaspReport & r){ return r._report; }) + ; + + // ---- jaspQmlSource ---- + py::class_>(m, "jaspQmlSource") + .def(py::init(), py::arg("sourceID") = "") + .def("setValue", [](jaspQmlSource & q, py::object val){ q.setValue(pyCellOrMixedToJsonValue(val, q.getEscapeHtml())); }) + .def("getValue", &jaspQmlSource::getValue) + ; + + // ---- jaspTable ---- + py::class_>(m, "jaspTable") + .def(py::init(), py::arg("title") = "") + .def("setData", &pyIngestSetData, + py::arg("data"), py::arg("col_names") = py::none(), py::arg("row_names") = py::none()) + .def("setColumn", &pyIngestSetColumn, py::arg("name"), py::arg("column")) + .def("addColumns", &pyIngestAddColumns, py::arg("data")) + .def("addRows", &pyIngestAddRows, py::arg("data"), py::arg("row_names") = std::vector()) + .def("addRow", &pyIngestAddRow, py::arg("row"), py::arg("row_name") = "") + .def("addColumnInfo", &pyIngestAddColumnInfo, + py::arg("name") = py::none(), py::arg("title") = py::none(), py::arg("type") = py::none(), + py::arg("format") = py::none(), py::arg("combine") = py::none(), py::arg("overtitle") = py::none()) + .def("addFootnote", &pyIngestAddFootnote, + py::arg("message"), py::arg("symbol") = py::none(), + py::arg("col_names") = py::none(), py::arg("row_names") = py::none()) + .def("setColNames", [](jaspTable & t, py::object v){ auto rf = pyToStringRowsAndFields(v); t.setColNames(rf.first, rf.second); }) + .def("setColTypes", [](jaspTable & t, py::object v){ auto rf = pyToStringRowsAndFields(v); t.setColTypes(rf.first, rf.second); }) + .def("setColTitles", [](jaspTable & t, py::object v){ auto rf = pyToStringRowsAndFields(v); t.setColTitles(rf.first, rf.second); }) + .def("setColOvertitles", [](jaspTable & t, py::object v){ auto rf = pyToStringRowsAndFields(v); t.setColOvertitles(rf.first, rf.second); }) + .def("setColFormats", [](jaspTable & t, py::object v){ auto rf = pyToStringRowsAndFields(v); t.setColFormats(rf.first, rf.second); }) + .def("setColCombines", [](jaspTable & t, py::object v){ auto rf = pyToBoolRowsAndFields(v); t.setColCombines(rf.first, rf.second); }) + .def("setRowNames", [](jaspTable & t, py::object v){ auto rf = pyToStringRowsAndFields(v); t.setRowNames(rf.first, rf.second); }) + .def("setRowTitles", [](jaspTable & t, py::object v){ auto rf = pyToStringRowsAndFields(v); t.setRowTitles(rf.first, rf.second); }) + .def("setExpectedSize", &jaspTable::setExpectedSize) + .def("setExpectedRows", &jaspTable::setExpectedRows) + .def("setExpectedColumns", &jaspTable::setExpectedColumns) + .def_property("status", [](jaspTable & t){ return t._status; }, + [](jaspTable & t, std::string s){ t._status = s; }) + .def_property("transpose", [](jaspTable & t){ return t._transposeTable; }, + [](jaspTable & t, bool v){ t._transposeTable = v; }) + .def_property("transposeWithOvertitle", [](jaspTable & t){ return t._transposeWithOvertitle; }, + [](jaspTable & t, bool v){ t._transposeWithOvertitle = v; }) + .def_property("showSpecifiedColumnsOnly", [](jaspTable & t){ return t._showSpecifiedColumnsOnly; }, + [](jaspTable & t, bool v){ t._showSpecifiedColumnsOnly = v; }) + .def("complete", &jaspTable::complete) + // Test/introspection helper: dump the raw column-major cells as JSON. + .def("_debugCells", [](jaspTable & t) + { + Json::Value arr(Json::arrayValue); + for(auto & col : t._data) + { + Json::Value c(Json::arrayValue); + for(auto & cell : col) c.append(cell); + arr.append(c); + } + return arr.toStyledString(); + }) + ; + + // ---- jaspPlot ---- + py::class_>(m, "jaspPlot") + .def(py::init(), py::arg("title") = "") + .def_property("status", [](jaspPlot & p){ return p._status; }, + [](jaspPlot & p, std::string s){ p._status = s; }) + .def_readwrite("filePathPng", &jaspPlot::_filePathPng) + .def_readwrite("width", &jaspPlot::_width) + .def_readwrite("height", &jaspPlot::_height) + .def_readwrite("aspectRatio", &jaspPlot::_aspectRatio) + ; + + // ---- jaspState ---- + py::class_>(m, "jaspState") + .def(py::init(), py::arg("title") = "") + .def("setObject", [](jaspState & s, py::object o){ s.setObject(std::any(pyCellOrMixedToJsonValue(o, s.getEscapeHtml()))); }) + .def("getObjectJson", [](jaspState & s) + { + std::any a = s.getObject(); + if(auto * v = std::any_cast(&a)) + return v->toStyledString(); + return std::string(""); + }) + ; + + // ---- jaspColumn ---- + py::class_>(m, "jaspColumn") + .def(py::init(), py::arg("columnName") = "", py::arg("computed") = false) + ; + + // ---- jaspContainer ---- + py::class_> containerCls(m, "jaspContainer"); + containerCls + .def(py::init(), py::arg("title") = "") + .def_property_readonly("length", &jaspContainer::length) + .def("insert", [](jaspContainer & c, std::string field, jaspObject * obj){ c.insert(field, obj); }, + py::arg("field"), py::arg("obj")) + .def("at", [](jaspContainer & c, std::string field) -> jaspObject * { return c.at(field); }, + py::return_value_policy::reference) + .def_property("initCollapsed", [](jaspContainer & c){ return c._initiallyCollapsed; }, + [](jaspContainer & c, bool v){ c._initiallyCollapsed = v; }) + ; + + // ---- jaspResults ---- + py::class_>(m, "jaspResults") + .def(py::init(), py::arg("title")) + .def("getResults", &jaspResults::getResults) + .def("setOptions", &jaspResults::setOptions) + .def("changeOptions", &jaspResults::changeOptions) + .def("setErrorMessage", &jaspResults::setErrorMessage) + .def("send", [](jaspResults & r, std::string otherMsg){ r.send(otherMsg); }, py::arg("otherMsg") = "") + .def("complete", &jaspResults::complete) + .def("saveResults", &jaspResults::saveResults) + .def("prepareForWriting", &jaspResults::prepareForWriting) + .def("finishWriting", &jaspResults::finishWriting) + .def_property("status", [](jaspResults & r){ return r.getStatus(); }, + [](jaspResults & r, std::string s){ r.setStatus(s); }) + .def_property("relativePathKeep", [](jaspResults & r){ return r._relativePathKeep; }, + [](jaspResults & r, std::string v){ r._relativePathKeep = v; }) + ; + + // ---- module-level functions ---- + m.def("setResponseData", &jaspResults::setResponseData); + m.def("setSaveLocation", &jaspResults::setSaveLocation); + m.def("setWriteSealLocation", &jaspResults::setWriteSealLocation); + m.def("setBaseCitation", &jaspResults::setBaseCitation); + m.def("setDeveloperMode", &jaspObject::setDeveloperMode); + m.def("destroyAllAllocatedObjects", &jaspObject::destroyAllAllocatedObjects); + + m.def("setSendFunc", [](py::function f) + { + if(!g_sendFunc) + g_sendFunc = new py::function(std::move(f)); + else + *g_sendFunc = std::move(f); + jaspResults::setSendFunc(&sendFuncTrampoline); + }); + + m.def("setLogFunc", [](py::function f) + { + if(!g_logFunc) + g_logFunc = new py::function(std::move(f)); + else + *g_logFunc = std::move(f); + jaspHost::logString = &logFuncTrampoline; + }); +} diff --git a/python/src/jaspresults/__init__.py b/python/src/jaspresults/__init__.py new file mode 100644 index 00000000..b1617eed --- /dev/null +++ b/python/src/jaspresults/__init__.py @@ -0,0 +1,31 @@ +"""Python interface to the jaspResults C++ core (shared with jaspBase's R engine). + +Status: Phase-2 preview — the object classes plus the §3.1/§3.2 table-ingest +conversions from tmp/plan-python-interface.md. The full Analysis runner, state +directory and plotly rendering land in Phase 3. +""" + +from jaspresults._jaspresults import * # noqa: F401,F403 +from jaspresults._jaspresults import ( # noqa: F401 + jaspObject, + jaspHtml, + jaspReport, + jaspQmlSource, + jaspTable, + jaspPlot, + jaspState, + jaspColumn, + jaspContainer, + jaspResults, + NaNString, + setResponseData, + setSaveLocation, + setWriteSealLocation, + setBaseCitation, + setDeveloperMode, + destroyAllAllocatedObjects, + setSendFunc, + setLogFunc, +) + +__version__ = "0.20.0" diff --git a/python/tests/test_ingest.py b/python/tests/test_ingest.py new file mode 100644 index 00000000..e56d15b7 --- /dev/null +++ b/python/tests/test_ingest.py @@ -0,0 +1,193 @@ +"""Pytest matrix for the Python table-ingest adapter (§3.1/§3.2). + +These run without R: they assert the JSON cells and shapes the pybind adapter +produces, locking down the Python side of the R-vs-Python equivalence contract. +The byte-identical R-vs-Python comparison lives in tests/equivalence/. + +Run from the repo root with the built extension on the path: + JASP_PY_MODULE_DIR=python/build python -m pytest python/tests -q +""" +import json +import os +import sys + +import pytest + +sys.path.insert(0, os.environ.get( + "JASP_PY_MODULE_DIR", + os.path.join(os.path.dirname(__file__), "..", "build"))) + +import _jaspresults as M # noqa: E402 + +np = pytest.importorskip("numpy") +pd = pytest.importorskip("pandas") + + +@pytest.fixture(autouse=True) +def _clean(): + M.destroyAllAllocatedObjects() + yield + M.destroyAllAllocatedObjects() + + +def cells(table): + """Return the column-major cell matrix as parsed JSON.""" + return json.loads(table._debugCells()) + + +def make_table(): + return M.jaspTable("t") + + +# --- §3.1 scalar -> cell ----------------------------------------------------- + +def test_none_is_empty_string(): + t = make_table() + t.setData({"x": [None]}) + assert cells(t) == [[""]] + + +def test_nan_is_empty_string(): + # Python can't distinguish NA from NaN; nan maps to NA -> "". + t = make_table() + t.setData({"x": [float("nan")]}) + assert cells(t) == [[""]] + + +def test_nanstring_marker_is_nan_literal(): + # The explicit marker reproduces R's NaN -> "NaN" cell. + t = make_table() + t.setData({"x": [M.NaNString]}) + assert cells(t) == [["NaN"]] + + +def test_inf_maps_to_infinity_glyphs(): + t = make_table() + t.setData({"x": [float("inf"), float("-inf")]}) + assert cells(t) == [["\u221e", "-\u221e"]] + + +def test_int_bool_string_cells(): + t = make_table() + t.setData({"i": [1, 2], "b": [True, False], "s": ["a", "b"]}) + assert cells(t) == [[1, 2], [True, False], ["a", "b"]] + + +def test_string_html_escaped_by_default(): + t = make_table() + t.setData({"s": ["a #include "jaspResults.h" +#include "jaspTable.h" #include "jaspColumn.h" +#include "jaspObjectInterface.h" +#include "rcppInterfaces.h" +#include "rcppHost.h" +#include "rcppColumn.h" +#include "rcppResults.h" JASP_OBJECT_CREATOR(jaspHtml) JASP_OBJECT_CREATOR(jaspPlot) @@ -10,7 +16,9 @@ JASP_OBJECT_CREATOR_ARG(jaspColumn, computed) JASP_OBJECT_CREATOR(jaspReport) JASP_OBJECT_CREATOR(jaspContainer) JASP_OBJECT_CREATOR(jaspQmlSource) -JASP_OBJECT_CREATOR_ARG(jaspResults, oldState) +// The R-side of the old jaspResults(title, oldState) constructor (R storage +// env, old-state fill, loadResults) lives in rcppCreateJaspResults. +jaspResults_Interface * create_jaspResults(Rcpp::String title, Rcpp::RObject oldState) { return new jaspResults_Interface(rcppCreateJaspResults(title, oldState)); } RCPP_MODULE(jaspResults) { @@ -25,23 +33,23 @@ RCPP_MODULE(jaspResults) JASP_OBJECT_CREATOR_FUNCTIONREGISTRATION(jaspQmlSource); - Rcpp::function("cpp_startProgressbar", jaspResults::staticStartProgressbar); + Rcpp::function("cpp_startProgressbar", rcppStaticStartProgressbar); Rcpp::function("cpp_progressbarTick", jaspResults::staticProgressbarTick); Rcpp::function("destroyAllAllocatedObjects", jaspObject::destroyAllAllocatedObjects); - Rcpp::function("setSendFunc", jaspResults::setSendFunc); - Rcpp::function("setPollMessagesFunc", jaspResults::setPollMessagesFunc); + Rcpp::function("setSendFunc", rcppSetSendFunc); + Rcpp::function("setPollMessagesFunc", rcppSetPollMessagesFunc); Rcpp::function("setBaseCitation", jaspResults::setBaseCitation); Rcpp::function("setInsideJasp", jaspResults::setInsideJASP); Rcpp::function("isInsideJASP", jaspResults::isInsideJASP); - Rcpp::function("writeSealFilename", jaspResults::writeSealFilename); + Rcpp::function("writeSealFilename", rcppWriteSealFilename); Rcpp::function("setResponseData", jaspResults::setResponseData); Rcpp::function("setDeveloperMode", jaspResults::setDeveloperMode); Rcpp::function("setSaveLocation", jaspResults::setSaveLocation); Rcpp::function("setWriteSealLocation", jaspResults::setWriteSealLocation); - Rcpp::function("setColumnFuncs", jaspColumn::setColumnFuncs); - Rcpp::function("createColumnsCPP", jaspColumn::createColumnsCPP); + Rcpp::function("setColumnFuncs", rcppSetColumnFuncs); + Rcpp::function("createColumnsCPP", rcppCreateColumnsCPP); Rcpp::function("columnDelete", jaspColumn::deleteColumn); Rcpp::function("columnIsMine", jaspColumn::columnIsMine); Rcpp::function("columnExists", jaspColumn::columnExists); diff --git a/src/adapters/rcpp/jaspObjectInterface.h b/src/adapters/rcpp/jaspObjectInterface.h new file mode 100644 index 00000000..b2ddb83e --- /dev/null +++ b/src/adapters/rcpp/jaspObjectInterface.h @@ -0,0 +1,81 @@ +#pragma once + +// R-facing interface wrapper of jaspObject plus the RCPP_MODULE helper macros. +// Moved verbatim from the old src/jaspObject.h (behaviour and signatures must +// stay bit-identical); conversions go through rcppConversions.h, toRObject() +// through rcppToRObject.h. + +#include +#include "jaspObject.h" +#include "rcppConversions.h" +#include "rcppToRObject.h" + +#define JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(JASP_TYPE, PROP_TYPE, PROP_NAME, PROP_CAPITALIZED_NAME) \ + void set ## PROP_CAPITALIZED_NAME (PROP_TYPE new ## PROP_CAPITALIZED_NAME) { ((JASP_TYPE *)myJaspObject)->PROP_NAME = new ## PROP_CAPITALIZED_NAME; myJaspObject->notifyParentOfChanges(); } \ + PROP_TYPE get ## PROP_CAPITALIZED_NAME () { return ((JASP_TYPE *)myJaspObject)->PROP_NAME; } + +#define JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NO_NOTIFY(JASP_TYPE, PROP_TYPE, PROP_NAME, PROP_CAPITALIZED_NAME) \ + void set ## PROP_CAPITALIZED_NAME (PROP_TYPE new ## PROP_CAPITALIZED_NAME) { ((JASP_TYPE *)myJaspObject)->PROP_NAME = new ## PROP_CAPITALIZED_NAME; } \ + PROP_TYPE get ## PROP_CAPITALIZED_NAME () { return ((JASP_TYPE *)myJaspObject)->PROP_NAME; } + +#define JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NATIVE_STRING(JASP_TYPE, PROP_NAME, PROP_CAPITALIZED_NAME) \ +void set ## PROP_CAPITALIZED_NAME (Rcpp::String new ## PROP_CAPITALIZED_NAME) { ((JASP_TYPE *)myJaspObject)->PROP_NAME = new ## PROP_CAPITALIZED_NAME; myJaspObject->notifyParentOfChanges(); } \ +Rcpp::String get ## PROP_CAPITALIZED_NAME () { return ((JASP_TYPE *)myJaspObject)->PROP_NAME; } + +class jaspObject_Interface +{ +public: + jaspObject_Interface(jaspObject * dataObj) : myJaspObject(dataObj) + { +#ifdef JASP_RESULTS_DEBUG_TRACES + std::cout << "Interface to " << dataObj->objectTitleString() << " is created!\n"<myJaspObject->objectTitleString() << " is copied!\n"<myJaspObject; + } + + void print() { myJaspObject->print(); } + void addMessage(Rcpp::String msg) { myJaspObject->addMessage(std::string(msg)); } + std::string toHtml() { return myJaspObject->toHtml(); } + std::string type() { return myJaspObject->type(); } + void printHtml() { jaspPrint(myJaspObject->toHtml()); } + + void setOptionMustBeDependency(std::string optionName, Rcpp::RObject mustBeThis) { myJaspObject->setOptionMustBeDependency(optionName, RObject_to_JsonValue(mustBeThis, myJaspObject->getEscapeHtml())); } + void setOptionMustContainDependency(std::string optionName, Rcpp::RObject mustContainThis) { myJaspObject->setOptionMustContainDependency(optionName, RObject_to_JsonValue(mustContainThis, myJaspObject->getEscapeHtml())); } + void dependOnNestedOptions(Rcpp::CharacterVector optionName) { myJaspObject->dependOnNestedOptions(Rcpp::as>(optionName)); } + void setNestedOptionMustContainDependency(Rcpp::CharacterVector optionName, Rcpp::RObject mustContainThis) { myJaspObject->setNestedOptionMustContainDependency(Rcpp::as>(optionName), RObject_to_JsonValue(mustContainThis, myJaspObject->getEscapeHtml())); } + void dependOnOptions(Rcpp::CharacterVector listOptions) { myJaspObject->dependOnOptions(Rcpp::as>(listOptions)); } + void copyDependenciesFromJaspObject(jaspObject_Interface * other) { myJaspObject->copyDependenciesFromJaspObject(other->myJaspObject); } + void addCitation(Rcpp::String fullCitation) { myJaspObject->addCitation(std::string(fullCitation)); } + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NATIVE_STRING(jaspObject, _title, Title) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NATIVE_STRING(jaspObject, _info, Info) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspObject, int, _position, Position) + + void setError(Rcpp::String message) { myJaspObject->setError(std::string(message)); } + bool getError() { return myJaspObject->getError(); } + + Rcpp::List toRObject() { return rcppToRObject(myJaspObject); } + + jaspObject * returnMyJaspObject() { return myJaspObject; } + +protected: + jaspObject * myJaspObject = NULL; +}; + +#define JASP_OBJECT_FINALIZER_LAMBDA(JASP_TYPE) //.finalizer( [](JASP_TYPE * obj) { std::cout << "finalizerLambda " #JASP_TYPE " Called\n" << std::flush; jaspObjectFinalizer(obj); }) + +#define JASP_OBJECT_CREATOR_FUNCTIONNAME(JASP_TYPE) create_ ## JASP_TYPE +#define JASP_OBJECT_CREATOR_FUNCTIONNAME_STR(JASP_TYPE) "create_cpp_" #JASP_TYPE +#define JASP_OBJECT_CREATOR(JASP_TYPE) JASP_TYPE ## _Interface * JASP_OBJECT_CREATOR_FUNCTIONNAME(JASP_TYPE)(Rcpp::String title) { return new JASP_TYPE ## _Interface (new JASP_TYPE(title)); } +#define JASP_OBJECT_CREATOR_FUNCTIONREGISTRATION(JASP_TYPE) Rcpp::function(JASP_OBJECT_CREATOR_FUNCTIONNAME_STR(JASP_TYPE), &JASP_OBJECT_CREATOR_FUNCTIONNAME(JASP_TYPE)) +#define JASP_OBJECT_CREATOR_ARG(JASP_TYPE, EXTRA_ARG) JASP_TYPE ## _Interface * JASP_OBJECT_CREATOR_FUNCTIONNAME(JASP_TYPE)(Rcpp::String title, Rcpp::RObject EXTRA_ARG) { return new JASP_TYPE ## _Interface (new JASP_TYPE(title, EXTRA_ARG)); } + + +RCPP_EXPOSED_CLASS_NODECL(jaspObject_Interface) diff --git a/src/adapters/rcpp/rcppColumn.cpp b/src/adapters/rcpp/rcppColumn.cpp new file mode 100644 index 00000000..2e9626a5 --- /dev/null +++ b/src/adapters/rcpp/rcppColumn.cpp @@ -0,0 +1,83 @@ +// R-facing column-callback registration; bridges the Rcpp::XPtr function +// pointers from the desktop engine into the R-free core jaspColumn callbacks. +// The three column-data setters keep their Rcpp::RObject payload end-to-end: +// the core passes it through opaquely as std::any. + +#include "rcppColumn.h" + +static rcppSetColumnDataFuncDef _rcppSetColumnDataAsScaleFunc = nullptr, + _rcppSetColumnDataAsOrdinalFunc = nullptr, + _rcppSetColumnDataAsNominalFunc = nullptr; + +static bool rcppSetColumnDataAsScaleBridge(std::string columnName, const std::any & data, bool computed) +{ + try + { + return (*_rcppSetColumnDataAsScaleFunc)(columnName, std::any_cast(data), computed); + } + catch(const std::bad_any_cast &) + { + return false; + } +} + +static bool rcppSetColumnDataAsOrdinalBridge(std::string columnName, const std::any & data, bool computed) +{ + try + { + return (*_rcppSetColumnDataAsOrdinalFunc)(columnName, std::any_cast(data), computed); + } + catch(const std::bad_any_cast &) + { + return false; + } +} + +static bool rcppSetColumnDataAsNominalBridge(std::string columnName, const std::any & data, bool computed) +{ + try + { + return (*_rcppSetColumnDataAsNominalFunc)(columnName, std::any_cast(data), computed); + } + catch(const std::bad_any_cast &) + { + return false; + } +} + +void rcppSetColumnFuncs( Rcpp::XPtr scalar, + Rcpp::XPtr ordinal, + Rcpp::XPtr nominal, + Rcpp::XPtr colType, + Rcpp::XPtr colAnaId, + Rcpp::XPtr colIndex, + Rcpp::XPtr colCreate, + Rcpp::XPtr colDelete, + Rcpp::XPtr colExists, + Rcpp::XPtr encode, + Rcpp::XPtr decode, + Rcpp::XPtr shouldEncode, + Rcpp::XPtr shouldDecode) +{ + _rcppSetColumnDataAsScaleFunc = *scalar; + _rcppSetColumnDataAsOrdinalFunc = *ordinal; + _rcppSetColumnDataAsNominalFunc = *nominal; + + jaspColumn::setColumnFuncs( rcppSetColumnDataAsScaleBridge, + rcppSetColumnDataAsOrdinalBridge, + rcppSetColumnDataAsNominalBridge, + *colType, *colAnaId, *colIndex, *colCreate, *colDelete, *colExists, + *encode, *decode, *shouldEncode, *shouldDecode); +} + +Rcpp::StringVector rcppCreateColumnsCPP(Rcpp::StringVector columnNames) +{ + Rcpp::StringVector result; + + std::vector created = jaspColumn::createColumns(Rcpp::as>(columnNames)); + + for(const std::string & encodedName : created) + result.push_back(encodedName); + + return result; +} diff --git a/src/adapters/rcpp/rcppColumn.h b/src/adapters/rcpp/rcppColumn.h new file mode 100644 index 00000000..918a4213 --- /dev/null +++ b/src/adapters/rcpp/rcppColumn.h @@ -0,0 +1,28 @@ +#pragma once + +// R-facing column-callback registration, moved from the old src/jaspColumn.h. +// The desktop engine hands us Rcpp::XPtr-wrapped function pointers (the data +// setters take raw Rcpp::RObject payloads); we unwrap them and bridge the three +// data setters into the R-free core signatures (std::any payloads). The module +// registration keeps the exact same names and signatures as before. + +#include +#include "jaspColumn.h" + +typedef bool (*rcppSetColumnDataFuncDef)(std::string, Rcpp::RObject, bool); + +void rcppSetColumnFuncs( Rcpp::XPtr scalar, + Rcpp::XPtr ordinal, + Rcpp::XPtr nominal, + Rcpp::XPtr colType, + Rcpp::XPtr colAnaId, + Rcpp::XPtr colIndex, + Rcpp::XPtr colCreate, + Rcpp::XPtr colDelete, + Rcpp::XPtr colExists, + Rcpp::XPtr encode, + Rcpp::XPtr decode, + Rcpp::XPtr shouldEncode, + Rcpp::XPtr shouldDecode); + +Rcpp::StringVector rcppCreateColumnsCPP(Rcpp::StringVector columnNames); diff --git a/src/adapters/rcpp/rcppContainer.cpp b/src/adapters/rcpp/rcppContainer.cpp new file mode 100644 index 00000000..12668438 --- /dev/null +++ b/src/adapters/rcpp/rcppContainer.cpp @@ -0,0 +1,119 @@ +// R-backed jaspContainer logic moved from the old src/jaspContainer.cpp. +// Behaviour is kept identical: insert dispatches on the exposed Rcpp wrapper +// classes, wrapJaspObject maps a core jaspObject* back to the right *_Interface +// SEXP, and toRObject builds the R wrapper list. + +#include "rcppContainer.h" +#include "jaspContainer.h" +#include "jaspTable.h" +#include "jaspColumn.h" +#include "rcppInterfaces.h" +#include "rcppConversions.h" +#include "rcppToRObject.h" + +void rcppContainerInsert(jaspContainer * container, std::string field, Rcpp::RObject value) +{ + if(value.isNULL()) + { + container->insert(field, nullptr); //core insert erases the field when given nullptr + + return; + } + + jaspObject * obj = nullptr; + + + if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); + else if(Rcpp::is(value)) obj = (jaspObject*)(rcppContainerFromList(Rcpp::as(value))); + else throw std::runtime_error("Unhandled Rcpp Object type"); + + container->insert(field, obj); +} + +jaspContainer * rcppContainerFromList(Rcpp::List convertThis) +{ + std::vector colNamesVec = extractElementOrColumnNames(convertThis); + + if(convertThis.size() > colNamesVec.size()) + Rf_error("If you add a list() to jaspResults or a jaspContainer each element should be named!"); + + jaspContainer * newContainer = new jaspContainer(); + + for(int i=0; i_title = Rcpp::String(Rcpp::RObject(convertThis[i])); + else + rcppContainerInsert(newContainer, colNamesVec[i], convertThis[i]); + + return newContainer; +} + +Rcpp::RObject rcppWrapJaspObject(jaspObject * ref) +{ + switch(ref->getType()) + { + case jaspObjectType::container: return Rcpp::wrap(jaspContainer_Interface(ref)); + case jaspObjectType::qmlSource: return Rcpp::wrap(jaspQmlSource_Interface(ref)); + case jaspObjectType::column: return Rcpp::wrap(jaspColumn_Interface(ref)); + case jaspObjectType::report: return Rcpp::wrap(jaspReport_Interface(ref)); + case jaspObjectType::table: return Rcpp::wrap(jaspTable_Interface(ref)); + case jaspObjectType::state: return Rcpp::wrap(jaspState_Interface(ref)); + case jaspObjectType::html: return Rcpp::wrap(jaspHtml_Interface(ref)); + case jaspObjectType::plot: return Rcpp::wrap(jaspPlot_Interface(ref)); + default: return R_NilValue; + } +} + +Rcpp::RObject rcppContainerAt(jaspContainer * container, std::string field) +{ + jaspObject * ref = container->at(field); + if(ref == nullptr) + return R_NilValue; + + return rcppWrapJaspObject(ref); +} + +Rcpp::List rcppContainerToRObject(jaspContainer * container) /*const*/ +{ + + std::vector keys = container->getSortedDataFields(); + Rcpp::List lst; + + for (const auto & key : keys) + { + + jaspObject* child = container->getJaspObjectFromData(key); + + Rcpp::List Robj = rcppToRObject(child); + if (Robj.length() > 0) + lst.push_back(Robj, key); + } + + lst.attr("class") = Rcpp::CharacterVector({"jaspContainerWrapper", "jaspWrapper"}); + lst.attr("title") = container->_title; + + // the reason this function is not const + Rcpp::Environment jaspObjectEnvironment = Rcpp::new_env(); + jaspObjectEnvironment.assign("jaspObject", Rcpp::as(Rcpp::wrap(jaspContainer_Interface(container)))); + lst.attr("jaspObjectEnvironment") = jaspObjectEnvironment; + + return lst; +} + +Rcpp::RObject jaspContainer_Interface::findObjectWithUniqueNestedName(std::string uniqueNestedName) +{ + jaspObject * found = ((jaspContainer*)myJaspObject)->findObjectWithUniqueNestedName(uniqueNestedName); + + if(found == nullptr) + return R_NilValue; + + return rcppWrapJaspObject(found); +} diff --git a/src/adapters/rcpp/rcppContainer.h b/src/adapters/rcpp/rcppContainer.h new file mode 100644 index 00000000..882336ad --- /dev/null +++ b/src/adapters/rcpp/rcppContainer.h @@ -0,0 +1,16 @@ +#pragma once + +// R-backed jaspContainer helpers: the original insert dispatch, wrapJaspObject, +// list-construction, at() and toRObject that depend on Rcpp types. See +// src/core/jaspContainer.h for the R-free tree/admin logic. + +#include + +class jaspContainer; +class jaspObject; + +void rcppContainerInsert(jaspContainer * container, std::string field, Rcpp::RObject value); +jaspContainer * rcppContainerFromList(Rcpp::List convertThis); +Rcpp::RObject rcppWrapJaspObject(jaspObject * ref); +Rcpp::RObject rcppContainerAt(jaspContainer * container, std::string field); +Rcpp::List rcppContainerToRObject(jaspContainer * container); diff --git a/src/adapters/rcpp/rcppConversions.cpp b/src/adapters/rcpp/rcppConversions.cpp new file mode 100644 index 00000000..9fa50441 --- /dev/null +++ b/src/adapters/rcpp/rcppConversions.cpp @@ -0,0 +1,113 @@ +// Implementations moved verbatim from the old src/jaspObject.cpp (behaviour must +// stay bit-identical), with the per-object `_escapeHtml` flag passed explicitly. + +#include "rcppConversions.h" + +std::vector RList_to_VectorJson(Rcpp::List obj, bool escapeHtml) +{ + std::vector vec; + + for(int row=0; row(obj)) return RObject_to_JsonValue((Rcpp::List) obj, escapeHtml); + else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::List) obj, escapeHtml); + else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::NumericMatrix) obj, escapeHtml); + else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::NumericVector) obj, escapeHtml); + else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::IntegerVector) obj, escapeHtml); + else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::LogicalVector) obj, escapeHtml); + else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::CharacterVector) obj, escapeHtml); + else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::StringVector) obj, escapeHtml); + else if(obj.isS4()) return "an S4, which is too complicated for jaspResults now."; + else return "something that is not understood by jaspResults right now.."; +} + +Json::Value MixedRObject_to_JsonValue(Rcpp::List obj, bool escapeHtml) +{ + + Json::Value value(Json::objectValue); + + // sometimes we receive list(mixed) and sometimes mixed, ideally we always just get mixed but I'm not sure that's possible with addRows. + Rcpp::List data = obj.length() != 3 ? obj[0] : obj; + + value["value"] = RObject_to_JsonValue((Rcpp::RObject)data["value"], escapeHtml); + value["type"] = RObject_to_JsonValue((Rcpp::RObject)data["type"], escapeHtml); + value["format"] = RObject_to_JsonValue((Rcpp::RObject)data["format"], escapeHtml); + + + return value; + +} + + +Json::Value RObject_to_JsonValue(Rcpp::List obj, bool escapeHtml) +{ + bool atLeastOneNamed = false; + + Rcpp::RObject namesListRObject = obj.names(); + Rcpp::CharacterVector namesList; + + if(!namesListRObject.isNULL()) + { + namesList = namesListRObject; + + for(int row=0; row=0; row--) //We go backwards because in R the first entry of a name in a list is used. So to emulate this we go backwars and we override an earlier occurence. (aka you have two elements with the name "a" in a list and in R list$a returns the first occurence. This is now also the element visible in the json.) + { + std::string name(namesList[row]); + + if(name == "") + name = "element_" + std::to_string(row); + + val[name] = RObject_to_JsonValue((Rcpp::RObject)obj[row], escapeHtml); + } + else + for(int row=0; row 0) //it can be INT_MIN at least, but if we are doing a -1 on it anyhow it should just be bigger than 0 + charCol[i] = factorLevels[originalColumn[i] - 1]; + + df[col] = charCol; + } + + return df; +} diff --git a/src/adapters/rcpp/rcppConversions.h b/src/adapters/rcpp/rcppConversions.h new file mode 100644 index 00000000..0e877c74 --- /dev/null +++ b/src/adapters/rcpp/rcppConversions.h @@ -0,0 +1,189 @@ +#pragma once + +// Rcpp-specific conversion machinery, moved verbatim from the old jaspObject.h/.cpp +// (R-side behaviour must stay bit-identical). The functions used to be members of +// jaspObject; they are free functions now, with the per-object `_escapeHtml` flag +// passed explicitly by the callers. + +#include +#include +#include +#include +#include +#include +#include +#include "stringutils.h" + +inline bool isMixedRObject(Rcpp::RObject obj) { return obj.inherits("mixed"); } + +#define TO_INFINITY_AND_BEYOND \ +{ \ + double val = static_cast(obj[row]); \ + return R_IsNA(val) ? "" : \ + R_IsNaN(val) ? "NaN" : \ + val == std::numeric_limits::infinity() ? "\u221E" : \ + val == -1 * std::numeric_limits::infinity() ? "-\u221E" : \ + Json::Value((double)(obj[row])); \ +} + +template inline Json::Value RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row, bool escapeHtml) { return ""; } + +template inline Json::Value RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row, bool escapeHtml) { return ""; } + +template<> inline Json::Value RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row, bool escapeHtml) +{ + return obj[row] == NA_INTEGER ? "" : Json::Value((int)(obj[row])); +} + +template<> inline Json::Value RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row, bool escapeHtml) +{ + return obj[row] == NA_LOGICAL ? "" : Json::Value((bool)(obj[row])); +} + +template<> inline Json::Value RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row, bool escapeHtml) +{ + return obj[row] == NA_STRING ? "" : Json::Value(escapeHtml ? stringUtils::escapeHtmlStuff(std::string(obj[row])) : std::string(obj[row])); +} + +template<> inline Json::Value RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row, bool escapeHtml) TO_INFINITY_AND_BEYOND + +template<> inline Json::Value RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row, bool escapeHtml) { return obj[row] == NA_INTEGER ? "" : Json::Value((int)(obj[row])); } + +template<> inline Json::Value RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row, bool escapeHtml) { return obj[row] == NA_LOGICAL ? "" : Json::Value((bool)(obj[row])); } + +template<> inline Json::Value RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row, bool escapeHtml) { return obj[row] == NA_STRING ? "" : Json::Value(escapeHtml ? stringUtils::escapeHtmlStuff(std::string(obj[row])) : std::string(obj[row])); } + +template<> inline Json::Value RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row, bool escapeHtml) TO_INFINITY_AND_BEYOND + + +template inline std::vector RcppVector_to_VectorJson(Rcpp::Vector obj, bool escapeHtml) +{ + std::vector vec; + + for(int row=0; row inline std::vector> RcppMatrix_to_Vector2Json(Rcpp::Matrix obj, bool escapeHtml) +{ + std::vector> vecvec; + + for(int col=0; col vec; + + for(int row=0; row RList_to_VectorJson(Rcpp::List obj, bool escapeHtml); + +template inline Json::Value RObject_to_JsonValue(Rcpp::Matrix obj, bool escapeHtml) +{ + Json::Value val(Json::arrayValue); + + for(int col=0; col inline Json::Value RObject_to_JsonValue(Rcpp::Vector obj, bool escapeHtml) +{ + Json::Value val(""); + + if(obj.size() == 1) + val = RVectorEntry_to_JsonValue(obj, 0, escapeHtml); + else if(obj.size() > 1) + { + val = Json::Value(Json::arrayValue); + + for(int row=0; row MixedRcppVector_to_VectorJson(Rcpp::List obj, bool escapeHtml) +{ + std::vector vec; + for(int i=0; i RcppVector_to_VectorJson(Rcpp::RObject obj, bool escapeHtml, bool throwError=false) +{ + if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::NumericVector) obj, escapeHtml); + else if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::LogicalVector) obj, escapeHtml); + else if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::IntegerVector) obj, escapeHtml); + else if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::StringVector) obj, escapeHtml); + else if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::CharacterVector) obj, escapeHtml); + else if(isMixedRObject(obj)) return MixedRcppVector_to_VectorJson( (Rcpp::List) obj, escapeHtml); + else if(Rcpp::is(obj)) return RList_to_VectorJson((Rcpp::List) obj, escapeHtml); + else if(throwError) Rf_error("JASPjson::RcppVector_to_VectorJson received an SEXP that is not a Vector of some kind."); + + return std::vector({""}); +} + +template inline std::vector extractElementOrColumnNames(RCPP_CLASS rObj) +{ + Rcpp::RObject colNamesRObject = Rcpp::colnames(rObj), kolnamesRObject = rObj.names(); + Rcpp::CharacterVector colNamesList; + std::vector colNamesVec; + + if(!colNamesRObject.isNULL() || !kolnamesRObject.isNULL()) + { + colNamesList = !colNamesRObject.isNULL() ? colNamesRObject : kolnamesRObject; + + for(size_t col=0; col(colNamesList[col])); + } + + return colNamesVec; +} + +Rcpp::DataFrame convertFactorsToCharacters(Rcpp::DataFrame df); + +///Split an R list into a positional vector and a name->value map (named +///entries with a non-empty name), mirroring today's jaspList::setRows(Rcpp::List). +template inline std::pair, std::map> rcppListToRowsAndFields(Rcpp::List vec) +{ + std::vector rows; + std::map fields; + + for(auto v : vec) + rows.push_back(Rcpp::as(v)); + + Rcpp::RObject namesListRObject = vec.names(); + + if(!namesListRObject.isNULL()) + { + Rcpp::CharacterVector namesList = Rcpp::as(namesListRObject); + + for(int row=0; row(namesList[row])] = Rcpp::as(vec[row]); + } + + return {rows, fields}; +} diff --git a/src/adapters/rcpp/rcppHost.cpp b/src/adapters/rcpp/rcppHost.cpp new file mode 100644 index 00000000..89533bd9 --- /dev/null +++ b/src/adapters/rcpp/rcppHost.cpp @@ -0,0 +1,66 @@ +// R-backed implementations of the jaspHost seam. + +#include +#include "jaspObject.h" +#include "jaspHost.h" +#include "jaspResults.h" +#include "rcppPlot.h" +#include "rcppResults.h" + +void setJaspLogFunction(Rcpp::XPtr func) +{ + jaspHost::logString = *func; + + if(jaspHost::logString) + jaspHost::logString("Log string function received loud and clear!"); +} + +void rcppWireHostStore() +{ + jaspHost::storeObject = [](const std::string & envName, std::any obj) + { + Rcpp::RObject rObj = obj.has_value() ? std::any_cast(obj) : Rcpp::RObject(R_NilValue); + rcppSetObjectInEnv(envName, rObj); + }; + + jaspHost::fetchObject = [](const std::string & envName) -> std::any + { + return std::any(rcppGetObjectFromEnv(envName)); + }; + + jaspHost::objectExists = [](const std::string & envName) + { + return rcppObjectExistsInEnv(envName); + }; + + jaspHost::clearObjects = []() + { + // The R storage environment is cleared from the R side (.onAttach in zzzWrappers.R) + }; + + jaspHost::destroyObjectStore = []() + { + rcppDestroyStorageEnv(); + }; + + jaspHost::signalAnalysisAbort = []() + { + static Rcpp::Function signalAnalysisAbort = Rcpp::Environment::namespace_env("jaspBase")["signalAnalysisAbort"]; + signalAnalysisAbort(); + }; + + jaspHost::saveStateArchive = [](jaspResults & results, const std::string & jsonPath) + { + rcppSaveResultsAsRds(results, jsonPath); + }; + + jaspHost::renderPlot = [](jaspPlot & plot) + { + rcppRenderPlot(plot); + }; + + jaspHost::plotStateSync = [](jaspPlot & plot) + { + rcppSetUserPlotChangesFromRStateObject(plot); + }; +} diff --git a/src/adapters/rcpp/rcppHost.h b/src/adapters/rcpp/rcppHost.h new file mode 100644 index 00000000..a426a045 --- /dev/null +++ b/src/adapters/rcpp/rcppHost.h @@ -0,0 +1,15 @@ +#pragma once + +// R-backed implementations of the jaspHost seam (see src/core/jaspHost.h). +// Installed by rcppWireHostStore(), called from the jaspResults constructor. + +#include +#include "jaspObject.h" // logFuncDef + +void setJaspLogFunction( Rcpp::XPtr func ); + +/// Points the jaspHost object store at jaspResults::_RStorageEnv so that +/// R objects stored by jaspState/jaspPlot stay protected from R's GC, and +/// installs the R-backed plot rendering / state-sync callbacks. +/// Idempotent; called from the jaspResults constructor. +void rcppWireHostStore(); diff --git a/src/adapters/rcpp/rcppInterfaces.h b/src/adapters/rcpp/rcppInterfaces.h new file mode 100644 index 00000000..560c1cff --- /dev/null +++ b/src/adapters/rcpp/rcppInterfaces.h @@ -0,0 +1,287 @@ +#pragma once + +// R-facing *_Interface wrappers for classes that already moved to src/core/. +// This header grows as more classes move (commits 05-08 of the phase-1 plan). + +#include +#include "jaspObjectInterface.h" +#include "rcppConversions.h" +#include "rcppPlot.h" +#include "rcppContainer.h" +#include "rcppTableIngest.h" +#include "rcppColumn.h" +#include "jaspHtml.h" +#include "jaspQmlSource.h" +#include "jaspReport.h" +#include "jaspState.h" +#include "jaspPlot.h" +#include "jaspContainer.h" +#include "jaspList.h" +#include "jaspTable.h" +#include "jaspColumn.h" + +class jaspHtml_Interface : public jaspObject_Interface +{ +public: + jaspHtml_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + void setText(Rcpp::String newRawText) { static_cast(myJaspObject)->setText(std::string(newRawText)); } + Rcpp::String getText() { return static_cast(myJaspObject)->getText(); } + std::string getHtml() { return static_cast(myJaspObject)->getHtml(); } + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspHtml, std::string, _elementType, ElementType) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspHtml, std::string, _class, Class) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspHtml, std::string, _maxWidth, MaxWidth) + +}; + +RCPP_EXPOSED_CLASS_NODECL(jaspHtml_Interface) + +class jaspQmlSource_Interface : public jaspObject_Interface +{ +public: + jaspQmlSource_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspQmlSource, std::string, _sourceID, SourceID) + + void setValue(Rcpp::RObject obj) { jaspQmlSource * q = static_cast(myJaspObject); q->setValue(RObject_to_JsonValue(obj, q->getEscapeHtml())); } + std::string getValue() { return static_cast(myJaspObject)->getValue(); } +}; + +RCPP_EXPOSED_CLASS_NODECL(jaspQmlSource_Interface) + +class jaspReport_Interface : public jaspObject_Interface +{ +public: + jaspReport_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + void setText(Rcpp::String newRawText) { static_cast(myJaspObject)->setText(std::string(newRawText)); } + Rcpp::String getText() { return static_cast(myJaspObject)->getText(); } + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspReport, bool, _report, Report) +}; + +RCPP_EXPOSED_CLASS_NODECL(jaspReport_Interface) + +class jaspState_Interface : public jaspObject_Interface +{ +public: + jaspState_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + void setObject(Rcpp::RObject obj) { static_cast(myJaspObject)->setObject(std::any(obj)); } + Rcpp::RObject getObject() + { + std::any obj = static_cast(myJaspObject)->getObject(); + if(!obj.has_value()) + return R_NilValue; + try + { + return std::any_cast(obj); + } + catch(const std::bad_any_cast &) + { + return R_NilValue; + } + } +}; + +RCPP_EXPOSED_CLASS_NODECL(jaspState_Interface) + +class jaspPlot_Interface : public jaspObject_Interface +{ +public: + jaspPlot_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + void setPlotObject(Rcpp::RObject plotObject) + { + jaspPlot * plot = static_cast(myJaspObject); + Rcpp::List plotInfo = Rcpp::List::create( + Rcpp::_["obj"] = plotObject, + Rcpp::_["width"] = plot->_width, + Rcpp::_["height"] = plot->_height, + Rcpp::_["revision"] = plot->_revision); + plot->setPlotObject(std::any((Rcpp::RObject)plotInfo)); + } + Rcpp::RObject getPlotObject() { return rcppGetPlotObject(static_cast(myJaspObject)); } + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, std::string, _filePathPng, FilePathPng) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, std::string, _status, Status) + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, float, _aspectRatio, AspectRatio) + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, int, _width, Width) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, int, _height, Height) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, int, _revision, Revision) + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NO_NOTIFY(jaspPlot, bool, _editing, Editing) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NO_NOTIFY(jaspPlot, bool, _resizedByUser, ResizedByUser) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NO_NOTIFY(jaspPlot, std::string, _interactiveJsonData, InteractiveJsonData) + + ///Set/export machine-readable data from R: + /// plot$export <- list(medianDelta = 0.45, ciLow = 0.12, ciHigh = 0.78) + ///Appears in both the JSON results and the RDS (survives stripping). + void setExport(Rcpp::List exportData); + Rcpp::List getExport(); +}; + +RCPP_EXPOSED_CLASS_NODECL(jaspPlot_Interface) + +class jaspContainer_Interface : public jaspObject_Interface +{ +public: + jaspContainer_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + int length() { return ((jaspContainer*)myJaspObject)->length(); } + Rcpp::RObject at(std::string field) { return rcppContainerAt((jaspContainer*)myJaspObject, field); } + void insert(std::string field, Rcpp::RObject value) { rcppContainerInsert((jaspContainer*)myJaspObject, field, value); } + Rcpp::RObject findObjectWithUniqueNestedName(std::string uniqueNestedName); + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspContainer, bool, _initiallyCollapsed, InitiallyCollapsed) +}; + +RCPP_EXPOSED_CLASS_NODECL(jaspContainer_Interface) + +template +class jaspList_Interface : public jaspObject_Interface +{ +public: + jaspList_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + void insert(Rcpp::RObject field, T value) + { + if(Rcpp::is(field) || Rcpp::is(field)) + static_cast*>(myJaspObject)->insertIndex(Rcpp::as(field) - 1, value); + else if(Rcpp::is(field) || Rcpp::is(field)) + static_cast*>(myJaspObject)->insertField(Rcpp::as(field), value); + else + Rf_error("Did not get a number, integer or string to index on."); + } + + T at(Rcpp::RObject field) + { + if(Rcpp::is(field) || Rcpp::is(field)) + return static_cast*>(myJaspObject)->atIndex(Rcpp::as(field) - 1); + else if(Rcpp::is(field) || Rcpp::is(field)) + return static_cast*>(myJaspObject)->atField(Rcpp::as(field)); + else + Rf_error("Did not get a number, integer or string to index on."); + + return T(); + } + + void add(T value) { static_cast*>(myJaspObject)->add(value); } +}; + +typedef jaspList_Interface jaspStringlist_Interface; +typedef jaspList_Interface jaspDoublelist_Interface; +typedef jaspList_Interface jaspIntlist_Interface; +typedef jaspList_Interface jaspBoollist_Interface; + +RCPP_EXPOSED_CLASS_NODECL(jaspStringlist_Interface) +RCPP_EXPOSED_CLASS_NODECL(jaspDoublelist_Interface) +RCPP_EXPOSED_CLASS_NODECL(jaspIntlist_Interface) +RCPP_EXPOSED_CLASS_NODECL(jaspBoollist_Interface) + +#define JASPLIST_MODULE_EXPORT(CLASS_NAME_CPP, CLASS_NAME_R) \ +Rcpp::class_(CLASS_NAME_R) \ + .derives("jaspObject") \ + .method( "[[", &CLASS_NAME_CPP::at, "Access element by fieldname (string) or index (int) ") \ + .method( "[[<-", &CLASS_NAME_CPP::insert, "Insert an element under index (int) or fieldname (string)") \ + .method( "insert", &CLASS_NAME_CPP::insert, "Insert an element under index (int) or fieldname (string)") \ + .method( "add", &CLASS_NAME_CPP::add, "Add an element at the end of the indexable list") \ + JASP_OBJECT_FINALIZER_LAMBDA(CLASS_NAME_CPP) \ +; + +class jaspTable_Interface : public jaspObject_Interface +{ +public: + jaspTable_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + jaspStringlist_Interface getColNames() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colNames) ); } + jaspStringlist_Interface getColTypes() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colTypes) ); } + jaspStringlist_Interface getColTitles() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colTitles) ); } + jaspStringlist_Interface getColOvertitles() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colOvertitles) ); } + jaspStringlist_Interface getColFormats() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colFormats) ); } + jaspBoollist_Interface getColCombines() { return jaspBoollist_Interface( &(((jaspTable*)myJaspObject)->_colCombines) ); } + jaspStringlist_Interface getRowNames() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_rowNames) ); } + jaspStringlist_Interface getRowTitles() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_rowTitles) ); } + + void setColNames(Rcpp::List newNames) + { + auto rf = rcppListToRowsAndFields(newNames); + ((jaspTable*)myJaspObject)->setColNames(rf.first, rf.second); + } + void setColTypes(Rcpp::List newTypes) + { + auto rf = rcppListToRowsAndFields(newTypes); + ((jaspTable*)myJaspObject)->setColTypes(rf.first, rf.second); + } + void setColTitles(Rcpp::List newTitles) + { + auto rf = rcppListToRowsAndFields(newTitles); + ((jaspTable*)myJaspObject)->setColTitles(rf.first, rf.second); + } + void setColOvertitles(Rcpp::List newTitles) + { + auto rf = rcppListToRowsAndFields(newTitles); + ((jaspTable*)myJaspObject)->setColOvertitles(rf.first, rf.second); + } + void setColFormats(Rcpp::List newFormats) + { + auto rf = rcppListToRowsAndFields(newFormats); + ((jaspTable*)myJaspObject)->setColFormats(rf.first, rf.second); + } + void setColCombines(Rcpp::List newCombines) + { + auto rf = rcppListToRowsAndFields(newCombines); + ((jaspTable*)myJaspObject)->setColCombines(rf.first, rf.second); + } + void setRowNames(Rcpp::List newNames) + { + auto rf = rcppListToRowsAndFields(newNames); + ((jaspTable*)myJaspObject)->setRowNames(rf.first, rf.second); + } + void setRowTitles(Rcpp::List newTitles) + { + auto rf = rcppListToRowsAndFields(newTitles); + ((jaspTable*)myJaspObject)->setRowTitles(rf.first, rf.second); + } + + void addColumnInfo(Rcpp::RObject name, Rcpp::RObject title, Rcpp::RObject type, Rcpp::RObject format, Rcpp::RObject combine, Rcpp::RObject overtitle) { rcppTableAddColumnInfo((jaspTable*)myJaspObject, name, title, type, format, combine, overtitle); } + void addFootnote(Rcpp::RObject message, Rcpp::RObject symbol, Rcpp::RObject col_names, Rcpp::RObject row_names) { rcppTableAddFootnote((jaspTable*)myJaspObject, message, symbol, col_names, row_names); } + + void setData(Rcpp::RObject newData) { rcppTableSetData((jaspTable*)myJaspObject, newData); } + void addColumns(Rcpp::RObject newColumns) { rcppTableAddColumns((jaspTable*)myJaspObject, newColumns); } + + void addRows( Rcpp::RObject newRows, Rcpp::CharacterVector rowNames) { rcppTableAddRows((jaspTable*)myJaspObject, newRows, rowNames); } + void addRowsWithoutNames( Rcpp::RObject newRows) { rcppTableAddRows((jaspTable*)myJaspObject, newRows, Rcpp::CharacterVector()); } + void addRow( Rcpp::RObject newRow, Rcpp::CharacterVector rowNames) { rcppTableAddRow((jaspTable*)myJaspObject, newRow, rowNames); } + void addRowWithoutNames( Rcpp::RObject newRow) { rcppTableAddRow((jaspTable*)myJaspObject, newRow, Rcpp::CharacterVector()); } + void setColumn( std::string columnName, Rcpp::RObject column) { rcppTableSetColumn((jaspTable*)myJaspObject, columnName, column); } + + void setExpectedSize(size_t columns, size_t rows) { ((jaspTable*)myJaspObject)->setExpectedSize(columns, rows); } + void setExpectedRows(size_t rows) { ((jaspTable*)myJaspObject)->setExpectedRows(rows); } + void setExpectedColumns(size_t columns) { ((jaspTable*)myJaspObject)->setExpectedColumns(columns); } + + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspTable, bool, _transposeTable, TransposeTable) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspTable, bool, _transposeWithOvertitle, TransposeWithOvertitle) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspTable, std::string, _status, Status) + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspTable, bool, _showSpecifiedColumnsOnly, ShowSpecifiedColumnsOnly) +}; + +RCPP_EXPOSED_CLASS_NODECL(jaspTable_Interface) + +class jaspColumn_Interface : public jaspObject_Interface +{ +public: + jaspColumn_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} + + bool setScale( Rcpp::RObject scalarData, bool computed = false) { return static_cast(myJaspObject)->setScale(std::any(scalarData), computed); } + bool setOrdinal( Rcpp::RObject ordinalData, bool computed = false) { return static_cast(myJaspObject)->setOrdinal(std::any(ordinalData), computed); } + bool setNominal( Rcpp::RObject nominalData, bool computed = false) { return static_cast(myJaspObject)->setNominal(std::any(nominalData), computed); } + bool setNominalText(Rcpp::RObject nominalData, bool computed = false) { return static_cast(myJaspObject)->setNominal(std::any(nominalData), computed); } + //void removeFromData() { return static_cast(myJaspObject)->removeFromData(); } +}; + +RCPP_EXPOSED_CLASS_NODECL(jaspColumn_Interface) diff --git a/src/adapters/rcpp/rcppPlot.cpp b/src/adapters/rcpp/rcppPlot.cpp new file mode 100644 index 00000000..1cabe88c --- /dev/null +++ b/src/adapters/rcpp/rcppPlot.cpp @@ -0,0 +1,233 @@ +// R-backed plot logic moved from the old src/jaspPlot.cpp. Behaviour is kept +// identical: the plot object (an R list with obj/width/height/revision) lives +// in the jaspHost object store, which in the R build is backed by +// jaspResults::_RStorageEnv. + +#include "rcppPlot.h" +#include "jaspPlot.h" +#include "jaspHost.h" +#include "jaspResults.h" +#include "rcppConversions.h" +#include "rcppInterfaces.h" + +static Rcpp::RObject anyToRObject(const std::any & obj) +{ + if(!obj.has_value()) + return R_NilValue; + + try + { + return std::any_cast(obj); + } + catch(const std::bad_any_cast &) + { + return R_NilValue; + } +} + +Rcpp::RObject rcppGetPlotObjectFromEnvName(const std::string & envName) +{ + Rcpp::RObject plotInfoObj = anyToRObject(jaspHost::fetchObject(envName)); + + if (!plotInfoObj.isNULL() && Rcpp::is(plotInfoObj)) + { + + Rcpp::List plotInfoList = Rcpp::as(plotInfoObj); + if (plotInfoList.containsElementNamed("obj")) + return Rcpp::as(plotInfoList["obj"]); + + } + return R_NilValue; +} + +Rcpp::RObject rcppGetPlotObject(jaspPlot * plot) +{ + return rcppGetPlotObjectFromEnvName(plot->_envName); +} + +void rcppSetUserPlotChangesFromRStateObject(jaspPlot & plot) +{ + Rcpp::RObject plotInfoObj = anyToRObject(jaspHost::fetchObject(plot._envName)); + + if (plotInfoObj.isNULL() || !Rcpp::is(plotInfoObj)) + return; + + Rcpp::List plotInfoList = Rcpp::as(plotInfoObj); + + if (plotInfoList.containsElementNamed("width")) + plot._width = Rcpp::as(plotInfoList["width"]); + + if (plotInfoList.containsElementNamed("height")) + plot._height = Rcpp::as(plotInfoList["height"]); + + if (plotInfoList.containsElementNamed("revision")) + plot._revision = Rcpp::as(plotInfoList["revision"]); +} + +static Rcpp::List rcppGetOldPlotInfo(jaspPlot & plot, Rcpp::List & plotInfo) +{ + std::vector names; + plot.getUniqueNestedNameVector(names); + jaspPlot * oldPlot = dynamic_cast(plot.getOldObjectFromUniqueNestedNameVector(names)); + + if (oldPlot == nullptr) + { + jaspPrint("could not find an old plot"); + return Rcpp::List(); + } + jaspPrint("found a " + oldPlot->type() + " with name: " + oldPlot->name() + ". Resized by user: " + (oldPlot->_resizedByUser ? "yes" : "no")); + + if (oldPlot->_resizedByUser) + { + plot._width = oldPlot->_width; + plot._height = oldPlot->_height; + plotInfo["width"] = plot._width; + plotInfo["height"] = plot._height; + } + + if (oldPlot->_editOptions == Json::nullValue) + return Rcpp::List(); + else + return Rcpp::List::create( + Rcpp::_["editOptions"] = Rcpp::String(oldPlot->_editOptions.toStyledString()), + Rcpp::_["oldPlot"] = rcppGetPlotObject(oldPlot) + ); + +} + +void rcppRenderPlot(jaspPlot & plot) +{ + // if a png exists the plot was already rendered, unless we're editing it + if (plot._filePathPng != "" && !plot._editing) + return; + + // empty plots were added to the state + Rcpp::RObject plotInfoObj = anyToRObject(jaspHost::fetchObject(plot._envName)); + if (plotInfoObj.isNULL()) + return; + + Rcpp::List plotInfo = Rcpp::as(plotInfoObj); + Rcpp::RObject obj = plotInfo["obj"]; + + if(!obj.isNULL()) + { + + jaspPrint("Now rendering a plot with name: " + plot.name()); + + static Rcpp::Function tryToWriteImage = Rcpp::Environment::namespace_env("jaspBase")["tryToWriteImageJaspResults"]; + Rcpp::List writeResult, oldPlotInfo; + if (plot._editing) + { + oldPlotInfo = Rcpp::List(); + plot._revision++; + writeResult = tryToWriteImage(Rcpp::_["width"] = plot._width, Rcpp::_["height"] = plot._height, Rcpp::_["plot"] = obj, Rcpp::_["oldPlotInfo"] = oldPlotInfo, Rcpp::_["relativePathpng"] = plot._filePathPng, Rcpp::_["relativePathJson"] = Rcpp::String(plot._interactiveJsonData)); + } + else + { + //getOldPlotInfo may update height & width + oldPlotInfo = rcppGetOldPlotInfo(plot, plotInfo); + writeResult = tryToWriteImage(Rcpp::_["width"] = plot._width, Rcpp::_["height"] = plot._height, Rcpp::_["plot"] = obj, Rcpp::_["oldPlotInfo"] = oldPlotInfo, Rcpp::_["relativePathpng"] = R_NilValue); + } + + // we need to overwrite plot functions with their recordedplot result + if(Rcpp::is(obj) && writeResult.containsElementNamed("obj")) + plotInfo["obj"] = writeResult["obj"]; + + if(writeResult.containsElementNamed("png")) + plot._filePathPng = Rcpp::as(writeResult["png"]); + + plot._editOptions = Json::nullValue; + + if(writeResult.containsElementNamed("editOptions") && !Rf_isNull(writeResult["editOptions"])) + { + std::string editOptionsStr = Rcpp::as(writeResult["editOptions"]); + + if(editOptionsStr != "") + { + plot._editOptions = Json::objectValue; + Json::Reader().parse(editOptionsStr, plot._editOptions); + } + } + + if(writeResult.containsElementNamed("interactive")) + { + plot._interactive = Rcpp::as(writeResult["interactive"]); + if (plot._interactive) + { + if(writeResult.containsElementNamed("interactiveConvertError")) + { + plot._interactiveConvertError = Rcpp::as(writeResult["interactiveConvertError"]); + plot._interactiveJsonData = ""; + } + else if (writeResult.containsElementNamed("interactiveJsonData")) + { + std::string interactiveJsonDataStr = Rcpp::as(writeResult["interactiveJsonData"]); + plot._interactiveJsonData = interactiveJsonDataStr; + plot._interactiveConvertError = ""; + } + else + plot._interactiveConvertError = "Unknown error converting interactive plot to JSON"; + } + } + + + if(writeResult.containsElementNamed("error")) + plot.setError(Rcpp::as(writeResult["error"])); + else + plot.clearError(); + + plot.complete(); + + jaspHost::storeObject(plot._envName, std::any((Rcpp::RObject)plotInfo)); + } +} + +Rcpp::List rcppPlotToRObject(jaspPlot * plot) +{ + Rcpp::List lst = Rcpp::List::create(Rcpp::Named("plotObject") = rcppGetPlotObject(plot)); + lst.attr("title") = plot->_title; + lst.attr("class") = Rcpp::CharacterVector({"jaspPlotWrapper", "jaspWrapper"}); + + // Include the export field so RoboReport and other RDS consumers can + // access machine-readable data (e.g., computed effect sizes) that the + // analysis author tagged onto the plot. Survives RDS stripping. + if (!plot->_export.isNull()) + { + static Rcpp::Function fromJSON_export = Rcpp::Environment::namespace_env("jaspBase")["fromJSON"]; + Json::StreamWriterBuilder builder; + std::string exportJson = Json::writeString(builder, plot->_export); + lst["export"] = Rcpp::as(fromJSON_export(exportJson)); + } + + // the reason this function is not const + Rcpp::Environment jaspObjectEnvironment = Rcpp::new_env(); + jaspObjectEnvironment.assign("jaspObject", Rcpp::as(Rcpp::wrap(jaspPlot_Interface(plot)))); + lst.attr("jaspObjectEnvironment") = jaspObjectEnvironment; + + return lst; +} + +// ---- jaspPlot_Interface::setExport / getExport ---- +// Defined here (not inline in the header) because the conversions between +// Rcpp::List and Json::Value require non-trivial logic. + +void jaspPlot_Interface::setExport(Rcpp::List exportData) +{ + jaspPlot* plot = (jaspPlot*)myJaspObject; + plot->_export = RObject_to_JsonValue(exportData, plot->getEscapeHtml()); + myJaspObject->notifyParentOfChanges(); +} + +Rcpp::List jaspPlot_Interface::getExport() +{ + jaspPlot* plot = (jaspPlot*)myJaspObject; + if (plot->_export.isNull() || plot->_export.empty()) + return Rcpp::List(); + + // Convert Json::Value -> string -> R list via jsonlite (jaspBase dep). + static Rcpp::Function fromJSON_getExport = + Rcpp::Environment::namespace_env("jaspBase")["fromJSON"]; + Json::StreamWriterBuilder builder; + std::string jsonStr = Json::writeString(builder, plot->_export); + return Rcpp::as(fromJSON_getExport(jsonStr)); +} diff --git a/src/adapters/rcpp/rcppPlot.h b/src/adapters/rcpp/rcppPlot.h new file mode 100644 index 00000000..74c0a137 --- /dev/null +++ b/src/adapters/rcpp/rcppPlot.h @@ -0,0 +1,14 @@ +#pragma once + +// R-backed plot helpers: the original jaspPlot rendering/state logic that +// depends on R (tryToWriteImageJaspResults, stored R plot objects, toRObject). + +#include + +class jaspPlot; + +void rcppRenderPlot(jaspPlot & plot); +Rcpp::RObject rcppGetPlotObject(jaspPlot * plot); +Rcpp::RObject rcppGetPlotObjectFromEnvName(const std::string & envName); +Rcpp::List rcppPlotToRObject(jaspPlot * plot); +void rcppSetUserPlotChangesFromRStateObject(jaspPlot & plot); diff --git a/src/adapters/rcpp/rcppResults.cpp b/src/adapters/rcpp/rcppResults.cpp new file mode 100644 index 00000000..ea72ce63 --- /dev/null +++ b/src/adapters/rcpp/rcppResults.cpp @@ -0,0 +1,220 @@ +// R-half of jaspResults; moved verbatim (behaviour and field names kept +// identical) from the old src/jaspResults.{h,cpp}. The R-free core lives in +// src/core/jaspResults.{h,cpp} and reaches everything R-specific through +// jaspHost callbacks installed here. + +#include +#include + +#include "rcppResults.h" +#include "rcppHost.h" +#include "rcppPlot.h" +#include "rcppToRObject.h" +#include "jaspHost.h" +#include "jaspState.h" + +// The R binding layer itself: RCPP_MODULE(jaspResults) + all *_Interface +// registrations (moved from the old src/jaspResults.cpp include). +#include "jaspModuleRegistration.h" + +// We need this environment to store R objects in a "named" fashion, because +// then the garbage collector doesn't throw away everything... +// Inside JASP a fresh child of the global env (reachable as +// globalenv()$RStorageEnv), otherwise jaspBase's .plotStateStorage. Moved +// from the old static jaspResults::_RStorageEnv member. +static Rcpp::Environment * _RStorageEnv = nullptr; + +static void rcppCreateStorageEnv() +{ + if(_RStorageEnv != nullptr) + delete _RStorageEnv; + + if(jaspResults::isInsideJASP()) + { + Rcpp::Environment::global_env()["RStorageEnv"] = Rcpp::Environment::global_env().new_child(true); + _RStorageEnv = new Rcpp::Environment(Rcpp::Environment::global_env()["RStorageEnv"]); + } + else + _RStorageEnv = new Rcpp::Environment(Rcpp::as(Rcpp::Environment::namespace_env("jaspBase")[".plotStateStorage"])); +} + +void rcppDestroyStorageEnv() +{ + if(_RStorageEnv != nullptr) + delete _RStorageEnv; + + _RStorageEnv = nullptr; +} + +/// The storage env used to be created only in the jaspResults constructor, +/// so a jaspState/jaspPlot store access before any jaspResults existed +/// dereferenced nullptr. Lazily create it with the same inside/outside-JASP +/// selection instead. +static void rcppEnsureStorageEnv() +{ + if(_RStorageEnv == nullptr) + rcppCreateStorageEnv(); +} + +Rcpp::RObject rcppGetObjectFromEnv(std::string envName) +{ + rcppEnsureStorageEnv(); + if(_RStorageEnv->exists(envName)) + return (*_RStorageEnv)[envName]; + return R_NilValue; +} + +void rcppSetObjectInEnv(std::string envName, Rcpp::RObject obj) +{ + rcppEnsureStorageEnv(); + (*_RStorageEnv)[envName] = obj; +} + +bool rcppObjectExistsInEnv(std::string envName) +{ + rcppEnsureStorageEnv(); + return _RStorageEnv->exists(envName); +} + +void rcppSetSendFunc(Rcpp::XPtr sendFunc) +{ + jaspResults::setSendFunc(*sendFunc); +} + +void rcppSetPollMessagesFunc(Rcpp::XPtr pollFunc) +{ + jaspResults::setPollMessagesFunc(*pollFunc); +} + +void rcppStaticStartProgressbar(int expectedTicks, Rcpp::String label) +{ + jaspResults::staticStartProgressbar(expectedTicks, std::string(label)); +} + +Rcpp::String rcppWriteSealFilename() +{ + return jaspResults::writeSealFilename(); +} + +void rcppFillEnvironmentWithStateObjects(Rcpp::List state) +{ + if(state.containsElementNamed("figures")) + { + //Let's try to load all previous plots from the state! + Rcpp::List figures = state["figures"]; + + for(Rcpp::List plotInfo : figures) + if(plotInfo.containsElementNamed("envName") && plotInfo.containsElementNamed("obj")) + { + std::string envName = Rcpp::as(plotInfo["envName"]); + (*_RStorageEnv)[envName] = plotInfo; + } + } + + if(state.containsElementNamed("other")) + { + //Let's try to load all previous plots from the state! + Rcpp::List others = state["other"]; + Rcpp::List names = others.names(); + + for(std::string name : names) + (*_RStorageEnv)[name] = others[name]; + } +} + +void rcppSaveResultsAsRds(jaspResults & results, const std::string & jsonPath) +{ + if (std::getenv("JASP_RESULTS_RDS") == nullptr) return; + + // Also write results as an RDS file alongside the JSON + std::string rdsPath = jsonPath; + size_t dotPos = rdsPath.rfind(".json"); + if(dotPos != std::string::npos) + rdsPath.replace(dotPos, 5, ".rds"); + else + rdsPath += ".rds"; + + // By default, strip bulky environments and plot objects from the + // RDS tree before saving. This keeps the file small (KB) for + // consumers like RoboReport. + // Users can opt out to get the full toRObject() tree (e.g. for + // debugging) by setting the env var: JASP_RDS_STRIP=FALSE (or 0/no) + Rcpp::RObject rdsObject = rcppToRObject(&results); + const char* stripEnvVal = std::getenv("JASP_RDS_STRIP"); + bool shouldStrip = (stripEnvVal == nullptr) || + (strcmp(stripEnvVal, "FALSE") != 0 && strcmp(stripEnvVal, "0") != 0 && + strcmp(stripEnvVal, "NO") != 0 && strcmp(stripEnvVal, "no") != 0 && + strcmp(stripEnvVal, "No") != 0); + if (shouldStrip) + { + Rcpp::Environment jaspBaseEnv = Rcpp::Environment::namespace_env("jaspBase"); + Rcpp::Function stripEnv = jaspBaseEnv[".jaspResults_stripEnv"]; + rdsObject = stripEnv(rdsObject); + } + Rcpp::Function saveRDS("saveRDS"); + saveRDS(rdsObject, rdsPath); + jaspPrint("Saved jaspResults as RDS to: '" + rdsPath + "'"); +} + +jaspResults * rcppCreateJaspResults(Rcpp::String title, Rcpp::RObject oldState) +{ + rcppWireHostStore(); // idempotent: point jaspHost at the R env store + R callbacks + rcppCreateStorageEnv(); // the old constructor's _RStorageEnv (re)creation + + jaspResults * results = new jaspResults(std::string(title)); + + bool imNotReincarnatedAfterBeingMurdered = results->lastWriteWorked(); + + if(imNotReincarnatedAfterBeingMurdered && !oldState.isNULL() && Rcpp::is(oldState)) + rcppFillEnvironmentWithStateObjects(Rcpp::as(oldState)); + + results->loadResultsIfLastWriteWorked(); + + return results; +} + +Rcpp::List rcppGetPlotObjectsForState(jaspResults * results) +{ + Rcpp::List returnThis; + Rcpp::Shield protectList(returnThis); + + for(const jaspPlotStateEntry & entry : results->harvestPlotObjects()) + { + Rcpp::List pngImg; + pngImg["obj"] = rcppGetPlotObjectFromEnvName(entry.envName); + pngImg["width"] = entry.width; + pngImg["height"] = entry.height; + pngImg["revision"] = entry.revision; + pngImg["envName"] = entry.envName; + pngImg["getUnique"] = entry.uniqueNestedName; + returnThis[entry.filePathPng] = pngImg; + } + + return returnThis; +} + +Rcpp::List rcppGetOtherObjectsForState(jaspResults * results) +{ + Rcpp::List returnThis; + Rcpp::Shield protectList(returnThis); + + for(const std::string & envName : results->harvestStateEnvNames()) + { + std::any stored = jaspHost::fetchObject(envName); + if(stored.has_value()) + returnThis[envName] = std::any_cast(stored); + } + + return returnThis; +} + +Rcpp::List rcppGetKeepList(jaspResults * results) +{ + std::vector keepVec = results->getKeepListVector(); + + Rcpp::List keep(static_cast(keepVec.size())); + for(size_t i = 0; i < keepVec.size(); i++) + keep[i] = keepVec[i]; + + return keep; +} diff --git a/src/adapters/rcpp/rcppResults.h b/src/adapters/rcpp/rcppResults.h new file mode 100644 index 00000000..d60f9ad9 --- /dev/null +++ b/src/adapters/rcpp/rcppResults.h @@ -0,0 +1,87 @@ +#pragma once + +// R-half of jaspResults (see src/core/jaspResults.h for the R-free core). +// +// Everything that used to live in the old src/jaspResults.{h,cpp} and needs R +// is here, behaviour kept identical: +// - the R storage environment (GC-safe named store for plot/state R objects), +// - filling it with old state objects on construction, +// - the RDS state archive (saveStateArchive; JASP_RESULTS_RDS/JASP_RDS_STRIP), +// - the Rcpp::List harvests (getPlotObjectsForState/getOtherObjectsForState/ +// getKeepList shapes used by common.R::finishJaspResults), +// - XPtr registration of the send/poll function pointers, +// - the R-facing jaspResults_Interface, +// - the signalAnalysisAbort R call (installed on jaspHost). + +#include +#include "jaspResults.h" +#include "rcppInterfaces.h" + +/// The R-side half of the old jaspResults constructor: sets up the jaspHost +/// callbacks + R storage env, fills the env with old state objects and loads +/// previous results from disk, exactly in the old order. +jaspResults * rcppCreateJaspResults(Rcpp::String title, Rcpp::RObject oldState); + +/// Old-state objects from `.retrieveState()`: figures[[$envName]] -> plotInfo +/// lists, other[[name]] -> stored objects. Moved verbatim from the old +/// jaspResults::fillEnvironmentWithStateObjects. +void rcppFillEnvironmentWithStateObjects(Rcpp::List state); + +/// XPtr unwrappers for the module registration (R: setSendFunc etc). +void rcppSetSendFunc(Rcpp::XPtr sendFunc); +void rcppSetPollMessagesFunc(Rcpp::XPtr pollFunc); + +/// Keep the pre-split R-visible signatures (Rcpp::String) on the module. +void rcppStaticStartProgressbar(int expectedTicks, Rcpp::String label); +Rcpp::String rcppWriteSealFilename(); + +/// Direct access to the R storage env (used by rcppWireHostStore and the +/// old jaspResults::getObjectFromEnv/setObjectInEnv/objectExistsInEnv callers). +Rcpp::RObject rcppGetObjectFromEnv(std::string envName); +void rcppSetObjectInEnv(std::string envName, Rcpp::RObject obj); +bool rcppObjectExistsInEnv(std::string envName); + +/// Deletes the Rcpp wrapper of the R storage env (old ~jaspResults; wired as +/// jaspHost::destroyObjectStore). +void rcppDestroyStorageEnv(); + +/// RDS state archive alongside jaspResults.json, gated on JASP_RESULTS_RDS +/// (old RDS branch of jaspResults::saveResults; wired as +/// jaspHost::saveStateArchive). +void rcppSaveResultsAsRds(jaspResults & results, const std::string & jsonPath); + +/// Rcpp::List rebuilders for today's state harvest shapes +/// (common.R::finishJaspResults depends on the exact field names). +Rcpp::List rcppGetPlotObjectsForState(jaspResults * results); +Rcpp::List rcppGetOtherObjectsForState(jaspResults * results); +Rcpp::List rcppGetKeepList(jaspResults * results); + +class jaspResults_Interface : public jaspContainer_Interface +{ +public: + jaspResults_Interface(jaspObject * dataObj) : jaspContainer_Interface(dataObj) {} + + void send() { ((jaspResults*)myJaspObject)->send(); } + void complete() { ((jaspResults*)myJaspObject)->complete(); } + void saveResults() { ((jaspResults*)myJaspObject)->saveResults(); } + void finishWriting() { ((jaspResults*)myJaspObject)->finishWriting(); } + Rcpp::List getOtherObjectsForState() { return rcppGetOtherObjectsForState((jaspResults*)myJaspObject); } + Rcpp::List getPlotObjectsForState() { return rcppGetPlotObjectsForState((jaspResults*)myJaspObject); } + Rcpp::List getKeepList() { return rcppGetKeepList((jaspResults*)myJaspObject); } + std::string getResults() { return ((jaspResults*)myJaspObject)->getResults(); } + + void setErrorMessage(Rcpp::String msg, std::string errorStatus) { ((jaspResults*)myJaspObject)->setErrorMessage(msg, errorStatus); } + + void setOptions(std::string opts) { ((jaspResults*)myJaspObject)->setOptions(opts); } + void changeOptions(std::string opts) { ((jaspResults*)myJaspObject)->changeOptions(opts); } + + void setStatus(std::string status) { ((jaspResults*)myJaspObject)->setStatus(status); } + std::string getStatus() { return ((jaspResults*)myJaspObject)->getStatus(); } + + void prepareForWriting() { ((jaspResults*)myJaspObject)->prepareForWriting(); } + + JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspResults, std::string, _relativePathKeep, RelativePathKeep) +}; + + +RCPP_EXPOSED_CLASS_NODECL(jaspResults_Interface) diff --git a/src/adapters/rcpp/rcppTableIngest.cpp b/src/adapters/rcpp/rcppTableIngest.cpp new file mode 100644 index 00000000..af64b6a9 --- /dev/null +++ b/src/adapters/rcpp/rcppTableIngest.cpp @@ -0,0 +1,532 @@ +// R-backed jaspTable ingest & export, moved verbatim from the old +// src/jaspTable.{h,cpp}: the SEXP dispatch and R-attribute name extraction +// that feed the neutral cell storage in src/core/jaspTable.h, plus the +// NULL-based addColumnInfo/addFootnote and toRObject() (incl. mixed columns). + +#include "rcppTableIngest.h" +#include "jaspTable.h" +#include "rcppConversions.h" +#include "rcppInterfaces.h" + +static size_t rcppLengthFromList(Rcpp::List list) { return list.size(); } +template static size_t rcppLengthFromVector(Rcpp::Vector vec) { return vec.size(); } + +static size_t rcppLengthFromRObject(Rcpp::RObject rObj) +{ + if(rObj.isNULL()) return 0; + else if(Rcpp::is(rObj)) return rcppLengthFromList((Rcpp::List) rObj); + else if(Rcpp::is(rObj)) return rcppLengthFromVector((Rcpp::NumericVector) rObj); + else if(Rcpp::is(rObj)) return rcppLengthFromVector((Rcpp::LogicalVector) rObj); + else if(Rcpp::is(rObj)) return rcppLengthFromVector((Rcpp::IntegerVector) rObj); + else if(Rcpp::is(rObj)) return rcppLengthFromVector((Rcpp::StringVector) rObj); + else if(Rcpp::is(rObj)) return rcppLengthFromVector((Rcpp::CharacterVector) rObj); + else Rf_error("Unexpected type.."); + + return 0; + +} + +template static std::vector rcppExtractRowNames(jaspTable * table, RCPP_CLASS rObj, bool setRowNamesInTable=false) +{ + Rcpp::RObject rowNamesRObject = Rcpp::rownames(rObj), rijnamesRObject = rObj.attr("row.names"); + Rcpp::CharacterVector rowNamesList; + std::vector rowNamesVec; + + if(!rowNamesRObject.isNULL() || !rijnamesRObject.isNULL()) + { + rowNamesList = !rowNamesRObject.isNULL() ? rowNamesRObject : rijnamesRObject; + + for(size_t row=0; row(rowNamesList[row])); + + if(setRowNamesInTable && rowNamesList[row] != "" && (table->_rowNames.rowCount() <= row || table->_rowNames[row] == "")) //Add new rowNames or overwrite unset ones but if the user took the trouble to manually set it then just leave it I guess? + table->_rowNames[row] = rowNamesList[row]; + } + } + + return rowNamesVec; +} + +template static void rcppSetDataFromVector(jaspTable * table, Rcpp::Vector newData) +{ + std::vector localColNames = extractElementOrColumnNames(newData); + rcppExtractRowNames(table, newData, true); + + table->_data.clear(); + auto cols = RcppVector_to_VectorJson(newData, table->getEscapeHtml()); + + for(int col=0; coladdOrSetColumnInData(std::vector({cols[col]}), localColNames.size() > col ? localColNames[col] : ""); +} + +static void rcppSetDataFromList(jaspTable * table, Rcpp::List newData) +{ + std::vector localColNames = extractElementOrColumnNames(newData); + rcppExtractRowNames(table, newData, true); + + table->_data.clear(); + for(size_t col=0; coladdOrSetColumnInData(RcppVector_to_VectorJson((Rcpp::RObject)newData[col], table->getEscapeHtml()), localColNames.size() > col ? localColNames[col] : ""); +} + +template static void rcppSetDataFromMatrix(jaspTable * table, Rcpp::Matrix newData) +{ + std::vector localColNames = extractElementOrColumnNames(newData); + rcppExtractRowNames(table, newData, true); + + std::vector> jsonMat = RcppMatrix_to_Vector2Json(newData, table->getEscapeHtml()); + + table->_data.clear(); + for(size_t col=0; coladdOrSetColumnInData(jsonMat[col], localColNames.size() > col ? localColNames[col] : ""); +} + +template static void rcppAddColumnFromVector(jaspTable * table, Rcpp::Vector newData) +{ + table->setRowNamesWhereApplicable(extractElementOrColumnNames(newData)); + + table->_data.push_back(RcppVector_to_VectorJson(newData, table->getEscapeHtml())); +} + +template static void rcppSetColumnFromVector(jaspTable * table, Rcpp::Vector newData, size_t col) +{ + table->setRowNamesWhereApplicable(extractElementOrColumnNames(newData)); + + table->setColumnCellsAt(RcppVector_to_VectorJson(newData, table->getEscapeHtml()), col); +} + +static void rcppSetColumnFromMixedVector(jaspTable * table, Rcpp::List newData, size_t col) +{ + table->setRowNamesWhereApplicable(extractElementOrColumnNames(newData)); + + table->setColumnCellsAt(MixedRcppVector_to_VectorJson(newData, table->getEscapeHtml()), col); +} + +static void rcppSetColumnFromList(jaspTable * table, Rcpp::List column, int colIndex) +{ + std::vector localRowNames = extractElementOrColumnNames(column); + table->setRowNamesWhereApplicable(localRowNames); + + std::vector cells; + for(int row=0; row jsonVec = RcppVector_to_VectorJson((Rcpp::RObject)column[row], table->getEscapeHtml(), false); + cells.push_back(jsonVec.size() > 0 ? jsonVec[0u] : Json::nullValue); + } + + table->setColumnCellsAt(cells, colIndex); +} + +template static void rcppAddColumnsFromMatrix(jaspTable * table, Rcpp::Matrix newData) +{ + std::vector localColNames = extractElementOrColumnNames(newData); + rcppExtractRowNames(table, newData, true); + + std::vector> jsonMat = RcppMatrix_to_Vector2Json(newData, table->getEscapeHtml()); + + for(size_t col=0; coladdOrSetColumnInData(jsonMat[col], localColNames.size() > col ? localColNames[col] : ""); +} + +static void rcppAddColumnsFromList(jaspTable * table, Rcpp::List newData) +{ + size_t elementLenghts = 0; + for(int el=0; el 1) //each entry is 1 or 0, this must be a single row with columnnames and not a set of rows with rownames.. + { + Rcpp::List newColList; + auto shield = new Rcpp::Shield(newColList); + newColList.push_back(newData); + rcppAddColumnsFromList(table, newColList); + delete shield; + + return; + } + + std::vector localColNames = extractElementOrColumnNames(newData); + rcppExtractRowNames(table, newData, true); + + for(int col=0; coladdOrSetColumnInData(RcppVector_to_VectorJson((Rcpp::RObject)newData[col], table->getEscapeHtml(), false), localColNames.size() > col ? localColNames[col] : ""); +} + +template static void rcppAddRowFromVector(jaspTable * table, Rcpp::Vector newData, Rcpp::CharacterVector newRowNames) +{ + std::vector localColNames = extractElementOrColumnNames(newData); + + auto row = RcppVector_to_VectorJson(newData, table->getEscapeHtml()); + + int equalizedColumnsLength = table->equalizeColumnsLengths(); + int previouslyAddedUnnamedCols = 0; + + for(int row=0; row_rowNames[row + equalizedColumnsLength] = newRowNames[row]; + + for(int col=0; colpushbackToColumnInData(std::vector({row[col]}), localColNames.size() > col ? localColNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamedCols); + +} + +static void rcppAddRowsFromList(jaspTable * table, Rcpp::List newData, Rcpp::CharacterVector newRowNames) +{ + int equalizedColumnsLength = table->equalizeColumnsLengths(), + previouslyAddedUnnamedCols = 0; + + std::vector localRowNames = extractElementOrColumnNames(newData); + + for(size_t row=0; row_rowNames[row + equalizedColumnsLength] = localRowNames[row]; + + for(size_t row=0; row_rowNames[row + equalizedColumnsLength] = newRowNames[row]; + + for(size_t row=0; row localColNames; + + if(Rcpp::is(rij)) + localColNames = extractElementOrColumnNames(Rcpp::as(rij)); + + auto jsonRij = RcppVector_to_VectorJson(rij, table->getEscapeHtml()); + + for(size_t col=0; colpushbackToColumnInData(std::vector({jsonRij[col]}), localColNames.size() > col ? localColNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamedCols); + + equalizedColumnsLength = table->equalizeColumnsLengths(); + } +} + +static void rcppAddRowFromList(jaspTable * table, Rcpp::List newData, Rcpp::CharacterVector newRowNames) +{ + Rcpp::List newRowList; + auto shield = new Rcpp::Shield(newRowList); + newRowList.push_back(newData); + rcppAddRowsFromList(table, newRowList, newRowNames); + delete shield; +} + +static void rcppAddRowsFromDataFrame(jaspTable * table, Rcpp::DataFrame newData) +{ + newData = convertFactorsToCharacters(newData); + int equalizedColumnsLength = table->equalizeColumnsLengths(); + int previouslyAddedUnnamedCols = 0; + + std::vector localColNames = extractElementOrColumnNames(newData); + + for(size_t col=0; colgetEscapeHtml()); + previouslyAddedUnnamedCols = table->pushbackToColumnInData(jsonKolom, localColNames.size() > col ? localColNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamedCols); + } + +} + +template static void rcppAddRowsFromMatrix(jaspTable * table, Rcpp::Matrix newData, Rcpp::CharacterVector newRowNames) +{ + std::vector localColNames = extractElementOrColumnNames(newData); + // ??? something with rownames? rcppExtractRowNames(table, newData, true); + + int equalizedColumnsLength = table->equalizeColumnsLengths(); + int previouslyAddedUnnamedCols = 0; + + for(int row=0; row_rowNames[row + equalizedColumnsLength] = newRowNames[row]; + + auto jsonMatrix = RcppMatrix_to_Vector2Json(newData, table->getEscapeHtml()); + + for(int col=0; colpushbackToColumnInData(std::vector({jsonMatrix[col]}), localColNames.size() > col ? localColNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamedCols); +} + +void rcppTableSetData(jaspTable * table, Rcpp::RObject newData) +{ +#ifdef JASP_RESULTS_DEBUG_TRACES + jaspPrint("jaspTable::setData"); +#endif + if(newData.isNULL()) + { + table->_data.clear(); + return; + } + + //Maybe this is overkill? + if(Rcpp::is(newData)) rcppSetDataFromList(table, convertFactorsToCharacters((Rcpp::DataFrame) newData)); + else if(Rcpp::is(newData)) rcppSetDataFromList(table, (Rcpp::List) newData); + + else if(Rcpp::is(newData)) rcppSetDataFromMatrix(table, (Rcpp::NumericMatrix) newData); + else if(Rcpp::is(newData)) rcppSetDataFromMatrix(table, (Rcpp::LogicalMatrix) newData); + else if(Rcpp::is(newData)) rcppSetDataFromMatrix(table, (Rcpp::IntegerMatrix) newData); + else if(Rcpp::is(newData)) rcppSetDataFromMatrix(table, (Rcpp::StringMatrix) newData); + else if(Rcpp::is(newData)) rcppSetDataFromMatrix(table, (Rcpp::CharacterMatrix) newData); + + else if(Rcpp::is(newData)) rcppSetDataFromVector(table, (Rcpp::NumericVector) newData); + else if(Rcpp::is(newData)) rcppSetDataFromVector(table, (Rcpp::LogicalVector) newData); + else if(Rcpp::is(newData)) rcppSetDataFromVector(table, (Rcpp::IntegerVector) newData); + else if(Rcpp::is(newData)) rcppSetDataFromVector(table, (Rcpp::StringVector) newData); + else if(Rcpp::is(newData)) rcppSetDataFromVector(table, (Rcpp::CharacterVector) newData); + + else + Rf_error("Cannot set this kind of data to a jaspTable, it is not understood. Try a list, dataframe, vector or matrix instead."); + + table->notifyParentOfChanges(); +} + +void rcppTableSetColumn(jaspTable * table, std::string columnName, Rcpp::RObject column) +{ + int colIndex = table->getDesiredColumnIndexFromNameForColumnAdding(columnName); + + if(Rcpp::is(column)) rcppSetColumnFromVector(table, (Rcpp::NumericVector) column, colIndex); + else if(Rcpp::is(column)) rcppSetColumnFromVector(table, (Rcpp::LogicalVector) column, colIndex); + else if(Rcpp::is(column)) rcppSetColumnFromVector(table, (Rcpp::IntegerVector) column, colIndex); + else if(Rcpp::is(column)) rcppSetColumnFromVector(table, (Rcpp::StringVector) column, colIndex); + else if(Rcpp::is(column)) rcppSetColumnFromVector(table, (Rcpp::CharacterVector) column, colIndex); + else if(isMixedRObject(column)) rcppSetColumnFromMixedVector(table, (Rcpp::List) column, colIndex); + else if(Rcpp::is(column)) rcppSetColumnFromList(table, (Rcpp::List) column, colIndex); + else Rf_error("Did not get a vector or list as column.."); + + table->notifyParentOfChanges(); +} + +void rcppTableAddColumns(jaspTable * table, Rcpp::RObject newData) +{ + if(newData.isNULL()) + return; + + //Maybe this is overkill? + if(Rcpp::is(newData)) rcppAddColumnsFromList(table, convertFactorsToCharacters((Rcpp::DataFrame) newData)); + else if(Rcpp::is(newData)) rcppAddColumnsFromList(table, (Rcpp::List) newData); + + else if(Rcpp::is(newData)) rcppAddColumnsFromMatrix(table, (Rcpp::NumericMatrix) newData); + else if(Rcpp::is(newData)) rcppAddColumnsFromMatrix(table, (Rcpp::LogicalMatrix) newData); + else if(Rcpp::is(newData)) rcppAddColumnsFromMatrix(table, (Rcpp::IntegerMatrix) newData); + else if(Rcpp::is(newData)) rcppAddColumnsFromMatrix(table, (Rcpp::StringMatrix) newData); + else if(Rcpp::is(newData)) rcppAddColumnsFromMatrix(table, (Rcpp::CharacterMatrix)newData); + + else if(Rcpp::is(newData)) rcppAddColumnFromVector(table, (Rcpp::NumericVector) newData); + else if(Rcpp::is(newData)) rcppAddColumnFromVector(table, (Rcpp::LogicalVector) newData); + else if(Rcpp::is(newData)) rcppAddColumnFromVector(table, (Rcpp::IntegerVector) newData); + else if(Rcpp::is(newData)) rcppAddColumnFromVector(table, (Rcpp::StringVector) newData); + else if(Rcpp::is(newData)) rcppAddColumnFromVector(table, (Rcpp::CharacterVector) newData); + + else + Rf_error("Cannot add this kind of data as a column to a jaspTable, it is not understood. Try a list, dataframe, vector or matrix instead."); + + table->notifyParentOfChanges(); +} + +void rcppTableAddRows(jaspTable * table, Rcpp::RObject newData, Rcpp::CharacterVector rowNames) +{ + if(newData.isNULL()) + return; + + //Maybe this is overkill? + if(Rcpp::is(newData)) rcppAddRowsFromDataFrame(table, (Rcpp::DataFrame) newData); + else if(Rcpp::is(newData)) rcppAddRowsFromList(table, (Rcpp::List) newData, rowNames); + + else if(Rcpp::is(newData)) rcppAddRowsFromMatrix(table, (Rcpp::NumericMatrix) newData, rowNames); + else if(Rcpp::is(newData)) rcppAddRowsFromMatrix(table, (Rcpp::LogicalMatrix) newData, rowNames); + else if(Rcpp::is(newData)) rcppAddRowsFromMatrix(table, (Rcpp::IntegerMatrix) newData, rowNames); + else if(Rcpp::is(newData)) rcppAddRowsFromMatrix(table, (Rcpp::StringMatrix) newData, rowNames); + else if(Rcpp::is(newData)) rcppAddRowsFromMatrix(table, (Rcpp::CharacterMatrix) newData, rowNames); + + else + Rf_error("Cannot add this kind of data as rows to a jaspTable, it is not understood. Try a list, dataframe or matrix instead."); + + table->notifyParentOfChanges(); +} + +void rcppTableAddRow(jaspTable * table, Rcpp::RObject newData, Rcpp::CharacterVector rowName) +{ + if(newData.isNULL()) + return; + + if (Rcpp::is(newData)) rcppAddRowFromList(table, (Rcpp::List) newData, rowName); + + else if (Rcpp::is(newData)) rcppAddRowFromVector(table, (Rcpp::NumericVector) newData, rowName); + else if (Rcpp::is(newData)) rcppAddRowFromVector(table, (Rcpp::LogicalVector) newData, rowName); + else if (Rcpp::is(newData)) rcppAddRowFromVector(table, (Rcpp::IntegerVector) newData, rowName); + else if (Rcpp::is(newData)) rcppAddRowFromVector(table, (Rcpp::StringVector) newData, rowName); + else if (Rcpp::is(newData)) rcppAddRowFromVector(table, (Rcpp::CharacterVector) newData, rowName); + + else + Rf_error("Cannot add this kind of data as a row to a jaspTable, it is not understood. Try a list or vector instead."); + + table->notifyParentOfChanges(); +} + +void rcppTableAddColumnInfo(jaspTable * table, Rcpp::RObject name, Rcpp::RObject title, Rcpp::RObject type, Rcpp::RObject format, Rcpp::RObject combine, Rcpp::RObject overtitle) +{ + std::string colName = name.isNULL() ? table->defaultColName(table->_colNames.rowCount()) : Rcpp::as(name); + table->_specifiedColumns.insert(colName); + + table->_colNames.add(colName); + + std::string lastAddedColName = table->getColName(table->_colNames.rowCount() - 1); + + if(!title.isNULL()) table->_colTitles[ lastAddedColName ] = Rcpp::String(title); + if(!type.isNULL()) table->_colTypes[ lastAddedColName ] = Rcpp::String(type); + if(!format.isNULL()) table->_colFormats[ lastAddedColName ] = Rcpp::String(format); + if(!overtitle.isNULL()) table->_colOvertitles[ lastAddedColName ] = Rcpp::String(overtitle); + if(!combine.isNULL()) table->_colCombines[ lastAddedColName ] = Rcpp::as(combine); +} + +void rcppTableAddFootnote(jaspTable * table, Rcpp::RObject message, Rcpp::RObject symbol, Rcpp::RObject col_names, Rcpp::RObject row_names) +{ + if (message.isNULL()) + Rf_error("One would expect a footnote to at least contain a message.."); + + std::string strMessage = Rcpp::String(message); + std::string strSymbol = symbol.isNULL() ? "" : Rcpp::String(symbol); + + std::vector colNames; + if (!col_names.isNULL()) + colNames = RcppVector_to_VectorJson(col_names, table->getEscapeHtml(), false); + + std::vector rowNames; + if (!row_names.isNULL()) + rowNames = RcppVector_to_VectorJson(row_names, table->getEscapeHtml(), false); + + table->addFootnote(strMessage, strSymbol, colNames, rowNames); +} + +Rcpp::List rcppTableToRObject(jaspTable * table) +{ + Rcpp::DataFrame df; + + for (size_t col = 0; col < table->_data.size(); col++) + { + + jaspTableColumnType type = table->deriveColumnType(col); + + switch(type) + { + + // this could be a templated or overloaded function? + case jaspTableColumnType::integer: + { + Rcpp::IntegerVector values(table->_data[col].size()); + for (size_t row = 0; row < table->_data[col].size(); row++) + { + const Json::Value & cell = table->_data[col][row]; + if (cell.isNumeric()) + values[row] = cell.asInt(); + else + values[row] = NA_INTEGER; // placeholder/null -> NA + } + + df[table->getColName(col)] = values; + break; + } + case jaspTableColumnType::number: + { + Rcpp::NumericVector values(table->_data[col].size()); + for (size_t row = 0; row < table->_data[col].size(); row++) + { + const Json::Value & cell = table->_data[col][row]; + if (cell.isNumeric()) + values[row] = cell.asDouble(); + else + values[row] = NA_REAL; // placeholder/null -> NA + } + + df[table->getColName(col)] = values; + break; + } + case jaspTableColumnType::logical: + { + Rcpp::LogicalVector values(table->_data[col].size()); + for (size_t row = 0; row < table->_data[col].size(); row++) + values[row] = table->_data[col][row].asBool(); + + df[table->getColName(col)] = values; + + break; + } + case jaspTableColumnType::string: + case jaspTableColumnType::various: + case jaspTableColumnType::unknown: + case jaspTableColumnType::composite: + { + Rcpp::StringVector values(table->_data[col].size()); + for (size_t row = 0; row < table->_data[col].size(); row++) + values[row] = decodeColumnNames(table->_data[col][row].asString()); + + df[decodeColumnNames(table->getColName(col))] = values; + break; + } + case jaspTableColumnType::mixed: + { + + Rcpp::List valuesData(table->_data[col].size()); + Rcpp::StringVector valuesTypes(table->_data[col].size()); + Rcpp::List valuesFormats(table->_data[col].size()); + for (size_t row = 0; row < table->_data[col].size(); row++) + { + valuesTypes[row] = table->_data[col][row]["type"].asString(); + + if (valuesTypes[row] == "number") valuesData[row] = table->_data[col][row]["value"].asDouble(); + else if (valuesTypes[row] == "pvalue") valuesData[row] = table->_data[col][row]["value"].asDouble(); + else if (valuesTypes[row] == "integer") valuesData[row] = table->_data[col][row]["value"].asInt(); + else if (valuesTypes[row] == "string") valuesData[row] = decodeColumnNames(table->_data[col][row]["value"].asString()); + + if (!table->_data[col][row]["format"].isNull()) + valuesFormats[row] = table->_data[col][row]["format"].asString(); + } + + Rcpp::Environment jaspBase = Rcpp::Environment::namespace_env("jaspBase"); + Rcpp::Function createMixedColumn = jaspBase["createMixedColumn"]; + Rcpp::List values = createMixedColumn(valuesData, valuesTypes, valuesFormats); + df[decodeColumnNames(table->getColName(col))] = values; + break; + } + // this case is probably unnecessary + case jaspTableColumnType::null: + { + df[table->getColName(col)] = R_NilValue; + break; + } + + } + } + + // footnotes toRObject (not very efficient, verbatim from the old footnotes::toRObject) + Rcpp::List notes; + + for (const auto & textRest : table->_footnotes._data) + for(const auto & symbolRest : textRest.second) + for(const footnotesNamespace::tableFields & fields : symbolRest.second) + { + Rcpp::List note = Rcpp::List::create( + Rcpp::Named("text") = textRest.first, + Rcpp::Named("symbol") = symbolRest.first +// TODO: I do not understand the data in here, or how to convert it to R... +// Rcpp::Named("rows") = fields.rowsToJSON(), +// Rcpp::Named("cols") = fields.colsToJSON() + ); + notes.push_back(note); + } + + df.attr("footnotes") = notes; + df.attr("title") = decodeColumnNames(table->_title); + df.attr("class") = Rcpp::CharacterVector({"jaspTableWrapper", "jaspWrapper", "data.frame"}); + + std::vector rowNames; + const size_t rowCount = table->_data.empty() ? 0 : table->_data[0].size(); // empty table (e.g. no variables selected) has no rows + rowNames.reserve(rowCount); + for (size_t i = 0; i < rowCount; i++) + rowNames.push_back(table->_rowNames[i] != "" ? decodeColumnNames(table->_rowNames[i]) : std::to_string(i + 1)); // R numbers from 1 to n by default + + df.attr("row.names") = rowNames; + + // the reason this function is not const + Rcpp::Environment jaspObjectEnvironment = Rcpp::new_env(); + jaspObjectEnvironment.assign("jaspObject", Rcpp::as(Rcpp::wrap(jaspTable_Interface(table)))); + df.attr("jaspObjectEnvironment") = jaspObjectEnvironment; + + return df; +} diff --git a/src/adapters/rcpp/rcppTableIngest.h b/src/adapters/rcpp/rcppTableIngest.h new file mode 100644 index 00000000..a8713759 --- /dev/null +++ b/src/adapters/rcpp/rcppTableIngest.h @@ -0,0 +1,20 @@ +#pragma once + +// R-backed jaspTable ingest & export, moved verbatim from the old +// src/jaspTable.{h,cpp}: the SEXP dispatch (data.frame/list/matrix/vector) for +// setData/setColumn/addColumns/addRows/addRow, the NULL-based addColumnInfo / +// addFootnote, and toRObject() incl. mixed columns. Core jaspTable keeps the +// neutral cell storage + JSON machinery (src/core/jaspTable.h). + +#include + +class jaspTable; + +void rcppTableSetData( jaspTable * table, Rcpp::RObject newData); +void rcppTableSetColumn( jaspTable * table, std::string columnName, Rcpp::RObject column); +void rcppTableAddColumns( jaspTable * table, Rcpp::RObject newColumns); +void rcppTableAddRows( jaspTable * table, Rcpp::RObject newRows, Rcpp::CharacterVector rowNames); +void rcppTableAddRow( jaspTable * table, Rcpp::RObject newRow, Rcpp::CharacterVector rowName); +void rcppTableAddColumnInfo( jaspTable * table, Rcpp::RObject name, Rcpp::RObject title, Rcpp::RObject type, Rcpp::RObject format, Rcpp::RObject combine, Rcpp::RObject overtitle); +void rcppTableAddFootnote( jaspTable * table, Rcpp::RObject message, Rcpp::RObject symbol, Rcpp::RObject col_names, Rcpp::RObject row_names); +Rcpp::List rcppTableToRObject( jaspTable * table); diff --git a/src/adapters/rcpp/rcppToRObject.cpp b/src/adapters/rcpp/rcppToRObject.cpp new file mode 100644 index 00000000..229a7dd0 --- /dev/null +++ b/src/adapters/rcpp/rcppToRObject.cpp @@ -0,0 +1,49 @@ +#include "rcppToRObject.h" +#include "jaspObject.h" +#include "jaspContainer.h" +#include "jaspTable.h" +#include "jaspPlot.h" +#include "jaspHtml.h" +#include "rcppInterfaces.h" // jaspHtml_Interface (and later interfaces) +#include "rcppPlot.h" +#include "rcppContainer.h" +#include "rcppTableIngest.h" +#include "rcppTableIngest.h" + +static Rcpp::List jaspHtmlToRObject(jaspHtml * html) +{ + // mimics convertToJSON, could also be a named character vector since everything is a string + Rcpp::List lst = Rcpp::List::create( + Rcpp::Named("rawtext") = html->_rawText, + Rcpp::Named("text") = html->convertTextToHtml(html->_rawText), + Rcpp::Named("class") = html->_class, + Rcpp::Named("maxWidth") = html->_maxWidth, + Rcpp::Named("elementType") = html->_elementType + ); + + lst.attr("title") = html->_title; + lst.attr("class") = Rcpp::CharacterVector({"jaspHtmlWrapper", "jaspWrapper"}); + + // the reason this function is not const + Rcpp::Environment jaspObjectEnvironment = Rcpp::new_env(); + jaspObjectEnvironment.assign("jaspObject", Rcpp::as(Rcpp::wrap(jaspHtml_Interface(html)))); + lst.attr("jaspObjectEnvironment") = jaspObjectEnvironment; + + return lst; +} + +Rcpp::List rcppToRObject(jaspObject * obj) +{ + if(obj == nullptr) + return R_NilValue; + + switch(obj->getType()) + { + case jaspObjectType::container: + case jaspObjectType::results: return rcppContainerToRObject(static_cast(obj)); + case jaspObjectType::table: return rcppTableToRObject(static_cast(obj)); + case jaspObjectType::plot: return rcppPlotToRObject(static_cast(obj)); + case jaspObjectType::html: return jaspHtmlToRObject(static_cast(obj)); + default: return R_NilValue; // old jaspObject::toRObject() default + } +} diff --git a/src/adapters/rcpp/rcppToRObject.h b/src/adapters/rcpp/rcppToRObject.h new file mode 100644 index 00000000..aa45c766 --- /dev/null +++ b/src/adapters/rcpp/rcppToRObject.h @@ -0,0 +1,11 @@ +#pragma once + +// Dispatch for the former virtual jaspObject::toRObject(). The per-class +// implementations stay members of their (still Rcpp-based) classes until each +// class moves to core; this function reproduces the old virtual dispatch. + +#include + +class jaspObject; + +Rcpp::List rcppToRObject(jaspObject * obj); diff --git a/src/jaspColumn.cpp b/src/core/jaspColumn.cpp similarity index 82% rename from src/jaspColumn.cpp rename to src/core/jaspColumn.cpp index ec901443..bd532567 100644 --- a/src/jaspColumn.cpp +++ b/src/core/jaspColumn.cpp @@ -1,5 +1,11 @@ +// CORE (R-free) version of jaspColumn.cpp. Column-data payloads travel as +// opaque std::any (R adapter: Rcpp::RObject end-to-end; Python adapter: +// py::object). The Rcpp::XPtr-based registration that the desktop engine +// calls lives in adapters/rcpp/rcppColumn.cpp and bridges into +// setColumnFuncs() below. + #include "jaspColumn.h" -#include "jaspResults.h" +#include "jaspHost.h" createColumnFuncDef jaspColumn::_createColumnFunc = nullptr; deleteColumnFuncDef jaspColumn::_deleteColumnFunc = nullptr; @@ -50,19 +56,19 @@ void jaspColumn::setColumnFuncs(colDataF scalar, colDataF ordinal, colDataF nomi colGetTF colType, colGetAIF colAnId, colGetAIF colIndex, colCreateF colCreate, colDeleteF colDelete, colExistsF colExists, encDecodeF encode, encDecodeF decode, shouldEncDecodeF shouldEncode, shouldEncDecodeF shouldDecode) { - _createColumnFunc = * colCreate; - _deleteColumnFunc = * colDelete; - _getColumnTypeFunc = * colType; - _getColumnAnalysisIdFunc = * colAnId; - _getColumnOriginalIndexFunc = * colIndex; - _setColumnDataAsScaleFunc = * scalar; - _setColumnDataAsOrdinalFunc = * ordinal; - _setColumnDataAsNominalFunc = * nominal; - _getColumnExistsFunc = * colExists; - _encodeFunc = * encode; - _decodeFunc = * decode; - _shouldEncodeFunc = * shouldEncode; - _shouldDecodeFunc = * shouldDecode; + _createColumnFunc = colCreate; + _deleteColumnFunc = colDelete; + _getColumnTypeFunc = colType; + _getColumnAnalysisIdFunc = colAnId; + _getColumnOriginalIndexFunc = colIndex; + _setColumnDataAsScaleFunc = scalar; + _setColumnDataAsOrdinalFunc = ordinal; + _setColumnDataAsNominalFunc = nominal; + _getColumnExistsFunc = colExists; + _encodeFunc = encode; + _decodeFunc = decode; + _shouldEncodeFunc = shouldEncode; + _shouldDecodeFunc = shouldDecode; } #define SET_COLUMN_DATA_BASE(FUNC) \ @@ -78,10 +84,10 @@ void jaspColumn::setColumnFuncs(colDataF scalar, colDataF ordinal, colDataF nomi return (*FUNC)(columnName, data, computed); \ } \ -bool jaspColumn::setColumnDataAsScale( const std::string & columnName, Rcpp::RObject data, bool computed) SET_COLUMN_DATA_BASE(_setColumnDataAsScaleFunc) -bool jaspColumn::setColumnDataAsOrdinal( const std::string & columnName, Rcpp::RObject data, bool computed) SET_COLUMN_DATA_BASE(_setColumnDataAsOrdinalFunc) -bool jaspColumn::setColumnDataAsNominal( const std::string & columnName, Rcpp::RObject data, bool computed) SET_COLUMN_DATA_BASE(_setColumnDataAsNominalFunc) -bool jaspColumn::setColumnDataAsNominalText( const std::string & columnName, Rcpp::RObject data, bool computed) SET_COLUMN_DATA_BASE(_setColumnDataAsNominalFunc) +bool jaspColumn::setColumnDataAsScale( const std::string & columnName, const std::any & data, bool computed) SET_COLUMN_DATA_BASE(_setColumnDataAsScaleFunc) +bool jaspColumn::setColumnDataAsOrdinal( const std::string & columnName, const std::any & data, bool computed) SET_COLUMN_DATA_BASE(_setColumnDataAsOrdinalFunc) +bool jaspColumn::setColumnDataAsNominal( const std::string & columnName, const std::any & data, bool computed) SET_COLUMN_DATA_BASE(_setColumnDataAsNominalFunc) +bool jaspColumn::setColumnDataAsNominalText( const std::string & columnName, const std::any & data, bool computed) SET_COLUMN_DATA_BASE(_setColumnDataAsNominalFunc) columnType jaspColumn::getColumnType(const std::string & columnName) @@ -110,13 +116,13 @@ int jaspColumn::getColumnOriginalIndex(const std::string &columnName) bool jaspColumn::columnIsMine( const std::string & columnName) { - if(jaspResults::analysisId() == -1) + if(jaspHost::analysisId() == -1) return true; - //jaspPrint("jaspColumn::columnIsMine?\njaspResults::analysisId(): " + std::to_string(jaspResults::analysisId())); + //jaspPrint("jaspColumn::columnIsMine?\njaspHost::analysisId(): " + std::to_string(jaspHost::analysisId())); //jaspPrint("getColumnAnalysisId("+columnName+"): " + std::to_string(getColumnAnalysisId(columnName))); - return jaspResults::analysisId() == getColumnAnalysisId(columnName); + return jaspHost::analysisId() == getColumnAnalysisId(columnName); } bool jaspColumn::getColumnExists(const std::string & columnName) @@ -216,25 +222,19 @@ bool jaspColumn::deleteColumn(const std::string &columnName) return (*_deleteColumnFunc)(columnName); } -Rcpp::StringVector jaspColumn::createColumnsCPP(Rcpp::StringVector columnNames) +std::vector jaspColumn::createColumns(const std::vector & columnNames) { jaspPrint("jaspBase::createColumns aka jaspColumn::createColumnsCPP is deprecated. jaspColumn is all you need!"); - Rcpp::StringVector result; + std::vector result; if(!_createColumnFunc) { jaspPrint("jaspColumn does nothing in R stand-alone!"); return result; } - - stringvec colNames; - colNames.reserve(columnNames.size()); - - for(const Rcpp::String columnName : columnNames) - colNames.push_back(columnName); - for(const std::string & columnName : colNames) + for(const std::string & columnName : columnNames) if(getColumnExists(columnName) && !columnIsMine(columnName)) { jaspPrint("Column '"+columnName+"' already exists and does NOT belong to this analysis..."); @@ -242,7 +242,7 @@ Rcpp::StringVector jaspColumn::createColumnsCPP(Rcpp::StringVector columnNames) } - for(const std::string & columnName : colNames) + for(const std::string & columnName : columnNames) if(!getColumnExists(columnName)) result.push_back((*_createColumnFunc)(columnName, false)); else @@ -251,7 +251,7 @@ Rcpp::StringVector jaspColumn::createColumnsCPP(Rcpp::StringVector columnNames) return result; } -bool jaspColumn::setScale(Rcpp::RObject scalarData, bool computed) +bool jaspColumn::setScale(std::any scalarData, bool computed) { if(!columnIsMine(_columnName)) return false; @@ -266,7 +266,7 @@ bool jaspColumn::setScale(Rcpp::RObject scalarData, bool computed) return true; } -bool jaspColumn::setOrdinal(Rcpp::RObject ordinalData, bool computed) +bool jaspColumn::setOrdinal(std::any ordinalData, bool computed) { if(!columnIsMine(_columnName)) return false; @@ -281,7 +281,7 @@ bool jaspColumn::setOrdinal(Rcpp::RObject ordinalData, bool computed) return true; } -bool jaspColumn::setNominal(Rcpp::RObject nominalData, bool computed) +bool jaspColumn::setNominal(std::any nominalData, bool computed) { if(!columnIsMine(_columnName)) return false; @@ -296,7 +296,7 @@ bool jaspColumn::setNominal(Rcpp::RObject nominalData, bool computed) return true; } -bool jaspColumn::setNominalText(Rcpp::RObject nominalData, bool computed) +bool jaspColumn::setNominalText(std::any nominalData, bool computed) { return setNominal(nominalData, computed); } diff --git a/src/jaspColumn.h b/src/core/jaspColumn.h similarity index 61% rename from src/jaspColumn.h rename to src/core/jaspColumn.h index 1626a87b..255c795e 100644 --- a/src/jaspColumn.h +++ b/src/core/jaspColumn.h @@ -1,12 +1,17 @@ #ifndef _JASPCOLUMN_HEADER #define _JASPCOLUMN_HEADER +// CORE (R-free) version of jaspColumn. The column-data callbacks use +// std::any payloads so that any host (R, Python, …) can pass opaque data. +// The R adapter (rcppColumn.h) provides the XPtr-based setColumnFuncs that +// the desktop engine expects, bridging to these plain function pointers. + #include "jaspObject.h" #include "columntype.h" -#include +#include typedef bool (*shouldEnDecodeFuncDef) (std::string); -typedef bool (*setColumnDataFuncDef) (std::string, Rcpp::RObject, bool); +typedef bool (*setColumnDataFuncDef) (std::string, const std::any &, bool); typedef columnType (*getColumnTypeFuncDef) (std::string); typedef int (*getColumnAnIdFuncDef) (std::string); typedef bool (*getColumnExistsFDef) (std::string); @@ -14,14 +19,16 @@ typedef std::string (*createColumnFuncDef) (std::string, bool); typedef bool (*deleteColumnFuncDef) (std::string); typedef std::string (*enDecodeFuncDef) (std::string); -typedef Rcpp::XPtr shouldEncDecodeF; -typedef Rcpp::XPtr colDataF; -typedef Rcpp::XPtr colGetTF; -typedef Rcpp::XPtr colGetAIF; -typedef Rcpp::XPtr colCreateF; -typedef Rcpp::XPtr colDeleteF; -typedef Rcpp::XPtr colExistsF; -typedef Rcpp::XPtr encDecodeF; +// Plain aliases so the core API stays R-free; the R adapter wraps these in +// Rcpp::XPtr when registering setColumnFuncs with the module. +typedef shouldEnDecodeFuncDef shouldEncDecodeF; +typedef setColumnDataFuncDef colDataF; +typedef getColumnTypeFuncDef colGetTF; +typedef getColumnAnIdFuncDef colGetAIF; +typedef createColumnFuncDef colCreateF; +typedef deleteColumnFuncDef colDeleteF; +typedef getColumnExistsFDef colExistsF; +typedef enDecodeFuncDef encDecodeF; class jaspColumn : public jaspObject @@ -41,17 +48,16 @@ class jaspColumn : public jaspObject Json::Value metaEntry() const override { return constructMetaEntry("column"); } Json::Value dataEntry(std::string & errorMessage) const override; - bool setScale( Rcpp::RObject scalarData, bool computed = false); - bool setOrdinal( Rcpp::RObject ordinalData, bool computed = false); - bool setNominal( Rcpp::RObject nominalData, bool computed = false); - bool setNominalText( Rcpp::RObject nominalData, bool computed = false); + bool setScale( std::any scalarData, bool computed = false); + bool setOrdinal( std::any ordinalData, bool computed = false); + bool setNominal( std::any nominalData, bool computed = false); + bool setNominalText( std::any nominalData, bool computed = false); //void removeFromData(); static bool columnIsMine( const std::string & columnName); ///< "Mine" means of analysis that is running static bool columnExists( const std::string & columnName) { return getColumnExists(columnName); } static int getColumnOriginalIndex( const std::string & encodedColumnName ); - static Rcpp::StringVector createColumnsCPP(Rcpp::StringVector columnNames); /// createColumns(const std::vector & columnNames); static void setColumnFuncs(colDataF scalar, colDataF ordinal, colDataF nominal, colGetTF colType, colGetAIF colAnaId, colGetAIF colIndex, colCreateF colCreate, colDeleteF colDelete, colExistsF colExists, encDecodeF encode, encDecodeF decode, shouldEncDecodeF shouldEncode, shouldEncDecodeF shouldDecode); static bool deleteColumn(const std::string & columnName); @@ -75,10 +81,10 @@ class jaspColumn : public jaspObject static int getColumnAnalysisId( const std::string & encodedColumnName ); void determineTypeTitle(); - bool setColumnDataAsScale( const std::string & encodedColumnName, Rcpp::RObject data, bool computed=false); - bool setColumnDataAsOrdinal( const std::string & encodedColumnName, Rcpp::RObject data, bool computed=false); - bool setColumnDataAsNominal( const std::string & encodedColumnName, Rcpp::RObject data, bool computed=false); - bool setColumnDataAsNominalText( const std::string & encodedColumnName, Rcpp::RObject data, bool computed=false); + bool setColumnDataAsScale( const std::string & encodedColumnName, const std::any & data, bool computed=false); + bool setColumnDataAsOrdinal( const std::string & encodedColumnName, const std::any & data, bool computed=false); + bool setColumnDataAsNominal( const std::string & encodedColumnName, const std::any & data, bool computed=false); + bool setColumnDataAsNominalText( const std::string & encodedColumnName, const std::any & data, bool computed=false); static createColumnFuncDef _createColumnFunc; static deleteColumnFuncDef _deleteColumnFunc; @@ -97,19 +103,4 @@ class jaspColumn : public jaspObject }; - - -class jaspColumn_Interface : public jaspObject_Interface -{ -public: - jaspColumn_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - bool setScale( Rcpp::RObject scalarData, bool computed = false) { return static_cast(myJaspObject)->setScale(scalarData, computed); } - bool setOrdinal( Rcpp::RObject ordinalData, bool computed = false) { return static_cast(myJaspObject)->setOrdinal(ordinalData, computed); } - bool setNominal( Rcpp::RObject nominalData, bool computed = false) { return static_cast(myJaspObject)->setNominal(nominalData, computed); } - bool setNominalText(Rcpp::RObject nominalData, bool computed = false) { return static_cast(myJaspObject)->setNominal(nominalData, computed); } - //void removeFromData() { return static_cast(myJaspObject)->removeFromData(); } -}; - -RCPP_EXPOSED_CLASS_NODECL(jaspColumn_Interface) #endif diff --git a/src/jaspContainer.cpp b/src/core/jaspContainer.cpp similarity index 74% rename from src/jaspContainer.cpp rename to src/core/jaspContainer.cpp index 2e3d676b..9b18d679 100644 --- a/src/jaspContainer.cpp +++ b/src/core/jaspContainer.cpp @@ -1,8 +1,18 @@ +// CORE (R-free) version of jaspContainer.cpp. +// The Rcpp-facing insert dispatch, wrapJaspObject, list-construction and +// toRObject live in src/adapters/rcpp/rcppContainer. Rendering/state-store +// interactions go through the normal core methods (renderPlot delegates to +// jaspHost), so no R is needed here. + #include "jaspContainer.h" +#include "jaspPlot.h" +#include "jaspQmlSource.h" +#include "jaspReport.h" +#include -void jaspContainer::insert(std::string field, Rcpp::RObject value) +void jaspContainer::insert(std::string field, jaspObject * obj) { - if(value.isNULL()) + if(obj == nullptr) { if(_data.count(field) > 0) _data.erase(field); //deletion will be taken care of by jaspObject::destroyAllAllocatedObjects() @@ -10,21 +20,6 @@ void jaspContainer::insert(std::string field, Rcpp::RObject value) return; } - jaspObject * obj = nullptr; - - - if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = Rcpp::as(value).returnMyJaspObject(); - else if(Rcpp::is(value)) obj = (jaspObject*)(jaspContainerFromRcppList(Rcpp::as(value))); - else throw std::runtime_error("Unhandled Rcpp Object type"); - #ifdef JASP_RESULTS_DEBUG_TRACES std::cout << "something {"<objectTitleString()<<"} added to jaspContainer "< colNamesVec = extractElementOrColumnNames(convertThis); - - if(convertThis.size() > colNamesVec.size()) - Rf_error("If you add a list() to jaspResults or a jaspContainer each element should be named!"); - - jaspContainer * newContainer = new jaspContainer(); - - for(int i=0; i_title = Rcpp::String(Rcpp::RObject(convertThis[i])); - else - newContainer->insert(colNamesVec[i], convertThis[i]); - - return newContainer; -} - -Rcpp::RObject jaspContainer::at(std::string field) +jaspObject * jaspContainer::at(std::string field) { if(_data.count(field) == 0) - return R_NilValue; + return nullptr; - jaspObject * ref = _data[field]; - return wrapJaspObject(ref); -} - -Rcpp::RObject jaspContainer::wrapJaspObject(jaspObject * ref) -{ - switch(ref->getType()) - { - case jaspObjectType::container: return Rcpp::wrap(jaspContainer_Interface(ref)); - case jaspObjectType::qmlSource: return Rcpp::wrap(jaspQmlSource_Interface(ref)); - case jaspObjectType::column: return Rcpp::wrap(jaspColumn_Interface(ref)); - case jaspObjectType::report: return Rcpp::wrap(jaspReport_Interface(ref)); - case jaspObjectType::table: return Rcpp::wrap(jaspTable_Interface(ref)); - case jaspObjectType::state: return Rcpp::wrap(jaspState_Interface(ref)); - case jaspObjectType::html: return Rcpp::wrap(jaspHtml_Interface(ref)); - case jaspObjectType::plot: return Rcpp::wrap(jaspPlot_Interface(ref)); - default: return R_NilValue; - } + return _data[field]; } std::string jaspContainer::dataToString(std::string prefix) const @@ -239,7 +199,7 @@ std::string jaspContainer::getCommonDenominatorMetaType() const for(const auto & keyval : _data) { std::string currentType = keyval.second->metaEntry()["type"].asString(); - + if(comDenom == "") comDenom = currentType; @@ -334,11 +294,8 @@ void jaspContainer::letChildrenRun() break; case jaspObjectType::table: - static_cast(obj)->letRun(); - break; - case jaspObjectType::plot: - static_cast(obj)->letRun(); + obj->letRun(); //virtual: jaspTable::letRun / jaspPlot::letRun break; default: @@ -362,11 +319,8 @@ void jaspContainer::completeChildren() break; case jaspObjectType::table: - static_cast(obj)->complete(); - break; - case jaspObjectType::plot: - static_cast(obj)->complete(); + obj->complete(); //virtual: jaspTable::complete / jaspPlot::complete break; case jaspObjectType::qmlSource: @@ -414,33 +368,6 @@ bool jaspContainer::canShowErrorMessage() const return false; } -Rcpp::List jaspContainer::toRObject() /*const*/ -{ - - std::vector keys = getSortedDataFields(); - Rcpp::List lst; - - for (const auto & key : keys) - { - - jaspObject* child = _data.at(key); - - Rcpp::List Robj = child->toRObject(); - if (Robj.length() > 0) - lst.push_back(Robj, key); - } - - lst.attr("class") = Rcpp::CharacterVector({"jaspContainerWrapper", "jaspWrapper"}); - lst.attr("title") = _title; - - // the reason this function is not const - Rcpp::Environment jaspObjectEnvironment = Rcpp::new_env(); - jaspObjectEnvironment.assign("jaspObject", Rcpp::as(Rcpp::wrap(jaspContainer_Interface(this)))); - lst.attr("jaspObjectEnvironment") = jaspObjectEnvironment; - - return lst; -} - Json::Value jaspContainer::convertToJSON() const { Json::Value obj = jaspObject::convertToJSON(); @@ -505,7 +432,7 @@ void jaspContainer::setError() d.second->setError(); } -void jaspContainer::setError(Rcpp::String message) +void jaspContainer::setError(std::string message) { _errorMessage = message; setError(); @@ -529,8 +456,3 @@ void jaspContainer::renderPlotsOfChildren() } } - -Rcpp::RObject jaspContainer_Interface::findObjectWithUniqueNestedName(std::string uniqueNestedName) -{ - return jaspContainer::wrapJaspObject(((jaspContainer*)myJaspObject)->findObjectWithUniqueNestedName(uniqueNestedName)); -} diff --git a/src/jaspContainer.h b/src/core/jaspContainer.h similarity index 62% rename from src/jaspContainer.h rename to src/core/jaspContainer.h index 685f1500..c620363e 100644 --- a/src/jaspContainer.h +++ b/src/core/jaspContainer.h @@ -1,20 +1,18 @@ #pragma once -#include "jaspObject.h" -#include "jaspColumn.h" -#include "jaspPlot.h" -#include "jaspTable.h" -#include "jaspState.h" -#include "jaspHtml.h" -#include "jaspQmlSource.h" -#include "jaspReport.h" -#include +// CORE (R-free) version of jaspContainer.h. The Rcpp-facing insert dispatch, +// wrapJaspObject, list-construction and toRObject live in +// src/adapters/rcpp/rcppContainer; jaspContainer_Interface lives in +// src/adapters/rcpp/rcppInterfaces.h. Core children only: insert(jaspObject*) +// and at() -> jaspObject*. +#include "jaspObject.h" +#include class jaspContainer : public jaspObject { public: - jaspContainer(Rcpp::String title = "", jaspObjectType type = jaspObjectType::container) : jaspObject(type, title) + jaspContainer(std::string title = "", jaspObjectType type = jaspObjectType::container) : jaspObject(type, title) { #ifdef JASP_RESULTS_DEBUG_TRACES std::cout << "JASPcontainer constructor for title: " << _title << std::endl; @@ -26,8 +24,8 @@ class jaspContainer : public jaspObject std::string dataToString(std::string prefix = "") const override; std::string toHtml() const override; - void insert(std::string field, Rcpp::RObject value); - Rcpp::RObject at(std::string field); + void insert(std::string field, jaspObject * value); + jaspObject * at(std::string field); Json::Value metaEntry(jaspObject * oldResult) const override; Json::Value dataEntry(jaspObject * oldResult, std::string & errorMsg) const override; @@ -38,8 +36,6 @@ class jaspContainer : public jaspObject void childFinalizedHandler(jaspObject *child) override; - static jaspContainer * jaspContainerFromRcppList(Rcpp::List convertThis); - Json::Value convertToJSON() const override; void convertFromJSON_SetFields(Json::Value in) override; void checkDependenciesChildren(Json::Value currentOptions) override; @@ -47,12 +43,11 @@ class jaspContainer : public jaspObject void completeChildren(); void letChildrenRun(); void setError() override; - void setError(Rcpp::String message) override; + void setError(std::string message) override; void renderPlotsOfChildren(); bool containsNonContainer(); bool canShowErrorMessage() const override; - Rcpp::List toRObject() /*const*/ override; bool _initiallyCollapsed = false; @@ -66,7 +61,6 @@ class jaspContainer : public jaspObject jaspObject * findObjectWithNestedNameVector(const std::vector &uniqueName, const size_t position = 0); jaspObject * findObjectWithUniqueNestedName(const std::string & uniqueNestedName); - static Rcpp::RObject wrapJaspObject(jaspObject * ref); protected: std::map _data; @@ -74,18 +68,3 @@ class jaspContainer : public jaspObject int _order_increment = 0; }; - -class jaspContainer_Interface : public jaspObject_Interface -{ -public: - jaspContainer_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - int length() { return ((jaspContainer*)myJaspObject)->length(); } - Rcpp::RObject at(std::string field) { return ((jaspContainer*)myJaspObject)->at(field); } - void insert(std::string field, Rcpp::RObject value) { ((jaspContainer*)myJaspObject)->insert(field, value); } - Rcpp::RObject findObjectWithUniqueNestedName(std::string uniqueNestedName); - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspContainer, bool, _initiallyCollapsed, InitiallyCollapsed) -}; - -RCPP_EXPOSED_CLASS_NODECL(jaspContainer_Interface) diff --git a/src/jaspEnums.cpp b/src/core/jaspEnums.cpp similarity index 100% rename from src/jaspEnums.cpp rename to src/core/jaspEnums.cpp diff --git a/src/jaspEnums.h b/src/core/jaspEnums.h similarity index 100% rename from src/jaspEnums.h rename to src/core/jaspEnums.h diff --git a/src/core/jaspHost.cpp b/src/core/jaspHost.cpp new file mode 100644 index 00000000..f4dee5d0 --- /dev/null +++ b/src/core/jaspHost.cpp @@ -0,0 +1,57 @@ +#include "jaspHost.h" +#include + +std::function jaspHost::logString = nullptr; +std::function jaspHost::sendResults = nullptr; +std::function jaspHost::pollMessages = nullptr; +std::function jaspHost::signalAnalysisAbort = nullptr; +std::function jaspHost::decodeColumnNames = [](const std::string & str) { return str; }; + +int jaspHost::_analysisId = -1; + +int jaspHost::analysisId() +{ + return _analysisId; +} + +void jaspHost::setAnalysisId(int id) +{ + _analysisId = id; +} + +namespace +{ + std::map & jaspHostDefaultObjectStore() + { + static std::map store; + return store; + } +} + +std::function jaspHost::storeObject = [](const std::string & envName, std::any obj) +{ + jaspHostDefaultObjectStore()[envName] = std::move(obj); +}; + +std::function jaspHost::fetchObject = [](const std::string & envName) -> std::any +{ + auto & store = jaspHostDefaultObjectStore(); + auto it = store.find(envName); + return it == store.end() ? std::any() : it->second; +}; + +std::function jaspHost::objectExists = [](const std::string & envName) +{ + return jaspHostDefaultObjectStore().count(envName) > 0; +}; + +std::function jaspHost::clearObjects = []() +{ + jaspHostDefaultObjectStore().clear(); +}; + +std::function jaspHost::destroyObjectStore = nullptr; + +std::function jaspHost::renderPlot = nullptr; +std::function jaspHost::plotStateSync = nullptr; +std::function jaspHost::saveStateArchive = nullptr; diff --git a/src/core/jaspHost.h b/src/core/jaspHost.h new file mode 100644 index 00000000..59861ded --- /dev/null +++ b/src/core/jaspHost.h @@ -0,0 +1,63 @@ +#pragma once + +// jaspHost: the language/host seam of the jaspResults core. +// +// Everything that used to be hardwired to R (logging, sending results to the +// desktop, polling for analysis changes, abort signalling, column-name +// decoding, the plot/state object store, plot rendering and state-file +// saving) becomes a callback or a store on this class. The R engine installs +// R-backed implementations (src/adapters/rcpp); a Python host will install +// Python-backed ones (see tmp/plan-python-interface.md section 2.1). +// +// Defaults are neutral no-ops (identity for decodeColumnNames) so the core is +// usable standalone, e.g. in tests. All callbacks are expected to be set +// before an analysis runs, mirroring today's engine flow where setSendFunc & +// friends are called before the analysis starts. Single-threaded use, like +// today's engine. + +#include +#include +#include + +class jaspPlot; +class jaspResults; + +class jaspHost +{ +public: + // messaging / engine loop ------------------------------------------------- + static std::function logString; ///< sink for jaspPrint + static std::function sendResults; ///< results JSON to the desktop + static std::function pollMessages; ///< true => analysis changed + static std::function signalAnalysisAbort; + static std::function decodeColumnNames; ///< default: identity + + // identity of the running analysis (set from setResponseData). Used by + // jaspReport (positioning) and jaspColumn (ownership) without needing the + // jaspResults object. -1 == none/unknown. + static int analysisId(); + static void setAnalysisId(int id); + + // object store (plot + state objects), keyed by envName. Default is a plain + // in-process map; hosts can override, e.g. the R engine stores objects in an + // R environment so R's GC keeps them alive (installed by jaspResults). + static std::function storeObject; + static std::function fetchObject; + static std::function objectExists; + static std::function clearObjects; + static std::function destroyObjectStore; ///< called by ~jaspResults (R: releases the storage env wrapper) + + // plotting ------------------------------------------------------------------ + // renderPlot owns the whole render pass (the R adapter reproduces the old + // tryToWriteImageJaspResults flow incl. old-plot info; the Python adapter + // will render plotly/matplotlib). plotStateSync re-applies user plot changes + // (width/height/revision) from the stored object after convertFromJSON. + static std::function renderPlot; + static std::function plotStateSync; + + // state archive (R: RDS alongside jaspResults.json; Python: pickle) --------- + static std::function saveStateArchive; + +private: + static int _analysisId; +}; diff --git a/src/jaspHtml.cpp b/src/core/jaspHtml.cpp similarity index 77% rename from src/jaspHtml.cpp rename to src/core/jaspHtml.cpp index 5c85ef17..46b5d111 100644 --- a/src/jaspHtml.cpp +++ b/src/core/jaspHtml.cpp @@ -99,25 +99,3 @@ std::string jaspHtml::getHtml() { return convertTextToHtml(_rawText); } -Rcpp::List jaspHtml::toRObject() -{ - // mimics convertToJSON, could also be a named character vector since everything is a string - Rcpp::List lst = Rcpp::List::create( - Rcpp::Named("rawtext") = _rawText, - Rcpp::Named("text") = convertTextToHtml(_rawText), - Rcpp::Named("class") = _class, - Rcpp::Named("maxWidth") = _maxWidth, - Rcpp::Named("elementType") = _elementType - ); - - lst.attr("title") = _title; - lst.attr("class") = Rcpp::CharacterVector({"jaspHtmlWrapper", "jaspWrapper"}); - - // the reason this function is not const - Rcpp::Environment jaspObjectEnvironment = Rcpp::new_env(); - jaspObjectEnvironment.assign("jaspObject", Rcpp::as(Rcpp::wrap(jaspHtml_Interface(this)))); - lst.attr("jaspObjectEnvironment") = jaspObjectEnvironment; - - - return lst; -} diff --git a/src/core/jaspHtml.h b/src/core/jaspHtml.h new file mode 100644 index 00000000..82d5ce6f --- /dev/null +++ b/src/core/jaspHtml.h @@ -0,0 +1,33 @@ +#pragma once + +// CORE (R-free) version of jaspHtml.h. toRObject() moved to +// src/adapters/rcpp/rcppToRObject.cpp, jaspHtml_Interface to +// src/adapters/rcpp/rcppInterfaces.h. + +#include "jaspObject.h" + +class jaspHtml : public jaspObject +{ +public: + jaspHtml(std::string text = "", std::string elementType = "p", std::string maxWidth="15cm", std::string Class = "") : jaspObject(jaspObjectType::html, ""), _rawText(text), _elementType(elementType), _class(Class), _maxWidth(maxWidth) {} + + ~jaspHtml() {} + + std::string dataToString(std::string prefix="") const override; + std::string toHtml() const override; + + Json::Value metaEntry() const override { return constructMetaEntry("htmlNode"); } + Json::Value dataEntry(std::string & errorMessage) const override; + + std::string _rawText, _elementType, _class, _maxWidth; + + Json::Value convertToJSON() const override; + void convertFromJSON_SetFields(Json::Value in) override; + + std::string convertTextToHtml( const std::string text) const; + static std::string sanitizeTextForHtml(const std::string text); + + void setText(std::string newRawText); + std::string getText(); + std::string getHtml(); +}; diff --git a/src/jaspList.h b/src/core/jaspList.h similarity index 58% rename from src/jaspList.h rename to src/core/jaspList.h index fb16a174..56a90297 100644 --- a/src/jaspList.h +++ b/src/core/jaspList.h @@ -1,4 +1,11 @@ #pragma once + +// CORE (R-free) version of jaspList.h. R-facing index/insert dispatch (1-based +// [[ints]] / fieldnames via Rcpp::RObject), setRows(Rcpp::List) and the +// jaspList_Interface + JASPLIST_MODULE_EXPORT live in +// src/adapters/rcpp/rcppInterfaces.h. Storage is plain vector/map and JSON is +// built here, so hosts (R, Python) only differ in how they index into it. + #include "jaspObject.h" template @@ -10,25 +17,20 @@ class jaspList : public jaspObject allocatedObjects->erase(this); // lists are never newed! } - void insert(Rcpp::RObject field, T value) + ///zero-based index insert; resizes the row vector when needed + void insertIndex(size_t row, T value) { - if(Rcpp::is(field) || Rcpp::is(field)) - { - int row = Rcpp::as(field) - 1; + if(_rows.size() <= row) + _rows.resize(row+1); - if(_rows.size() <= row) - _rows.resize(row+1); + _rows[row] = value; - _rows[row] = value; - } - else if(Rcpp::is(field) || Rcpp::is(field)) - { - std::string fieldName = Rcpp::as(field); + notifyParentOfChanges(); + } - _field_to_val[fieldName] = value; - } - else - Rf_error("Did not get a number, integer or string to index on."); + void insertField(std::string fieldName, T value) + { + _field_to_val[fieldName] = value; notifyParentOfChanges(); } @@ -39,28 +41,19 @@ class jaspList : public jaspObject notifyParentOfChanges(); } - ///using [] (in c++) will give you normal zero-based array but also grows the vector if your request lies outside of it, at() ([[]] in R) however gives you 1-based access and just returns a dummy value if you request something out of range. - T at(Rcpp::RObject field) const + ///zero-based index access; returns a default value when out of range. + T atIndex(size_t row) const { - if(Rcpp::is(field) || Rcpp::is(field)) - { - int row = Rcpp::as(field) - 1; - - if(row > _rows.size()) - return T(); - - return _rows[row]; - } - else if(Rcpp::is(field) || Rcpp::is(field)) - { - std::string fieldName = Rcpp::as(field); + if(row >= _rows.size()) + return T(); - return _field_to_val.at(fieldName); - } - else - Rf_error("Did not get a number, integer or string to index on."); + return _rows[row]; + } - return T(); + ///field access; throws std::out_of_range for unknown fields (today's R behavior). + T atField(std::string fieldName) const + { + return _field_to_val.at(fieldName); } std::string dataToString(std::string prefix) const override @@ -103,29 +96,21 @@ class jaspList : public jaspObject return out.str(); } - void setRows(Rcpp::List vec) + ///clears the rows and appends the named entries to the existing fields + ///(no clearing of _field_to_val, no notify: exactly today's setRows) + void setRows(const std::vector & vec, const std::map & fields = {}) { _rows.clear(); - for(auto v : vec) - _rows.push_back(Rcpp::as(v)); - - Rcpp::RObject namesListRObject = vec.names(); + _rows.insert(_rows.end(), vec.begin(), vec.end()); - if(!namesListRObject.isNULL()) - { - Rcpp::CharacterVector namesList; - namesList = namesListRObject; - - for(int row=0; row(namesList[row])] = Rcpp::as(vec[row]); - } + for(const auto & keyval : fields) + _field_to_val[keyval.first] = keyval.second; } size_t rowCount() const { return _rows.size(); } size_t fieldCount() const { return _field_to_val.size(); } - ///using [] (in c++) will give you normal zero-based array but also grows the vector if your request lies outside of it, at() ([[]] in R) however gives you 1-based access and just returns a dummy value if you request something out of range. + ///using [] (in c++) will give you normal zero-based array but also grows the vector if your request lies outside of it T & operator[](size_t index) { if(_rows.size() <= index) @@ -223,37 +208,3 @@ typedef jaspList jaspStringlist; typedef jaspList jaspDoublelist; typedef jaspList jaspIntlist; typedef jaspList jaspBoollist; - -template -class jaspList_Interface : public jaspObject_Interface -{ -public: - jaspList_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - void insert(Rcpp::RObject field, T value) { static_cast*>(myJaspObject)->insert(field, value); } - T at(Rcpp::RObject field) { return static_cast*>(myJaspObject)->at(field); } - void add(T value) { static_cast*>(myJaspObject)->add(value); } -}; - -typedef jaspList_Interface jaspStringlist_Interface; -typedef jaspList_Interface jaspDoublelist_Interface; -typedef jaspList_Interface jaspIntlist_Interface; -typedef jaspList_Interface jaspBoollist_Interface; - -RCPP_EXPOSED_CLASS_NODECL(jaspStringlist_Interface) -RCPP_EXPOSED_CLASS_NODECL(jaspDoublelist_Interface) -RCPP_EXPOSED_CLASS_NODECL(jaspIntlist_Interface) -RCPP_EXPOSED_CLASS_NODECL(jaspBoollist_Interface) - -//.constructor( "Default constructor without setting the title explicitly") -//.constructor( "Constructor that sets the title explicitly") - -#define JASPLIST_MODULE_EXPORT(CLASS_NAME_CPP, CLASS_NAME_R) \ -Rcpp::class_(CLASS_NAME_R) \ - .derives("jaspObject") \ - .method( "[[", &CLASS_NAME_CPP::at, "Access element by fieldname (string) or index (int) ") \ - .method( "[[<-", &CLASS_NAME_CPP::insert, "Insert an element under index (int) or fieldname (string)") \ - .method( "insert", &CLASS_NAME_CPP::insert, "Insert an element under index (int) or fieldname (string)") \ - .method( "add", &CLASS_NAME_CPP::add, "Add an element at the end of the indexable list") \ - JASP_OBJECT_FINALIZER_LAMBDA(CLASS_NAME_CPP) \ -; diff --git a/src/jaspObject.cpp b/src/core/jaspObject.cpp similarity index 70% rename from src/jaspObject.cpp rename to src/core/jaspObject.cpp index b812e7cb..cd3723fa 100644 --- a/src/jaspObject.cpp +++ b/src/core/jaspObject.cpp @@ -1,29 +1,38 @@ -#include "jaspObject.h" -#include +// CORE (R-free) version of jaspObject.cpp. +// SEXP->Json conversions moved to src/adapters/rcpp/rcppConversions.cpp; +// logging/column-name decoding go through jaspHost. -#ifdef BUILDING_JASP -#include -#else +#include "jaspObject.h" +#include "jaspHost.h" #include "json/json_value.cpp" // hacky way to get libjson in the code ^^ #include "json/json_reader.cpp" #include "json/json_writer.cpp" -#endif +#include +#include +#include +#include std::string stringExtend(std::string & str, size_t len, char kar) { - if(str.size() < len) - str += std::string(len - str.size(), kar); + std::string uit(str); - return str; + while(uit.size() < len) + uit += kar; + + return uit; } std::string stringRemove(std::string str, char kar) { - for(size_t removeMe = str.find_first_of(kar); removeMe != std::string::npos; removeMe = str.find_first_of(kar)) - str.erase(removeMe, 1); - return str; + std::string uit; + + for(char k : str) + if(k != kar) + uit.push_back(k); + + return uit; } std::vector stringSplit(std::string str, char kar) @@ -40,36 +49,20 @@ std::vector stringSplit(std::string str, char kar) return strs; } -logFuncDef _jaspRCPP_logString = nullptr; - -void setJaspLogFunction(Rcpp::XPtr func) -{ - _jaspRCPP_logString = * func; - - _jaspRCPP_logString("Log string function received loud and clear!"); -} - void jaspPrint(std::string msg) { msg = decodeColumnNames(msg); -#ifdef JASP_R_INTERFACE_LIBRARY - _jaspRCPP_logString(msg + "\n"); -#else - Rcpp::Rcout << msg << "\n"; -#endif + if(jaspHost::logString) + jaspHost::logString(msg + "\n"); + else + std::cout << msg << "\n"; } std::string decodeColumnNames(const std::string & str) { - static Rcpp::Environment jaspBase = Rcpp::Environment::namespace_env("jaspBase"); - static Rcpp::Function decodeAll = jaspBase["decodeColNames"]; - - if (!decodeAll.isNULL()) - { - Rcpp::String decodeStr = decodeAll(str); - return decodeStr; - } + if(jaspHost::decodeColumnNames) + return jaspHost::decodeColumnNames(str); return str; } @@ -102,8 +95,6 @@ void jaspObject::destroyAllAllocatedObjects() { jaspObject * p = *(allocatedObjects->begin()); - //std::cout << "p == "<objectTitleString()<<"!\n"<erase(allocatedObjects->begin()); delete p; } @@ -156,7 +147,6 @@ Json::Value jaspObject::getObjectFromNestedOption(std::vector neste if (obj.isNull()) return ifNotFound; - } return obj; } @@ -303,35 +293,6 @@ std::string jaspObject::toString(std::string prefix) const return objectTitleString(prefix) + (dataString == "" ? "\n" : ":\n" + dataString); } -Rcpp::DataFrame jaspObject::convertFactorsToCharacters(Rcpp::DataFrame df) -{ - - for(int col=0; col 0) //it can be INT_MIN at least, but if we are doing a -1 on it anyhow it should just be bigger than 0 - charCol[i] = factorLevels[originalColumn[i] - 1]; - - df[col] = charCol; - } - - return df; -} - Json::Value jaspObject::constructMetaEntry(std::string type, std::string meta) const { Json::Value obj(Json::objectValue); @@ -479,13 +440,12 @@ void jaspObject::convertFromJSON_SetFields(Json::Value in) Json::Value jaspObject::currentOptions = Json::nullValue; -void jaspObject::dependOnOptions(Rcpp::CharacterVector listOptions) +void jaspObject::dependOnOptions(std::vector listOptions) { - if(currentOptions.isNull()) Rf_error("No options known!"); + if(currentOptions.isNull()) throw std::runtime_error("No options known!"); - for(auto & nameOption : listOptions) + for(auto & name : listOptions) { - std::string name = Rcpp::as(nameOption); std::string nameTypes = name + ".types"; _optionMustBe[name] = currentOptions.get(name, Json::nullValue); if (currentOptions.isMember(nameTypes)) @@ -493,40 +453,39 @@ void jaspObject::dependOnOptions(Rcpp::CharacterVector listOptions) } } -void jaspObject::setOptionMustBeDependency(std::string optionName, Rcpp::RObject mustBeThis) +void jaspObject::setOptionMustBeDependency(std::string optionName, Json::Value mustBeThis) { - _optionMustBe[optionName] = RObject_to_JsonValue(mustBeThis); + _optionMustBe[optionName] = mustBeThis; } -void jaspObject::setOptionMustContainDependency(std::string optionName, Rcpp::RObject mustContainThis) +void jaspObject::setOptionMustContainDependency(std::string optionName, Json::Value mustContainThis) { - if (mustContainThis.isNULL()) - Rf_error("setOptionMustContainDependency expected not null!"); + if (mustContainThis.isNull()) + throw std::runtime_error("setOptionMustContainDependency expected not null!"); - _optionMustContain[optionName] = RObject_to_JsonValue(mustContainThis); + _optionMustContain[optionName] = mustContainThis; } -void jaspObject::dependOnNestedOptions(Rcpp::CharacterVector nestedOptionName) +void jaspObject::dependOnNestedOptions(std::vector nestedKey) { - std::vector nestedKey = Rcpp::as>(nestedOptionName); Json::Value obj = getObjectFromNestedOption(nestedKey); if (obj.isNull()) - Rf_error("nested key \"%s\" does not exist in the options!", nestedKeyToString(nestedKey, "$").c_str()); + throw std::runtime_error("nested key \"" + nestedKeyToString(nestedKey, "$") + "\" does not exist in the options!"); _nestedOptionMustBe[nestedKey] = obj; } -void jaspObject::setNestedOptionMustContainDependency(Rcpp::CharacterVector nestedOptionName, Rcpp::RObject mustContainThis) +void jaspObject::setNestedOptionMustContainDependency(std::vector nestedOptionName, Json::Value mustContainThis) { - if (mustContainThis.isNULL()) - Rf_error("setNestedOptionMustContainDependency expected not null!"); + if (mustContainThis.isNull()) + throw std::runtime_error("setNestedOptionMustContainDependency expected not null!"); - std::vector nestedKey = Rcpp::as>(nestedOptionName); + std::vector nestedKey = nestedOptionName; Json::Value obj = getObjectFromNestedOption(nestedKey); if (obj.isNull()) - Rf_error("nested key \"%s\" does not exist in the options!", nestedKeyToString(nestedKey, "$").c_str()); + throw std::runtime_error("nested key \"" + nestedKeyToString(nestedKey, "$") + "\" does not exist in the options!"); - _nestedOptionMustContain[nestedKey] = RObject_to_JsonValue(mustContainThis); + _nestedOptionMustContain[nestedKey] = mustContainThis; } @@ -662,86 +621,6 @@ std::map> jaspObject::nestedMustContains() co return out; } -std::vector jaspObject::RList_to_VectorJson(Rcpp::List obj) -{ - std::vector vec; - - for(int row=0; row(obj)) return RObject_to_JsonValue((Rcpp::List) obj); - else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::List) obj); - else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::NumericMatrix) obj); - else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::NumericVector) obj); - else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::IntegerVector) obj); - else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::LogicalVector) obj); - else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::CharacterVector) obj); - else if(Rcpp::is(obj)) return RObject_to_JsonValue((Rcpp::StringVector) obj); - else if(obj.isS4()) return "an S4, which is too complicated for jaspResults now."; - else return "something that is not understood by jaspResults right now.."; -} - -Json::Value jaspObject::MixedRObject_to_JsonValue(Rcpp::List obj) -{ - - Json::Value value(Json::objectValue); - - // sometimes we receive list(mixed) and sometimes mixed, ideally we always just get mixed but I'm not sure that's possible with addRows. - Rcpp::List data = obj.length() != 3 ? obj[0] : obj; - - value["value"] = RObject_to_JsonValue((Rcpp::RObject)data["value"]); - value["type"] = RObject_to_JsonValue((Rcpp::RObject)data["type"]); - value["format"] = RObject_to_JsonValue((Rcpp::RObject)data["format"]); - - - return value; - -} - - -Json::Value jaspObject::RObject_to_JsonValue(Rcpp::List obj) -{ - bool atLeastOneNamed = false; - - Rcpp::RObject namesListRObject = obj.names(); - Rcpp::CharacterVector namesList; - - if(!namesListRObject.isNULL()) - { - namesList = namesListRObject; - - for(int row=0; row=0; row--) //We go backwards because in R the first entry of a name in a list is used. So to emulate this we go backwars and we override an earlier occurence. (aka you have two elements with the name "a" in a list and in R list$a returns the first occurence. This is now also the element visible in the json.) - { - std::string name(namesList[row]); - - if(name == "") - name = "element_" + std::to_string(row); - - val[name] = RObject_to_JsonValue((Rcpp::RObject)obj[row]); - } - else - for(int row=0; row set) { Json::Value array(Json::arrayValue); @@ -765,5 +644,3 @@ Json::Value jaspObject::VectorJson_to_ArrayJson(std::vector vec) array.append(val); return array; } - - diff --git a/src/core/jaspObject.h b/src/core/jaspObject.h new file mode 100644 index 00000000..ccbaccc2 --- /dev/null +++ b/src/core/jaspObject.h @@ -0,0 +1,191 @@ +#ifndef JASPOBJECT_MANUAL_INCLUDE_GUARD +#define JASPOBJECT_MANUAL_INCLUDE_GUARD + +// CORE (R-free) version of jaspObject.h. +// The SEXP->Json conversion helpers moved to src/adapters/rcpp/rcppConversions.h, +// the R-facing jaspObject_Interface + module macros to +// src/adapters/rcpp/jaspObjectInterface.h, toRObject() dispatch to +// src/adapters/rcpp/rcppToRObject.*. Logging/decoding go through jaspHost. + +#include +#include +#include +#include +#include +#include +#include +#include "jaspEnums.h" +#include + +typedef void (*logFuncDef)(const std::string &); + +void jaspPrint( std::string msg); +std::string decodeColumnNames(const std::string & str); + + +#define JASPOBJECT_DEFAULT_POSITION 9999 +//#define JASP_RESULTS_DEBUG_TRACES + +class jaspContainer; + +std::string stringExtend(std::string & str, size_t len, char kar = ' '); +std::string stringRemove(std::string str, char kar = ' '); +std::vector stringSplit(std::string str, char kar = ';'); + +//Simple base-class for all JASP-objects, containing things like a title or a warning and stuff like that +class jaspObject +{ +public: + jaspObject() : _title(""), _type(jaspObjectType::unknown) { allocatedObjects->insert(this); } + jaspObject(std::string title) : _title(title), _type(jaspObjectType::unknown) { allocatedObjects->insert(this); } + jaspObject(jaspObjectType type, std::string title) : _title(title), _type(type) { allocatedObjects->insert(this); } + jaspObject(const jaspObject& that) = delete; + virtual ~jaspObject(); + + std::string objectTitleString(std::string prefix="") const { return prefix + jaspObjectTypeToString(_type) + " " + _title; } + virtual std::string dataToString(std::string) const { return ""; } + std::string toString(std::string prefix = "") const; + + virtual std::string toHtml() const { return ""; } + std::string htmlTitle() const { return "

" + _title + "

"; } + + std::string type() { return jaspObjectTypeToString(_type); } + + bool getError() { return _error; } + virtual void setError() { _error = true; } + virtual void setError(std::string message) { _errorMessage = message; _error = true; } + virtual void clearError() { _error = false; _errorMessage.clear(); } + virtual bool canShowErrorMessage() const { return false; } + + virtual void letRun() {} ///< overriden by jaspTable/jaspPlot; jaspContainer::letChildrenRun dispatches on it + virtual void complete() {} ///< idem for jaspContainer::completeChildren + + void print() { try { jaspPrint(toString()); } catch(std::exception e) { jaspPrint(std::string("toString failed because of: ") + e.what()); } } + void addMessage(std::string msg) { _messages.push_back(msg); } + virtual void childrenUpdatedCallbackHandler(bool) {} ///Can be called by jaspResults to send changes and stuff like that. + + void setOptionMustBeDependency(std::string optionName, Json::Value mustBeThis); + void setOptionMustContainDependency(std::string optionName, Json::Value mustContainThis); + void dependOnNestedOptions(std::vector nestedOptionName); + void setNestedOptionMustContainDependency(std::vector nestedOptionName, Json::Value mustContainThis); + void dependOnOptions(std::vector listOptions); + void copyDependenciesFromJaspObject(jaspObject * other); + + bool checkDependencies(Json::Value currentOptions); //returns false if no longer valid and destroys children (if applicable) that are no longer valid + virtual void checkDependenciesChildren(Json::Value currentOptions) {} + + void addCitation(std::string fullCitation); + + std::string _title, + _info; + int _position = JASPOBJECT_DEFAULT_POSITION; + + jaspObjectType getType() const { return _type; } + virtual bool shouldBePartOfResultsJson(bool meta = false) const { return _type != jaspObjectType::state; } + + Json::Value constructMetaEntry(std::string type, std::string meta = "") const; + + //These functions convert the object to a json that can be understood by the resultsviewer + virtual Json::Value metaEntry() const { return Json::Value(Json::nullValue); } + virtual Json::Value dataEntry(std::string & errorMessage) const ; + + //These two are meant for jaspContainer and take old results into account and a possible errorMessage + virtual Json::Value metaEntry(jaspObject * oldResult) const { return metaEntry(); } + virtual Json::Value dataEntry(jaspObject * oldResult, std::string & errorMessage) const { return dataEntry(errorMessage); } + + Json::Value dataEntryBase() const; + + //These functions convert to object and all to a storable json-representation that can be written to disk and loaded again. + virtual Json::Value convertToJSON() const; + static jaspObject * convertFromJSON(Json::Value in); + virtual void convertFromJSON_SetFields(Json::Value in); + + ///Gives nested name to avoid namingclashes + std::string getUniqueNestedName() const; + void getUniqueNestedNameVector(std::vector & names) const; + void setName(std::string name) { _name = name; } + const std::string & name() const { return _name; } + + void childrenUpdatedCallback(bool ignoreSendTimer); + virtual void childFinalizedHandler(jaspObject * child) {} + void childFinalized(jaspObject * child); + void finalized(); + virtual void finalizedHandler() {} + + static void destroyAllAllocatedObjects(); + + std::set & getChildren() { return children; } + + static Json::Value currentOptions; + + void notifyParentOfChanges(); ///let ancestors know about updates + + static int getCurrentTimeMs(); + static void setDeveloperMode(bool developerMode); + + bool connectedToJaspResults(); + + virtual jaspObject * getOldObjectFromUniqueNestedNameVector(const std::vector &uniqueName); + + static Json::Value SetJson_to_ArrayJson(std::set set); + static std::set ArrayJson_to_SetJson(Json::Value arr); + static Json::Value VectorJson_to_ArrayJson(std::vector vec); + + bool getEscapeHtml() const { return _escapeHtml; } + +protected: + jaspObjectType _type; + std::string _errorMessage = ""; + bool _error = false, + _escapeHtml = true; // Used to escape Html characters when converting R object to Json. This is true per default, because the results of these objects are usually send to a Web Browser. + + std::vector _messages; + std::set _citations; + std::string _name; + + std::set nestedMustBes() const; + std::map> nestedMustContains() const; + std::map _optionMustContain; + std::map _optionMustBe; + std::map, Json::Value> _nestedOptionMustContain; + std::map, Json::Value> _nestedOptionMustBe; + + +//Should add dependencies somehow here? + +//Some basic administration of objecttree: + bool hasAncestor(jaspObject * ancestor) { return parent == ancestor || parent == NULL ? false : parent->hasAncestor(ancestor); } + void addChild(jaspObject * child); + + void removeChild(jaspObject * child); + + + jaspObject *parent = NULL; + std::set children; + + static std::set * allocatedObjects; + static bool _developerMode; + +private: + + Json::Value getObjectFromNestedOption(std::vector nestedKey, Json::Value ifNotFound = Json::nullValue) const; + std::string nestedKeyToString(const std::vector & nestedKey, const std::string & sep = "$!_SEP_!$") const; + std::vector stringToNestedKey(const std::string & nestedKey, const std::string & sep = "$!_SEP_!$") const; + bool isJsonSubArray(const Json::Value needle, const Json::Value haystack) const; + + bool _finalizedAlready = false; +}; + +void jaspObjectFinalizer(jaspObject * obj); + +//#define JASP_R_INTERFACE_TIMERS + +#ifdef JASP_R_INTERFACE_TIMERS +#define JASP_OBJECT_TIMERBEGIN static int cumulativeTime = 0; int startSerialize = getCurrentTimeMs(); +#define JASP_OBJECT_TIMEREND(ACTIVITY) cumulativeTime += getCurrentTimeMs() - startSerialize; std::cout << jaspObjectTypeToString(getType()) << " spent " << cumulativeTime << "ms " #ACTIVITY "!" << std::endl; +#else +#define JASP_OBJECT_TIMERBEGIN /* Doin' nothing */ +#define JASP_OBJECT_TIMEREND(ACTIVITY) /* What you didn't start you need not stop */ +#endif + +#endif diff --git a/src/core/jaspPlot.cpp b/src/core/jaspPlot.cpp new file mode 100644 index 00000000..2230c40a --- /dev/null +++ b/src/core/jaspPlot.cpp @@ -0,0 +1,148 @@ +// CORE (R-free) version of jaspPlot.cpp. +// Rendering is delegated to jaspHost::renderPlot; the R adapter supplies the +// original tryToWriteImage logic (see adapters/rcpp/rcppPlot.cpp). + +#include "jaspPlot.h" + +jaspPlot::~jaspPlot() +{ +#ifdef JASP_RESULTS_DEBUG_TRACES + jaspPrint("Destructor of JASPplot("+_title+") is called! "); +#endif + + finalizedHandler(); +} + +std::string jaspPlot::dataToString(std::string prefix) const +{ + std::stringstream out; + + out << + prefix << "aspectRatio: " << _aspectRatio << "\n" << + prefix << "dims: " << _width << "X" << _height << "\n" << + prefix << "error: '" << _error << "': '" << _errorMessage << "'\n" << + prefix << "filePath: " << _filePathPng << "\n" << + prefix << "status: " << _status << "\n" ; + + return out.str(); +} + +Json::Value jaspPlot::dataEntry(std::string & errorMessage) const +{ + Json::Value data(jaspObject::dataEntry(errorMessage)); + + data["title"] = _title; + data["convertible"] = true; + data["data"] = _filePathPng; + data["height"] = _height; + data["width"] = _width; + data["aspectRatio"] = _aspectRatio; + data["status"] = _error ? "error" : _status; + data["revision"] = _revision; + data["name"] = getUniqueNestedName(); + data["editOptions"] = _editOptions; + data["reasonNotEditable"] = _editOptions.get("reasonNotEditable", "unknown reason"); + data["errorType"] = _editOptions.get("errorType", "fatalError"); + data["editable"] = !_editOptions.isNull() && data["errorType"] == "success"; + + data["interactive"] = _interactive; + data["interactiveConvertError"] = _interactiveConvertError; + data["interactiveJsonData"] = _interactiveJsonData; + + data["export"] = _export; + + return data; +} + +void jaspPlot::initEnvName() +{ + static int counter = 0; + + _envName = "plot_" + std::to_string(counter++); +} + +void jaspPlot::setPlotObject(std::any plotSerialized) +{ + if (!_editing) + _filePathPng = ""; + + jaspHost::storeObject(_envName, std::move(plotSerialized)); + + if (connectedToJaspResults()) + renderPlot(); +} + +void jaspPlot::renderPlot() +{ + if (jaspHost::renderPlot) + jaspHost::renderPlot(*this); +} + +Json::Value jaspPlot::convertToJSON() const +{ + Json::Value obj = jaspObject::convertToJSON(); + + obj["aspectRatio"] = _aspectRatio; + obj["width"] = _width; + obj["height"] = _height; + obj["status"] = _status; + obj["filePathPng"] = _filePathPng; + obj["revision"] = _revision; + obj["environmentName"] = _envName; + obj["editOptions"] = _editOptions; + obj["resizedByUser"] = _resizedByUser; + + obj["interactive"] = _interactive; + obj["interactiveConvertError"] = _interactiveConvertError; + obj["interactiveJsonData"] = _interactiveJsonData; + + obj["export"] = _export; + + return obj; +} + +void jaspPlot::convertFromJSON_SetFields(Json::Value in) +{ + jaspObject::convertFromJSON_SetFields(in); + + _aspectRatio = in.get("aspectRatio", 0.0f).asDouble(); + _width = in.get("width", -1).asInt(); + _height = in.get("height", -1).asInt(); + _revision = in.get("revision", 0).asInt(); + _status = in.get("status", "complete").asString(); + _filePathPng = in.get("filePathPng", "null").asString(); + _envName = in.get("environmentName", _envName).asString(); + _editOptions = in.get("editOptions", Json::nullValue); + _resizedByUser = in.get("resizedByUser", false).asBool(); + + _interactive = in.get("interactive", false).asBool(); + _interactiveConvertError = in.get("interactiveConvertError", "").asString(); + _interactiveJsonData = in.get("interactiveJsonData", "").asString(); + + _export = in.get("export", Json::nullValue); + + if (jaspHost::plotStateSync) + jaspHost::plotStateSync(*this); +} + +std::string jaspPlot::toHtml() const +{ + std::stringstream out; + + out << "
" "\n" + << htmlTitle() << "\n"; + + if(_error || _errorMessage != "") + { + out << "

\n"; + if(_error ) out << "error: '" << _error << "'"; + if(_errorMessage != "") out << (_error ? " msg: '" : "errormessage: '") << _errorMessage << "'"; + out << "\n

"; + } + else + out << "\"a"; + + out << "
\n"; + + return out.str(); +} diff --git a/src/core/jaspPlot.h b/src/core/jaspPlot.h new file mode 100644 index 00000000..e207476d --- /dev/null +++ b/src/core/jaspPlot.h @@ -0,0 +1,62 @@ +#pragma once + +// CORE (R-free) version of jaspPlot.h. Plot object payloads travel through the +// jaspHost object store as opaque std::any handles. Rendering is delegated to +// jaspHost::renderPlot (R adapter implements the legacy tryToWriteImage path; +// Python adapter will implement plotly/matplotlib rendering). The R-facing +// jaspPlot_Interface lives in src/adapters/rcpp/rcppInterfaces.h. + +#include "jaspObject.h" +#include "jaspHost.h" +#include + +class jaspPlot : public jaspObject +{ +public: + jaspPlot(std::string title = "") : jaspObject(jaspObjectType::plot, title) { initEnvName(); } + + ~jaspPlot(); + + float _aspectRatio = 0.0f; + int _width = 0, + _height = 0, + _revision = 0; + bool _editing = false, + _resizedByUser = false, + _interactive = false; + std::string _filePathPng, + _status = "waiting", + _envName, + _interactiveConvertError = "", + _interactiveJsonData = ""; + Json::Value _editOptions = Json::nullValue; + + ///Machine-readable data exported by analysis authors for consumers + ///like RoboReport (e.g., median effect size, credible intervals, + ///BF at specific prior widths). Survives RDS stripping because it's + ///a plain JSON value, not an environment or ggplot object. + Json::Value _export = Json::nullValue; + + ///For safekeeping (aka state replacement?) + void setPlotObject(std::any plotSerialized); + void renderPlot(); + + std::string dataToString(std::string prefix) const override; + + Json::Value metaEntry() const override { return constructMetaEntry("image"); } + Json::Value dataEntry(std::string & errorMessage) const override; + std::string toHtml() const override; + + Json::Value convertToJSON() const override; + void convertFromJSON_SetFields(Json::Value in) override; + + bool canShowErrorMessage() const override { return true; } + + void complete() override { if(_status == "running" || _status == "waiting") _status = "complete"; } + void letRun() override { _status = "running"; } + +private: + void initEnvName(); + + //Rcpp::Vector _plotObjSerialized; +}; diff --git a/src/jaspQmlSource.cpp b/src/core/jaspQmlSource.cpp similarity index 100% rename from src/jaspQmlSource.cpp rename to src/core/jaspQmlSource.cpp diff --git a/src/jaspQmlSource.h b/src/core/jaspQmlSource.h similarity index 62% rename from src/jaspQmlSource.h rename to src/core/jaspQmlSource.h index ed377265..d13d5b7b 100644 --- a/src/jaspQmlSource.h +++ b/src/core/jaspQmlSource.h @@ -1,6 +1,11 @@ #ifndef JASPQMLSOURCE_H #define JASPQMLSOURCE_H +// CORE (R-free) version of jaspQmlSource.h. setValue() takes Json::Value (the +// Rcpp conversion happens in jaspQmlSource_Interface); the unused Rcpp-only +// RcppVector_to_ArrayJson helper was dropped. jaspQmlSource_Interface moved to +// src/adapters/rcpp/rcppInterfaces.h. + #include "jaspObject.h" class jaspQmlSource : public jaspObject @@ -10,7 +15,7 @@ class jaspQmlSource : public jaspObject void setSourceID(const std::string & sourceID) { _sourceID = sourceID; } std::string sourceID() const; - void setValue(Rcpp::RObject Robj) { _json = RObject_to_JsonValue(Robj); _changed = true; } + void setValue(Json::Value json) { _json = json; _changed = true; } std::string getValue() const { return _json.toStyledString(); } Json::Value metaEntry() const override; @@ -23,11 +28,9 @@ class jaspQmlSource : public jaspObject std::string jsonToPrefixedStrings(std::string prefix = "") const { return jsonToPrefixedStrings(_json, prefix); } std::string jsonToPrefixedStrings(Json::Value val, std::string prefix) const; - Json::Value RcppVector_to_ArrayJson(Rcpp::RObject obj, bool throwError=true) { return VectorJson_to_ArrayJson(RcppVector_to_VectorJson(obj, throwError)); } - bool shouldBePartOfResultsJson(bool meta = false) const override; - void complete() { _complete = true; } + void complete() override { _complete = true; } bool changed() const { return _changed; } std::string _sourceID; @@ -38,19 +41,4 @@ class jaspQmlSource : public jaspObject }; - -class jaspQmlSource_Interface : public jaspObject_Interface -{ -public: - jaspQmlSource_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspQmlSource, std::string, _sourceID, SourceID) - - void setValue(Rcpp::RObject obj) { ((jaspQmlSource*)myJaspObject)->setValue(obj); } - std::string getValue() { return ((jaspQmlSource*)myJaspObject)->getValue(); } -}; - -RCPP_EXPOSED_CLASS_NODECL(jaspQmlSource_Interface) - - #endif // JASPQMLSOURCE_H diff --git a/src/jaspReport.cpp b/src/core/jaspReport.cpp similarity index 94% rename from src/jaspReport.cpp rename to src/core/jaspReport.cpp index 6cfaa757..338ef9c6 100644 --- a/src/jaspReport.cpp +++ b/src/core/jaspReport.cpp @@ -1,6 +1,6 @@ #include "jaspReport.h" #include "jaspHtml.h" -#include "jaspResults.h" +#include "jaspHost.h" size_t jaspReport::_totalWarnings = 0; @@ -43,7 +43,7 @@ Json::Value jaspReport::dataEntry(std::string & errorMessage) const data["report"] = _report; data["warningIndex"] = int(_warningIndex); data["warningsTotal"] = int(_totalWarnings); - data["analysisId"] = jaspResults::analysisId(); //Used to find analysis position in js and reposition topnodes + data["analysisId"] = jaspHost::analysisId(); //Used to find analysis position in js and reposition topnodes return data; } diff --git a/src/jaspReport.h b/src/core/jaspReport.h similarity index 58% rename from src/jaspReport.h rename to src/core/jaspReport.h index d932df5e..ac8323af 100644 --- a/src/jaspReport.h +++ b/src/core/jaspReport.h @@ -1,10 +1,14 @@ #pragma once + +// CORE (R-free) version of jaspReport.h. jaspReport_Interface moved to +// src/adapters/rcpp/rcppInterfaces.h; analysisId comes from jaspHost. + #include "jaspObject.h" class jaspReport : public jaspObject { public: - jaspReport(Rcpp::String text = "", bool report = false) + jaspReport(std::string text = "", bool report = false) : jaspObject(jaspObjectType::report, ""), _rawText(text), _report(report) {} @@ -20,7 +24,7 @@ class jaspReport : public jaspObject Json::Value convertToJSON() const override; void convertFromJSON_SetFields(Json::Value in) override; - void setText(Rcpp::String newRawText) { _rawText = newRawText; } + void setText(std::string newRawText) { _rawText = newRawText; } std::string getText() const { return _rawText; } std::string _rawText; @@ -33,19 +37,3 @@ class jaspReport : public jaspObject size_t _warningIndex = 0; static size_t _totalWarnings; }; - - - -class jaspReport_Interface : public jaspObject_Interface -{ -public: - jaspReport_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - void setText(Rcpp::String newRawText) { static_cast(myJaspObject)->setText(newRawText); } - Rcpp::String getText() { return static_cast(myJaspObject)->getText(); } - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspReport, bool, _report, Report) -}; - -RCPP_EXPOSED_CLASS_NODECL(jaspReport_Interface) - diff --git a/src/jaspResults.cpp b/src/core/jaspResults.cpp similarity index 67% rename from src/jaspResults.cpp rename to src/core/jaspResults.cpp index 8997000c..f75da25d 100644 --- a/src/jaspResults.cpp +++ b/src/core/jaspResults.cpp @@ -1,10 +1,22 @@ +// CORE (R-free) version of jaspResults.cpp. +// R-specific parts (R storage environment, RDS export, Rcpp::List state +// harvest, XPtr registration, signalAnalysisAbort R call) moved to +// src/adapters/rcpp/rcppResults.cpp. + #include -#include "jaspModuleRegistration.h" #include #include +#include +#include "jaspResults.h" +#include "jaspTable.h" +#include "jaspColumn.h" +#include "jaspHtml.h" +#include "jaspState.h" +#include "jaspPlot.h" +#include "jaspQmlSource.h" +#include "jaspReport.h" -#include typedef std::ofstream bofstream; typedef std::ifstream bifstream; #define BREMOVE std::remove //Also not a type @@ -17,19 +29,17 @@ std::string jaspResults::_saveResultsRoot = ""; std::string jaspResults::_writeSealRoot = ""; std::string jaspResults::_writeSealRelative = ""; std::string jaspResults::_baseCitation = ""; -Rcpp::Environment* jaspResults::_RStorageEnv = nullptr; bool jaspResults::_insideJASP = false; jaspResults* jaspResults::_jaspResults = nullptr; -int jaspResults::_analysisId = -1; -void jaspResults::setSendFunc(Rcpp::XPtr sendFunc) +void jaspResults::setSendFunc(sendFuncDef sendFunc) { - _ipccSendFunc = *sendFunc; + _ipccSendFunc = sendFunc; } -void jaspResults::setPollMessagesFunc(Rcpp::XPtr pollFunc) +void jaspResults::setPollMessagesFunc(pollMessagesFuncDef pollFunc) { - _ipccPollFunc = *pollFunc; + _ipccPollFunc = pollFunc; } @@ -40,7 +50,7 @@ void jaspResults::setBaseCitation(std::string baseCitation) void jaspResults::setAnalysisId(int analysisId) { - _analysisId = analysisId; + jaspHost::setAnalysisId(analysisId); } void jaspResults::setResponseData(int analysisID, int revision) @@ -79,45 +89,35 @@ void jaspResults::setInsideJASP() _insideJASP = true; } -jaspResults::jaspResults(Rcpp::String title, Rcpp::RObject oldState) +jaspResults::jaspResults(std::string title) : jaspContainer(title, jaspObjectType::results) { _jaspResults = this; - if(_RStorageEnv != nullptr) - delete _RStorageEnv; - - if(_insideJASP) - { - Rcpp::Environment::global_env()["RStorageEnv"] = Rcpp::Environment::global_env().new_child(true); - _RStorageEnv = new Rcpp::Environment(Rcpp::Environment::global_env()["RStorageEnv"]); - - if(_writeSealRoot + _writeSealRelative == "") - throw std::runtime_error("Write seal location not given and we are running in JASP, this should never happen!"); - } - else - _RStorageEnv = new Rcpp::Environment(Rcpp::as(Rcpp::Environment::namespace_env("jaspBase")[".plotStateStorage"])); - - bool imNotReincarnatedAfterBeingMurdered = lastWriteWorked(); + if(_insideJASP && _writeSealRoot + _writeSealRelative == "") + throw std::runtime_error("Write seal location not given and we are running in JASP, this should never happen!"); - if(imNotReincarnatedAfterBeingMurdered && !oldState.isNULL() && Rcpp::is(oldState)) - fillEnvironmentWithStateObjects(Rcpp::as(oldState)); + // The host adapter is expected to have set up its object store (R build: + // _RStorageEnv + rcppWireHostStore, see adapters/rcpp/rcppResults.cpp) and + // to fill it with old state objects before calling + // loadResultsIfLastWriteWorked(), exactly as the old constructor did. setStatus("running"); if(_baseCitation != "") addCitation(_baseCitation); - - if(imNotReincarnatedAfterBeingMurdered && _saveResultsHere != "") - loadResults(); } jaspResults::~jaspResults() { - if(_RStorageEnv != nullptr) - delete _RStorageEnv; - - _RStorageEnv = nullptr; + if(jaspHost::destroyObjectStore) + jaspHost::destroyObjectStore(); +} + +void jaspResults::loadResultsIfLastWriteWorked() +{ + if(lastWriteWorked() && _saveResultsHere != "") + loadResults(); } void jaspResults::setStatus(std::string status) @@ -200,42 +200,16 @@ void jaspResults::saveResults() { static std::string error; error = "Could not open file for saving jaspResults! File: '" + _saveResultsRoot + _saveResultsHere + "'"; - Rf_error("%s", error.c_str());; + throw std::runtime_error(error); } saveHere << convertToJSON() << std::flush; saveHere.close(); - if (std::getenv("JASP_RESULTS_RDS") == nullptr) { JASP_OBJECT_TIMEREND(saveResults); return; } - - - // Also write results as an RDS file alongside the JSON - std::string rdsPath = _saveResultsRoot + _saveResultsHere; - size_t dotPos = rdsPath.rfind(".json"); - if(dotPos != std::string::npos) - rdsPath.replace(dotPos, 5, ".rds"); - else - rdsPath += ".rds"; - - // By default, strip bulky environments and plot objects from the - // RDS tree before saving. This keeps the file small (KB) for - // consumers like RoboReport. - // Users can opt out to get the full toRObject() tree (e.g. for - // debugging) by setting the env var: JASP_RDS_STRIP=FALSE (or 0/no) - Rcpp::RObject rdsObject = toRObject(); - const char* stripEnvVal = std::getenv("JASP_RDS_STRIP"); - bool shouldStrip = (stripEnvVal == nullptr) || - (strcmp(stripEnvVal, "FALSE") != 0 && strcmp(stripEnvVal, "0") != 0 && - strcmp(stripEnvVal, "NO") != 0 && strcmp(stripEnvVal, "no") != 0 && - strcmp(stripEnvVal, "No") != 0); - if (shouldStrip) - { - Rcpp::Environment jaspBaseEnv = Rcpp::Environment::namespace_env("jaspBase"); - Rcpp::Function stripEnv = jaspBaseEnv[".jaspResults_stripEnv"]; - rdsObject = stripEnv(rdsObject); - } - Rcpp::Function saveRDS("saveRDS"); - saveRDS(rdsObject, rdsPath); - jaspPrint("Saved jaspResults as RDS to: '" + rdsPath + "'"); + + // Host-specific state archive (R: RDS alongside the JSON; Python: pickle). + // The host decides whether/what to write; see adapters. + if(jaspHost::saveStateArchive) + jaspHost::saveStateArchive(*this, _saveResultsRoot + _saveResultsHere); JASP_OBJECT_TIMEREND(saveResults) } @@ -261,7 +235,7 @@ void jaspResults::loadResults() { static std::string error; error = "loading jaspResults had a problem, '" + _saveResultsRoot + _saveResultsHere + "' wasn't a JSON object!"; - Rf_error("%s", error.c_str());; + throw std::runtime_error(error); } convertFromJSON_SetFields(val); @@ -324,8 +298,8 @@ void jaspResults::checkForAnalysisChanged() { jaspPrint("Polling for analysis changes found a change, analysis should restart!"); setStatus("changed"); - static Rcpp::Function signalAnalysisAbort = Rcpp::Environment::namespace_env("jaspBase")["signalAnalysisAbort"]; - signalAnalysisAbort(); + if(jaspHost::signalAnalysisAbort) + jaspHost::signalAnalysisAbort(); } } @@ -425,108 +399,71 @@ Json::Value jaspResults::dataEntry(std::string &) const -void jaspResults::setErrorMessage(Rcpp::String msg, std::string errorStatus) +void jaspResults::setErrorMessage(std::string msg, std::string errorStatus) { errorMessage = msg; setStatus(errorStatus); } -Rcpp::List jaspResults::getPlotObjectsForState() +std::vector jaspResults::harvestPlotObjects() { - Rcpp::List returnThis; - Rcpp::Shield protectList(returnThis); + std::vector entries; JASP_OBJECT_TIMERBEGIN - addSerializedPlotObjsForStateFromJaspObject(this, returnThis); + addPlotStateEntriesFromJaspObject(this, entries); JASP_OBJECT_TIMEREND(getting plot objects) - return returnThis; + + return entries; } -void jaspResults::addSerializedPlotObjsForStateFromJaspObject(jaspObject * obj, Rcpp::List & pngImgObj) +void jaspResults::addPlotStateEntriesFromJaspObject(jaspObject * obj, std::vector & entries) { if(obj->getType() == jaspObjectType::plot) { jaspPlot * plot = (jaspPlot*)obj; if(plot->_filePathPng != "") - { - Rcpp::List pngImg; - pngImg["obj"] = plot->getPlotObject(); - pngImg["width"] = plot->_width; - pngImg["height"] = plot->_height; - pngImg["revision"] = plot->_revision; - pngImg["envName"] = plot->_envName; - pngImg["getUnique"] = plot->getUniqueNestedName(); - pngImgObj[plot->_filePathPng] = pngImg; - } + entries.push_back({ plot->_filePathPng, plot->_envName, plot->getUniqueNestedName(), plot->_width, plot->_height, plot->_revision }); } for(auto c : obj->getChildren()) - addSerializedPlotObjsForStateFromJaspObject(c, pngImgObj); + addPlotStateEntriesFromJaspObject(c, entries); } -Rcpp::List jaspResults::getOtherObjectsForState() +std::vector jaspResults::harvestStateEnvNames() { - Rcpp::List returnThis; - Rcpp::Shield protectList(returnThis); + std::vector envNames; JASP_OBJECT_TIMERBEGIN - addSerializedOtherObjsForStateFromJaspObject(this, returnThis); + addStateEnvNamesFromJaspObject(this, envNames); JASP_OBJECT_TIMEREND(getting other objects) - return returnThis; + + return envNames; } -void jaspResults::addSerializedOtherObjsForStateFromJaspObject(jaspObject * obj, Rcpp::List & cumulativeList) +void jaspResults::addStateEnvNamesFromJaspObject(jaspObject * obj, std::vector & envNames) { if(obj->getType() == jaspObjectType::state) { - jaspState * state = (jaspState*)obj; //If other objects are needed this code can be generalized + jaspState * state = (jaspState*)obj; //If other objects are needed this code can be generalized - if(objectExistsInEnv(state->_envName)) - cumulativeList[state->_envName] = state->getObject(); + if(jaspHost::objectExists(state->_envName)) + envNames.push_back(state->_envName); } for(auto child : obj->getChildren()) - addSerializedOtherObjsForStateFromJaspObject(child, cumulativeList); + addStateEnvNamesFromJaspObject(child, envNames); } -void jaspResults::fillEnvironmentWithStateObjects(Rcpp::List state) +std::vector jaspResults::harvestPlotPathsForKeep() { - if(state.containsElementNamed("figures")) - { - //Let's try to load all previous plots from the state! - Rcpp::List figures = state["figures"]; - - for(Rcpp::List plotInfo : figures) - if(plotInfo.containsElementNamed("envName") && plotInfo.containsElementNamed("obj")) - { - std::string envName = Rcpp::as(plotInfo["envName"]); - (*_RStorageEnv)[envName] = plotInfo; - } - } - - if(state.containsElementNamed("other")) - { - //Let's try to load all previous plots from the state! - Rcpp::List others = state["other"]; - Rcpp::List names = others.names(); - - for(std::string name : names) - (*_RStorageEnv)[name] = others[name]; - } -} + std::vector plotPaths; -Rcpp::List jaspResults::getPlotPathsForKeep() -{ - Rcpp::List returnThis; - auto * protectList = new Rcpp::Shield(returnThis); + addPlotPathsForKeepFromJaspObject(this, plotPaths); - addPlotPathsForKeepFromJaspObject(this, returnThis); - - delete protectList; - return returnThis; + return plotPaths; } -void jaspResults::addPlotPathsForKeepFromJaspObject(jaspObject * obj, Rcpp::List & pngPlotPaths) +void jaspResults::addPlotPathsForKeepFromJaspObject(jaspObject * obj, std::vector & pngPlotPaths) { if(obj->getType() == jaspObjectType::plot) { @@ -543,12 +480,13 @@ void jaspResults::addPlotPathsForKeepFromJaspObject(jaspObject * obj, Rcpp::List addPlotPathsForKeepFromJaspObject(c, pngPlotPaths); } -Rcpp::List jaspResults::getKeepList() +std::vector jaspResults::getKeepListVector() { - Rcpp::List keep = getPlotPathsForKeep(); - keep.push_front(std::string(_saveResultsHere)); - keep.push_front(std::string(_writeSealRelative)); - keep.push_front(_relativePathKeep); + std::vector keep = harvestPlotPathsForKeep(); + + keep.insert(keep.begin(), _saveResultsHere); + keep.insert(keep.begin(), _writeSealRelative); + keep.insert(keep.begin(), _relativePathKeep); // Also keep jaspResults.rds if it was saved alongside the JSON if (!_saveResultsHere.empty()) @@ -557,7 +495,7 @@ Rcpp::List jaspResults::getKeepList() size_t dot = rdsPath.rfind('.'); if (dot != std::string::npos) rdsPath.replace(dot, std::string::npos, ".rds"); - keep.push_front(rdsPath); + keep.insert(keep.begin(), rdsPath); } return keep; @@ -584,7 +522,7 @@ void jaspResults::convertFromJSON_SetFields(Json::Value in) -void jaspResults::startProgressbar(int expectedTicks, Rcpp::String label) +void jaspResults::startProgressbar(int expectedTicks, std::string label) { _progressbarExpectedTicks = expectedTicks; _progressbarLastUpdateTime = getCurrentTimeMs(); @@ -592,7 +530,7 @@ void jaspResults::startProgressbar(int expectedTicks, Rcpp::String label) Json::Value progress; progress["value"] = 0; - progress["label"] = std::string(label); + progress["label"] = label; _response["progress"] = progress; send(); @@ -642,20 +580,3 @@ jaspObject * jaspObject::convertFromJSON(Json::Value in) return newObject; } - -Rcpp::RObject jaspResults::getObjectFromEnv(std::string envName) -{ - if(_RStorageEnv->exists(envName)) - return (*_RStorageEnv)[envName]; - return R_NilValue; -} - -void jaspResults::setObjectInEnv(std::string envName, Rcpp::RObject obj) -{ - (*_RStorageEnv)[envName] = obj; -} - -bool jaspResults::objectExistsInEnv(std::string envName) -{ - return _RStorageEnv->exists(envName); -} diff --git a/src/core/jaspResults.h b/src/core/jaspResults.h new file mode 100644 index 00000000..0843c958 --- /dev/null +++ b/src/core/jaspResults.h @@ -0,0 +1,133 @@ +#pragma once + +// CORE (R-free) version of jaspResults. The R storage environment, RDS save, +// Rcpp::List state harvest, XPtr send/poll unwrapping and the R-facing +// jaspResults_Interface live in src/adapters/rcpp/rcppResults. The object +// store is reached through jaspHost (R build wires it to the R environment). + +#include "jaspContainer.h" +#include "jaspHost.h" + +//copied from jasprcpp_interface.h +typedef void (*sendFuncDef)(const char *); +typedef bool (*pollMessagesFuncDef)(); + +///Neutral plot-state harvest entry. The R adapter rebuilds today's +///Rcpp::List shape (obj/width/height/revision/envName/getUnique) from these. +struct jaspPlotStateEntry +{ + std::string filePathPng, envName, uniqueNestedName; + int width = 0, height = 0, revision = 0; +}; + +class jaspResults : public jaspContainer +{ +public: + jaspResults(std::string title); + ~jaspResults(); + + //static functions to allow the values to be set before the constructor is called from R. Would be nicer to just run the constructor in C++ maybe? + static void setSendFunc(sendFuncDef sendFunc); + static void setPollMessagesFunc(pollMessagesFuncDef pollFunc); + static void setResponseData(int analysisID, int revision); + static void setSaveLocation(const std::string & root, const std::string & relativePath); + static void setWriteSealLocation(const std::string & root, const std::string & relativePath); + static void setBaseCitation(std::string baseCitation); + static void setInsideJASP(); + static bool isInsideJASP() { return _insideJASP; } + static std::string writeSealFilename() { return "jaspResultsFinishedWriting.txt"; } + + void send(std::string otherMsg = ""); + void checkForAnalysisChanged(); + void setStatus(std::string status); + std::string getStatus(); + + const char * constructResultJson(); + Json::Value metaEntry() const override; + Json::Value dataEntry(std::string & errorMessage) const override; + Json::Value dataEntry() const { std::string dummy(""); return dataEntry(dummy); } + + void childrenUpdatedCallbackHandler(bool ignoreSendTimer) override; + + void finalizedHandler() override { complete(); } + void complete() override; + + void prepareForWriting(); + void finishWriting(); + bool lastWriteWorked() const; + void saveResults(); + + void loadResults(); + void setErrorMessage(std::string msg, std::string errorStatus); + void changeOptions(std::string opts); + void setOptions(std::string opts); + void pruneInvalidatedData(); + + ///Neutral state harvests (host adapters convert to their native shapes). + std::vector harvestPlotObjects(); + std::vector harvestStateEnvNames(); + std::vector harvestPlotPathsForKeep(); + std::vector getKeepListVector(); + + std::string getResults() { return constructResultJson(); } + + std::string _relativePathKeep; + + Json::Value convertToJSON() const override; + void convertFromJSON_SetFields(Json::Value in) override; + + ///Second half of the old constructor: load previous results from disk if + ///the last write worked. Host adapters call this after they have set up + ///the object store and filled it with old-state objects. + void loadResultsIfLastWriteWorked(); + + void startProgressbar(int expectedTicks, std::string label); + void progressbarTick(); + + static void staticStartProgressbar(int expectedTicks, std::string label) { _jaspResults->startProgressbar(expectedTicks, label); } + static void staticProgressbarTick() { _jaspResults->progressbarTick(); } + + static int analysisId() { return jaspHost::analysisId(); } ///< To pass analysisId to jaspReport easily + + jaspContainer * getOldResults() const { return _oldResults; } + + jaspObject * getOldObjectFromUniqueNestedNameVector(const std::vector& uniqueNames) override { return _oldResults == nullptr ? nullptr : _oldResults->findObjectWithNestedNameVector(uniqueNames); } ; + +private: + + // silences e.g., "./jaspResults.h:36:15: warning: 'jaspResults::dataEntry' hides overloaded virtual function [-Woverloaded-virtual]" + Json::Value metaEntry(jaspObject * ) const override { throw std::runtime_error("Don't call jaspResults::metaEntry(jaspObject * oldResult)"); }; + Json::Value dataEntry(jaspObject *, std::string & ) const override { throw std::runtime_error("Don't call jaspResults::dataEntry(jaspObject * oldResult, std::string & errorMsg)"); }; + + static jaspResults * _jaspResults; + static Json::Value _response; + static sendFuncDef _ipccSendFunc; + static pollMessagesFuncDef _ipccPollFunc; + static std::string _saveResultsHere, + _saveResultsRoot, + _baseCitation, + _writeSealRoot, + _writeSealRelative; + static bool _insideJASP; + + std::string errorMessage = ""; + Json::Value _currentOptions = Json::nullValue, + _previousOptions = Json::nullValue; + + jaspContainer * _oldResults = nullptr; + + void addPlotStateEntriesFromJaspObject( jaspObject * obj, std::vector & entries); + void addPlotPathsForKeepFromJaspObject( jaspObject * obj, std::vector & pngPlotPaths); + void addStateEnvNamesFromJaspObject( jaspObject * obj, std::vector & envNames); + void storeOldResults(); + + static void setAnalysisId(int analysisId); + + + int _progressbarExpectedTicks = 100, + _progressbarLastUpdateTime = -1, + _progressbarTicks = 0, + _sendingFeedbackLastTime = -1, + _progressbarBetweenUpdatesTime = 500, + _sendingFeedbackInterval = 1000; +}; diff --git a/src/jaspState.cpp b/src/core/jaspState.cpp similarity index 50% rename from src/jaspState.cpp rename to src/core/jaspState.cpp index 57b13e34..6e301adf 100644 --- a/src/jaspState.cpp +++ b/src/core/jaspState.cpp @@ -1,5 +1,8 @@ +// CORE (R-free) version of jaspState.cpp. Storage goes through the jaspHost +// object store; the R engine wires that store to jaspResults::_RStorageEnv so +// R's GC keeps the objects alive (see adapters/rcpp/rcppHost.cpp). + #include "jaspState.h" -#include "jaspResults.h" Json::Value jaspState::convertToJSON() const { @@ -16,21 +19,26 @@ void jaspState::convertFromJSON_SetFields(Json::Value in) } -void jaspState::setObject(Rcpp::RObject obj) +void jaspState::setObject(std::any obj) +{ + jaspHost::storeObject(_envName, std::move(obj)); +} + +std::any jaspState::getObject() { - jaspResults::setObjectInEnv(_envName, obj); + return jaspHost::fetchObject(_envName); } -Rcpp::RObject jaspState::getObject() +bool jaspState::hasObject() const { - return jaspResults::getObjectFromEnv(_envName); + return jaspHost::objectExists(_envName); } std::string jaspState::dataToString(std::string prefix) const { std::stringstream out; - out << prefix << "object stored: " << ( jaspResults::objectExistsInEnv(_envName) ? "no" : "yes") << "\n"; + out << prefix << "object stored: " << ( jaspHost::objectExists(_envName) ? "yes" : "no") << "\n"; return out.str(); } diff --git a/src/core/jaspState.h b/src/core/jaspState.h new file mode 100644 index 00000000..22751ac2 --- /dev/null +++ b/src/core/jaspState.h @@ -0,0 +1,28 @@ +#pragma once + +// CORE (R-free) version of jaspState.h. Object payloads travel through the +// jaspHost object store as opaque std::any handles (Rcpp::RObject in the R +// build, py::object in the Python build); jaspState_Interface in +// src/adapters/rcpp/rcppInterfaces.h keeps the R-facing SEXP API. + +#include "jaspObject.h" +#include "jaspHost.h" +#include + +class jaspState : public jaspObject +{ +public: + jaspState(std::string title = "") : jaspObject(jaspObjectType::state, title) { initEnvName(); } + + void setObject(std::any obj); + std::any getObject(); + bool hasObject() const; + + Json::Value convertToJSON() const override; + void convertFromJSON_SetFields(Json::Value in) override; + std::string dataToString(std::string prefix) const override; + std::string _envName; + +private: + void initEnvName(); +}; diff --git a/src/jaspTable.cpp b/src/core/jaspTable.cpp similarity index 71% rename from src/jaspTable.cpp rename to src/core/jaspTable.cpp index 2616e715..7999b587 100644 --- a/src/jaspTable.cpp +++ b/src/core/jaspTable.cpp @@ -12,56 +12,36 @@ std::string jaspColRowCombination::toString() return out.str(); } -size_t jaspTable::lengthFromRObject(Rcpp::RObject rObj) +void jaspTable::setDataColumns(const jaspTableData & newData) { - if(rObj.isNULL()) return 0; - else if(Rcpp::is(rObj)) return lengthFromList((Rcpp::List) rObj); - else if(Rcpp::is(rObj)) return lengthFromVector((Rcpp::NumericVector) rObj); - else if(Rcpp::is(rObj)) return lengthFromVector((Rcpp::LogicalVector) rObj); - else if(Rcpp::is(rObj)) return lengthFromVector((Rcpp::IntegerVector) rObj); - else if(Rcpp::is(rObj)) return lengthFromVector((Rcpp::StringVector) rObj); - else if(Rcpp::is(rObj)) return lengthFromVector((Rcpp::CharacterVector) rObj); - else Rf_error("Unexpected type.."); + _data.clear(); - return 0; + for(size_t col=0; col col ? newData.colNames[col] : ""); + for(size_t row=0; row column, size_t col) +{ + if(_data.size() <= col) + _data.resize(col + 1); + _data[col] = std::move(column); +} -void jaspTable::setData(Rcpp::RObject newData) +void jaspTable::setRowNamesWhereApplicable(std::vector rowNamesList) { -#ifdef JASP_RESULTS_DEBUG_TRACES - jaspPrint("jaspTable::setData"); -#endif - if(newData.isNULL()) + for(size_t row=0; row(newData)) setDataFromList(convertFactorsToCharacters((Rcpp::DataFrame) newData)); - else if(Rcpp::is(newData)) setDataFromList((Rcpp::List) newData); - - else if(Rcpp::is(newData)) setDataFromMatrix((Rcpp::NumericMatrix) newData); - else if(Rcpp::is(newData)) setDataFromMatrix((Rcpp::LogicalMatrix) newData); - else if(Rcpp::is(newData)) setDataFromMatrix((Rcpp::IntegerMatrix) newData); - else if(Rcpp::is(newData)) setDataFromMatrix((Rcpp::StringMatrix) newData); - else if(Rcpp::is(newData)) setDataFromMatrix((Rcpp::CharacterMatrix) newData); - - else if(Rcpp::is(newData)) setDataFromVector((Rcpp::NumericVector) newData); - else if(Rcpp::is(newData)) setDataFromVector((Rcpp::LogicalVector) newData); - else if(Rcpp::is(newData)) setDataFromVector((Rcpp::IntegerVector) newData); - else if(Rcpp::is(newData)) setDataFromVector((Rcpp::StringVector) newData); - else if(Rcpp::is(newData)) setDataFromVector((Rcpp::CharacterVector) newData); - - else - Rf_error("Cannot set this kind of data to a jaspTable, it is not understood. Try a list, dataframe, vector or matrix instead."); - - notifyParentOfChanges(); } + void jaspTable::addOrSetColumnInData(std::vector column, std::string colName) { if(colName == "") @@ -140,169 +120,6 @@ int jaspTable::getDesiredColumnIndexFromNameForRowAdding(std::string colName, in return std::max(_colNames.rowCount(), _data.size()); } -void jaspTable::setColumn(std::string columnName, Rcpp::RObject column) -{ - int colIndex = getDesiredColumnIndexFromNameForColumnAdding(columnName); - - if(Rcpp::is(column)) setColumnFromVector((Rcpp::NumericVector) column, colIndex); - else if(Rcpp::is(column)) setColumnFromVector((Rcpp::LogicalVector) column, colIndex); - else if(Rcpp::is(column)) setColumnFromVector((Rcpp::IntegerVector) column, colIndex); - else if(Rcpp::is(column)) setColumnFromVector((Rcpp::StringVector) column, colIndex); - else if(Rcpp::is(column)) setColumnFromVector((Rcpp::CharacterVector) column, colIndex); - else if(isMixedRObject(column)) setColumnFromMixedVector((Rcpp::List) column, colIndex); - else if(Rcpp::is(column)) setColumnFromList((Rcpp::List) column, colIndex); - else Rf_error("Did not get a vector or list as column.."); - - notifyParentOfChanges(); -} - -void jaspTable::addColumns(Rcpp::RObject newData) -{ - if(newData.isNULL()) - return; - - //Maybe this is overkill? - if(Rcpp::is(newData)) addColumnsFromList(convertFactorsToCharacters((Rcpp::DataFrame) newData)); - else if(Rcpp::is(newData)) addColumnsFromList((Rcpp::List) newData); - - else if(Rcpp::is(newData)) addColumnsFromMatrix((Rcpp::NumericMatrix) newData); - else if(Rcpp::is(newData)) addColumnsFromMatrix((Rcpp::LogicalMatrix) newData); - else if(Rcpp::is(newData)) addColumnsFromMatrix((Rcpp::IntegerMatrix) newData); - else if(Rcpp::is(newData)) addColumnsFromMatrix((Rcpp::StringMatrix) newData); - else if(Rcpp::is(newData)) addColumnsFromMatrix((Rcpp::CharacterMatrix)newData); - - else if(Rcpp::is(newData)) addColumnFromVector((Rcpp::NumericVector) newData); - else if(Rcpp::is(newData)) addColumnFromVector((Rcpp::LogicalVector) newData); - else if(Rcpp::is(newData)) addColumnFromVector((Rcpp::IntegerVector) newData); - else if(Rcpp::is(newData)) addColumnFromVector((Rcpp::StringVector) newData); - else if(Rcpp::is(newData)) addColumnFromVector((Rcpp::CharacterVector) newData); - - else - Rf_error("Cannot add this kind of data as a column to a jaspTable, it is not understood. Try a list, dataframe, vector or matrix instead."); - - notifyParentOfChanges(); -} - -void jaspTable::addRows(Rcpp::RObject newData, Rcpp::CharacterVector rowNames) -{ - if(newData.isNULL()) - return; - - //Maybe this is overkill? - if(Rcpp::is(newData)) addRowsFromDataFrame((Rcpp::DataFrame) newData); - else if(Rcpp::is(newData)) addRowsFromList((Rcpp::List) newData, rowNames); - - else if(Rcpp::is(newData)) addRowsFromMatrix((Rcpp::NumericMatrix) newData, rowNames); - else if(Rcpp::is(newData)) addRowsFromMatrix((Rcpp::LogicalMatrix) newData, rowNames); - else if(Rcpp::is(newData)) addRowsFromMatrix((Rcpp::IntegerMatrix) newData, rowNames); - else if(Rcpp::is(newData)) addRowsFromMatrix((Rcpp::StringMatrix) newData, rowNames); - else if(Rcpp::is(newData)) addRowsFromMatrix((Rcpp::CharacterMatrix) newData, rowNames); - - else - Rf_error("Cannot add this kind of data as rows to a jaspTable, it is not understood. Try a list, dataframe or matrix instead."); - - notifyParentOfChanges(); -} - -void jaspTable::addRow(Rcpp::RObject newData, Rcpp::CharacterVector rowName) -{ - if(newData.isNULL()) - return; - - if (Rcpp::is(newData)) addRowFromList((Rcpp::List) newData, rowName); - - else if (Rcpp::is(newData)) addRowFromVector((Rcpp::NumericVector) newData, rowName); - else if (Rcpp::is(newData)) addRowFromVector((Rcpp::LogicalVector) newData, rowName); - else if (Rcpp::is(newData)) addRowFromVector((Rcpp::IntegerVector) newData, rowName); - else if (Rcpp::is(newData)) addRowFromVector((Rcpp::StringVector) newData, rowName); - else if (Rcpp::is(newData)) addRowFromVector((Rcpp::CharacterVector) newData, rowName); - - else - Rf_error("Cannot add this kind of data as a row to a jaspTable, it is not understood. Try a list or vector instead."); - - notifyParentOfChanges(); -} - -void jaspTable::addRowFromList(Rcpp::List newData, Rcpp::CharacterVector newRowNames) -{ - Rcpp::List newRowList; - auto shield = new Rcpp::Shield(newRowList); - newRowList.push_back(newData); - addRowsFromList(newRowList, newRowNames); - delete shield; -} - -void jaspTable::addRowsFromList(Rcpp::List newData, Rcpp::CharacterVector newRowNames) -{ - int equalizedColumnsLength = equalizeColumnsLengths(), - previouslyAddedUnnamedCols = 0; - - std::vector localRowNames = extractElementOrColumnNames(newData); - - for(size_t row=0; row localColNames; - - if(Rcpp::is(rij)) - localColNames = extractElementOrColumnNames(Rcpp::as(rij)); - - auto jsonRij = RcppVector_to_VectorJson(rij); - - for(size_t col=0; col({jsonRij[col]}), localColNames.size() > col ? localColNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamedCols); - - equalizedColumnsLength = equalizeColumnsLengths(); - } -} - -void jaspTable::addColumnsFromList(Rcpp::List newData) -{ - size_t elementLenghts = 0; - for(int el=0; el 1) //each entry is 1 or 0, this must be a single row with columnnames and not a set of rows with rownames.. - { - Rcpp::List newColList; - auto shield = new Rcpp::Shield(newColList); - newColList.push_back(newData); - addColumnsFromList(newColList); - delete shield; - - return; - } - - std::vector localColNames = extractElementOrColumnNames(newData); - extractRowNames(newData, true); - - for(int col=0; col col ? localColNames[col] : ""); -} - -///Logically we must assume that each entry in the list is a single element vector -void jaspTable::setColumnFromList(Rcpp::List column, int colIndex) -{ - std::vector localRowNames = extractElementOrColumnNames(column); - setRowNamesWhereApplicable(localRowNames); - - if(_data.size() <= colIndex) - _data.resize(colIndex+1); - _data[colIndex].clear(); - - for(int row=0; row jsonVec = RcppVector_to_VectorJson((Rcpp::RObject)column[row], false); - _data[colIndex].push_back(jsonVec.size() > 0 ? jsonVec[0u] : Json::nullValue); - } -} - int jaspTable::equalizeColumnsLengths() { if(_data.size() == 0) @@ -470,126 +287,6 @@ void jaspTable::calculateMaxColRow(size_t & maxCol, size_t & maxRow) const maxCol = std::max(maxCol, _expectedColumnCount); } -Rcpp::List jaspTable::toRObject() -{ - Rcpp::DataFrame df; - - for (size_t col = 0; col < _data.size(); col++) - { - - jaspTableColumnType type = deriveColumnType(col); - - switch(type) - { - - // this could be a templated or overloaded function? - case jaspTableColumnType::integer: - { - Rcpp::IntegerVector values(_data[col].size()); - for (size_t row = 0; row < _data[col].size(); row++) - { - const Json::Value & cell = _data[col][row]; - if (cell.isNumeric()) - values[row] = cell.asInt(); - else - values[row] = NA_INTEGER; // placeholder/null -> NA - } - - df[getColName(col)] = values; - break; - } - case jaspTableColumnType::number: - { - Rcpp::NumericVector values(_data[col].size()); - for (size_t row = 0; row < _data[col].size(); row++) - { - const Json::Value & cell = _data[col][row]; - if (cell.isNumeric()) - values[row] = cell.asDouble(); - else - values[row] = NA_REAL; // placeholder/null -> NA - } - - df[getColName(col)] = values; - break; - } - case jaspTableColumnType::logical: - { - Rcpp::LogicalVector values(_data[col].size()); - for (size_t row = 0; row < _data[col].size(); row++) - values[row] = _data[col][row].asBool(); - - df[getColName(col)] = values; - - break; - } - case jaspTableColumnType::string: - case jaspTableColumnType::various: - case jaspTableColumnType::unknown: - case jaspTableColumnType::composite: - { - Rcpp::StringVector values(_data[col].size()); - for (size_t row = 0; row < _data[col].size(); row++) - values[row] = decodeColumnNames(_data[col][row].asString()); - - df[decodeColumnNames(getColName(col))] = values; - break; - } - case jaspTableColumnType::mixed: - { - - Rcpp::List valuesData(_data[col].size()); - Rcpp::StringVector valuesTypes(_data[col].size()); - Rcpp::List valuesFormats(_data[col].size()); - for (size_t row = 0; row < _data[col].size(); row++) - { - valuesTypes[row] = _data[col][row]["type"].asString(); - - if (valuesTypes[row] == "number") valuesData[row] = _data[col][row]["value"].asDouble(); - else if (valuesTypes[row] == "pvalue") valuesData[row] = _data[col][row]["value"].asDouble(); - else if (valuesTypes[row] == "integer") valuesData[row] = _data[col][row]["value"].asInt(); - else if (valuesTypes[row] == "string") valuesData[row] = decodeColumnNames(_data[col][row]["value"].asString()); - - if (!_data[col][row]["format"].isNull()) - valuesFormats[row] = _data[col][row]["format"].asString(); - } - - Rcpp::Environment jaspBase = Rcpp::Environment::namespace_env("jaspBase"); - Rcpp::Function createMixedColumn = jaspBase["createMixedColumn"]; - Rcpp::List values = createMixedColumn(valuesData, valuesTypes, valuesFormats); - df[decodeColumnNames(getColName(col))] = values; - break; - } - // this case is probably unnecessary - case jaspTableColumnType::null: - { - df[getColName(col)] = R_NilValue; - break; - } - - } - } - - df.attr("footnotes") = _footnotes.toRObject(); - df.attr("title") = decodeColumnNames(_title); - df.attr("class") = Rcpp::CharacterVector({"jaspTableWrapper", "jaspWrapper", "data.frame"}); - - std::vector rowNames; - const size_t rowCount = _data.empty() ? 0 : _data[0].size(); // empty table (e.g. no variables selected) has no rows - rowNames.reserve(rowCount); - for (size_t i = 0; i < rowCount; i++) - rowNames.push_back(_rowNames[i] != "" ? decodeColumnNames(_rowNames[i]) : std::to_string(i + 1)); // R numbers from 1 to n by default - - df.attr("row.names") = rowNames; - - // the reason this function is not const - Rcpp::Environment jaspObjectEnvironment = Rcpp::new_env(); - jaspObjectEnvironment.assign("jaspObject", Rcpp::as(Rcpp::wrap(jaspTable_Interface(this)))); - df.attr("jaspObjectEnvironment") = jaspObjectEnvironment; - - return df; -} - std::vector> jaspTable::dataToRectangularVector(bool normalizeColLengths, bool normalizeRowLengths) const { size_t maxRow, maxCol; @@ -1214,28 +911,6 @@ void footnotes::convertToJSONOrdered(std::map rowNames, std mergedList = jaspObject::VectorJson_to_ArrayJson(notesToOrderMerged); } -Rcpp::List footnotes::toRObject() const -{ - - // this is not very efficient - Rcpp::List notes; - - for (const auto & textRest : _data) - for(const auto & symbolRest : textRest.second) - for(const tableFields & fields : symbolRest.second) - { - Rcpp::List note = Rcpp::List::create( - Rcpp::Named("text") = textRest.first, - Rcpp::Named("symbol") = symbolRest.first -// TODO: I do not understand the data in here, or how to convert it to R... -// Rcpp::Named("rows") = fields.rowsToJSON(), -// Rcpp::Named("cols") = fields.colsToJSON() - ); - notes.push_back(note); - } - return notes; -} - void footnotes::convertFromJSON_SetFields(Json::Value footnotes) { if (footnotes.isArray()) @@ -1261,23 +936,9 @@ void footnotes::insert(std::string text, std::string symbol, std::vector colNames; - if (!col_names.isNULL()) - colNames = RcppVector_to_VectorJson(col_names, false); - - std::vector rowNames; - if (!row_names.isNULL()) - rowNames = RcppVector_to_VectorJson(row_names, false); - - _footnotes.insert(strMessage, strSymbol, colNames, rowNames); +void jaspTable::addFootnote(std::string message, std::string symbol, std::vector col_names, std::vector row_names) +{ + _footnotes.insert(message, symbol, col_names, row_names); } Json::Value jaspTable::dataEntry(std::string & errorMessage) const @@ -1511,20 +1172,23 @@ std::string jaspTable::getColType(size_t col) const } ///Going to assume it is called like addColumInfo(name=NULL, title=NULL, type=NULL, format=NULL, combine=NULL) -void jaspTable::addColumnInfo(Rcpp::RObject name, Rcpp::RObject title, Rcpp::RObject type, Rcpp::RObject format, Rcpp::RObject combine, Rcpp::RObject overtitle) +///Neutral version for non-R hosts: empty string means "unset" (R's NULL +///semantics preserved byte-identically by the R adapter, which keeps its own +///faithful NULL-based implementation in rcppTableIngest.cpp). +void jaspTable::addColumnInfo(std::string name, std::string title, std::string type, std::string format, bool hasCombine, bool combine, std::string overtitle) { - std::string colName = name.isNULL() ? defaultColName(_colNames.rowCount()) : Rcpp::as(name); + std::string colName = name == "" ? defaultColName(_colNames.rowCount()) : name; _specifiedColumns.insert(colName); _colNames.add(colName); std::string lastAddedColName = getColName(_colNames.rowCount() - 1); - if(!title.isNULL()) _colTitles[ lastAddedColName ] = Rcpp::String(title); - if(!type.isNULL()) _colTypes[ lastAddedColName ] = Rcpp::String(type); - if(!format.isNULL()) _colFormats[ lastAddedColName ] = Rcpp::String(format); - if(!overtitle.isNULL()) _colOvertitles[ lastAddedColName ] = Rcpp::String(overtitle); - if(!combine.isNULL()) _colCombines[ lastAddedColName ] = Rcpp::as(combine); + if(title != "") _colTitles[ lastAddedColName ] = title; + if(type != "") _colTypes[ lastAddedColName ] = type; + if(format != "") _colFormats[ lastAddedColName ] = format; + if(overtitle != "") _colOvertitles[ lastAddedColName ] = overtitle; + if(hasCombine) _colCombines[ lastAddedColName ] = combine; } diff --git a/src/core/jaspTable.h b/src/core/jaspTable.h new file mode 100644 index 00000000..69918070 --- /dev/null +++ b/src/core/jaspTable.h @@ -0,0 +1,217 @@ +#pragma once + +// CORE (R-free) version of jaspTable.h. SEXP/ingest dispatch, name extraction +// from R attributes, toRObject() and the *_Interface live under +// src/adapters/rcpp/ (rcppTableIngest + rcppInterfaces). Storage is +// std::vector> exactly as in the original, so the +// JSON/formatting/footnotes machinery is unchanged. + +#include "jaspObject.h" +#include "jaspList.h" +#include + +struct jaspColRowCombination +{ + jaspColRowCombination(std::string name, std::string title, bool overwrite, bool removeSeparator, Json::Value colNames, Json::Value rowNames, Json::Value colOvertitles, Json::Value rowOvertitles) + : name(name), title(title), overwrite(overwrite), removeSeparator(removeSeparator), colNames(colNames), rowNames(rowNames), colOvertitles(colOvertitles), rowOvertitles(rowOvertitles) {} + + jaspColRowCombination(Json::Value convertFromThis) { throw std::runtime_error("Not implemented");} + + std::string name, title; + bool overwrite, removeSeparator; + Json::Value colNames, rowNames, colOvertitles, rowOvertitles; + + std::string toString(); + + Json::Value convertToJSON() const { throw std::runtime_error("Not implemented"); } + +}; + +namespace footnotesNamespace +{ + +struct tableFields +{ + + tableFields(std::set rows, std::set cols) : _rows(rows), _cols(cols) {} + + Json::Value rowsToJSON() const; + Json::Value colsToJSON() const; + + struct hasher //Special hash func obj to differentiate between different sets of tableFields + { + std::size_t operator()(tableFields const & tf) const noexcept + { + return std::hash{}(tf.getCompareString()); + } + }; + + struct comparer + { + bool operator()(const tableFields & lhs, const tableFields & rhs) const + { + return lhs.getCompareString() < rhs.getCompareString(); //Don't really care about the results logic + } + + }; + + std::string getCompareString() const { return rowsToJSON().toStyledString() + "<$>" + colsToJSON().toStyledString(); } + +private: + std::set _rows, + _cols; +}; + +inline bool operator==(const tableFields & lhs, const tableFields & rhs) +{ + return lhs.getCompareString() == rhs.getCompareString(); +} + +struct footnotes +{ + void insert(std::string text, std::string symbol, std::vector colNames, std::vector rowNames); + void convertFromJSON_SetFields(Json::Value footnotes); + Json::Value convertToJSON() const; + void convertToJSONOrdered(std::map rowNames, std::map colNames, Json::Value & fullList, Json::Value & mergedList) const; + + std::map >> _data; //text -> symbol -> rows+cols (public so the R adapter can build toRObject) +}; + +} + +using footnotesNamespace::footnotes; + +///Neutral ingest payload: column-major cells plus optional column/row names. +///Host adapters (R SEXP, Python dict/DataFrame) build this and call +///setDataColumns(). +struct jaspTableData +{ + std::vector> columns; //First columns, then rows, like _data + std::vector colNames, //"" allowed, becomes col + rowNames; //"" allowed, becomes row +}; + +class jaspTable : public jaspObject +{ +public: + jaspTable(std::string title = "") : jaspObject(jaspObjectType::table, title), _colNames("colNames"), _colTypes("colTypes"), _colTitles("colTitles"), _colOvertitles("colOvertitles"), _colFormats("colFormats"), _rowNames("rowNames"), _rowTitles("rowTitles") {} + + void setColNames( std::vector newNames, const std::map & fields = {}) { _colNames.setRows(newNames, fields); } + jaspStringlist _colNames; + + void setColTypes( std::vector newTypes, const std::map & fields = {}) { _colTypes.setRows(newTypes, fields); } + jaspStringlist _colTypes; + + void setColTitles( std::vector newTitles, const std::map & fields = {}) { _colTitles.setRows(newTitles, fields); } + jaspStringlist _colTitles; + + void setColOvertitles( std::vector newTitles, const std::map & fields = {}) { _colOvertitles.setRows(newTitles, fields); } + jaspStringlist _colOvertitles; + + void setColFormats( std::vector newFormats, const std::map & fields = {}) { _colFormats.setRows(newFormats, fields); } + jaspStringlist _colFormats; + + void setColCombines( std::vector newCombines, const std::map & fields = {}) { _colCombines.setRows(newCombines, fields); } + jaspBoollist _colCombines; + + void setRowNames( std::vector newNames, const std::map & fields = {}) { _rowNames.setRows(newNames, fields); } + jaspStringlist _rowNames; + + void setRowTitles( std::vector newTitles, const std::map & fields = {}) { _rowTitles.setRows(newTitles, fields); } + jaspStringlist _rowTitles; + + ///Neutral ingest: clear existing data and load columns (+ any names) from + ///jaspTableData. R/Python adapters convert their native data first. + void setDataColumns(const jaspTableData & newData); + + void addFootnote(std::string message, std::string symbol, std::vector col_names, std::vector row_names); + + ///neutral addColumnInfo: name="" means "use default colN"; title/type/format/overtitle "" mean "leave unset"; hasCombine gates the combine bool. + void addColumnInfo(std::string name, std::string title, std::string type, std::string format, bool hasCombine, bool combine, std::string overtitle); + + std::string dataToString(std::string prefix) const override; + + void complete() override { if(_status == "running") _status = "complete"; } + void letRun() override { _status = "running"; } + + bool canShowErrorMessage() const override { return true; } + + Json::Value metaEntry() const override { return constructMetaEntry("table"); } + Json::Value dataEntry(std::string & errorMessage) const override; + std::string toHtml() const override; + + std::string defaultColName(size_t col) const { return "col"+ std::to_string(col); } + std::string defaultRowName(size_t row) const { return "row"+ std::to_string(row); } + std::string getRowName(size_t row) const { return _rowNames[row] == "" ? defaultRowName(row) : _rowNames[row]; } + std::string getColName(size_t col) const { return _colNames[col] == "" ? defaultColName(col) : _colNames[col]; } + std::string getColType(size_t col) const; + + bool isSpecialColumn(size_t col) const; + bool columnSpecified(size_t col) const { return _specifiedColumns.count(getColName(col)) > 0; } + bool columnSpecified(std::string col) const { return _specifiedColumns.count(col) > 0; } + + Json::Value getCell( size_t col, size_t row, size_t maxCol, size_t maxRow) const; + std::string getCellFormatted( size_t col, size_t row, size_t maxCol, size_t maxRow) const; + + void calculateMaxColRow(size_t & maxCol, size_t & maxRow) const; + + void setExpectedSize(size_t columns, size_t rows) { setExpectedRows(rows); setExpectedColumns(columns); } + void setExpectedRows(size_t rows) { _expectedRowCount = rows; } + void setExpectedColumns(size_t columns) { _expectedColumnCount = columns; } + +protected: + std::vector getDisplayableColTitles(bool normalizeLengths = true, bool onlySpecifiedColumns = true) const; + std::vector getDisplayableRowTitles(bool normalizeLengths = true) const; + void rectangularDataWithNamesToString( std::stringstream & out, std::string prefix, std::vector> vierkant, std::vector sideNames, std::vector topNames, std::map sideOvertitles, std::map topOvertitles) const; + void rectangularDataWithNamesToHtml( std::stringstream & out, std::vector> vierkant, std::vector sideNames, std::vector topNames, std::map sideOvertitles, std::map topOvertitles) const; + + + std::map getOvertitlesMap() const; + std::vector> dataToRectangularVector(bool normalizeColLengths = false, bool normalizeRowLengths = false) const; + static std::vector> transposeRectangularVector(const std::vector> & in); + std::map> getOvertitleRanges(std::vector names, std::map overtitles) const; + + int getDesiredColumnIndexFromNameForRowAdding(std::string colName, int previouslyAddedUnnamed); + + Json::Value schemaJson(Json::Value tmpFootnotesFull) const; + Json::Value rowsJson(Json::Value tmpFootnotesFull) const; + +public: + jaspTableColumnType deriveColumnType(int col) const; + +protected: + + std::map mapColNamesToIndices() const; + std::map mapRowNamesToIndices() const; + + Json::Value convertToJSON() const override; + void convertFromJSON_SetFields(Json::Value in) override; + + bool isMixedJson(const Json::Value &v) const { return v.isObject() && !v.get("value", Json::nullValue).isNull() && !v.get("type", Json::nullValue).isNull() && v.isMember("format"); } + +public: + // cell-storage primitives, public so host adapters (R SEXP dispatch, + // Python conversions) can build tables on top of them: + int getDesiredColumnIndexFromNameForColumnAdding(std::string colName); + void addOrSetColumnInData(std::vector column, std::string colName=""); + int pushbackToColumnInData(std::vector column, std::string colName, int equalizedColumnsLength, int previouslyAddedUnnamed); + void setColumnCellsAt(std::vector column, size_t col); + void setRowNamesWhereApplicable(std::vector rowNamesList); + int equalizeColumnsLengths(); + + bool _transposeTable = false, + _transposeWithOvertitle = false, + _showSpecifiedColumnsOnly = false; + std::string _status = "running"; + + std::set _specifiedColumns; + + //public so host adapters (R toRObject/mixed-columns, Python) can read/write the raw cells + footnotes _footnotes; + std::vector> _data; //First columns, then rows. + +private: + std::vector _colRowCombinations; + size_t _expectedColumnCount = 0, + _expectedRowCount = 0; +}; diff --git a/src/jaspHtml.h b/src/jaspHtml.h deleted file mode 100644 index 19f0fc9d..00000000 --- a/src/jaspHtml.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once -#include "jaspObject.h" - -class jaspHtml : public jaspObject -{ -public: - jaspHtml(Rcpp::String text = "", std::string elementType = "p", std::string maxWidth="15cm", std::string Class = "") : jaspObject(jaspObjectType::html, ""), _rawText(text), _elementType(elementType), _class(Class), _maxWidth(maxWidth) {} - - ~jaspHtml() {} - - std::string dataToString(std::string prefix="") const override; - std::string toHtml() const override; - - Json::Value metaEntry() const override { return constructMetaEntry("htmlNode"); } - Json::Value dataEntry(std::string & errorMessage) const override; - - std::string _rawText, _elementType, _class, _maxWidth; - - Json::Value convertToJSON() const override; - void convertFromJSON_SetFields(Json::Value in) override; - - std::string convertTextToHtml( const std::string text) const; - static std::string sanitizeTextForHtml(const std::string text); - - void setText(std::string newRawText); - std::string getText(); - std::string getHtml(); - - Rcpp::List toRObject() /*const*/ override; - -}; - - - -class jaspHtml_Interface : public jaspObject_Interface -{ -public: - jaspHtml_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - void setText(Rcpp::String newRawText) { static_cast(myJaspObject)->setText(newRawText); } - Rcpp::String getText() { return static_cast(myJaspObject)->getText(); } - std::string getHtml() { return static_cast(myJaspObject)->getHtml(); } - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspHtml, std::string, _elementType, ElementType) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspHtml, std::string, _class, Class) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspHtml, std::string, _maxWidth, MaxWidth) - -}; - -RCPP_EXPOSED_CLASS_NODECL(jaspHtml_Interface) - diff --git a/src/jaspObject.h b/src/jaspObject.h deleted file mode 100644 index e5097b83..00000000 --- a/src/jaspObject.h +++ /dev/null @@ -1,403 +0,0 @@ -#ifndef JASPOBJECT_MANUAL_INCLUDE_GUARD -#define JASPOBJECT_MANUAL_INCLUDE_GUARD -#include -#include -#include -#include -#include "jaspEnums.h" -#include - -typedef void (*logFuncDef)(const std::string &); - -void setJaspLogFunction( Rcpp::XPtr func ); -void jaspPrint( std::string msg); -std::string decodeColumnNames(const std::string & str); - - -#define JASPOBJECT_DEFAULT_POSITION 9999 -//#define JASP_RESULTS_DEBUG_TRACES - -class jaspContainer; - -std::string stringExtend(std::string & str, size_t len, char kar = ' '); -std::string stringRemove(std::string str, char kar = ' '); -std::vector stringSplit(std::string str, char kar = ';'); - -//Simple base-class for all JASP-objects, containing things like a title or a warning and stuff like that -class jaspObject -{ -public: - jaspObject() : _title(""), _type(jaspObjectType::unknown) { allocatedObjects->insert(this); } - jaspObject(Rcpp::String title) : _title(title), _type(jaspObjectType::unknown) { allocatedObjects->insert(this); } - jaspObject(jaspObjectType type, Rcpp::String title) : _title(title), _type(type) { allocatedObjects->insert(this); } - jaspObject(const jaspObject& that) = delete; - virtual ~jaspObject(); - - std::string objectTitleString(std::string prefix="") const { return prefix + jaspObjectTypeToString(_type) + " " + _title; } - virtual std::string dataToString(std::string) const { return ""; } - std::string toString(std::string prefix = "") const; - - virtual std::string toHtml() const { return ""; } - std::string htmlTitle() const { return "

" + _title + "

"; } - - std::string type() { return jaspObjectTypeToString(_type); } - - bool getError() { return _error; } - virtual void setError() { _error = true; } - virtual void setError(Rcpp::String message) { _errorMessage = message; _error = true; } - virtual bool canShowErrorMessage() const { return false; } - - void print() { try { jaspPrint(toString()); } catch(std::exception e) { jaspPrint(std::string("toString failed because of: ") + e.what()); } } - void addMessage(std::string msg) { _messages.push_back(msg); } - virtual void childrenUpdatedCallbackHandler(bool) {} ///Can be called by jaspResults to send changes and stuff like that. - - void setOptionMustBeDependency(std::string optionName, Rcpp::RObject mustBeThis); - void setOptionMustContainDependency(std::string optionName, Rcpp::RObject mustContainThis); - void dependOnNestedOptions(Rcpp::CharacterVector nestedOptionName); - void setNestedOptionMustContainDependency(Rcpp::CharacterVector nestedOptionName, Rcpp::RObject mustContainThis); - void dependOnOptions(Rcpp::CharacterVector listOptions); - void copyDependenciesFromJaspObject(jaspObject * other); - - bool checkDependencies(Json::Value currentOptions); //returns false if no longer valid and destroys children (if applicable) that are no longer valid - virtual void checkDependenciesChildren(Json::Value currentOptions) {} - - void addCitation(std::string fullCitation); - - std::string _title, - _info; - int _position = JASPOBJECT_DEFAULT_POSITION; - - jaspObjectType getType() const { return _type; } - virtual bool shouldBePartOfResultsJson(bool meta = false) const { return _type != jaspObjectType::state; } - - Json::Value constructMetaEntry(std::string type, std::string meta = "") const; - - //These functions convert the object to a json that can be understood by the resultsviewer - virtual Json::Value metaEntry() const { return Json::Value(Json::nullValue); } - virtual Json::Value dataEntry(std::string & errorMessage) const ; - - //These two are meant for jaspContainer and take old results into account and a possible errorMessage - virtual Json::Value metaEntry(jaspObject * oldResult) const { return metaEntry(); } - virtual Json::Value dataEntry(jaspObject * oldResult, std::string & errorMessage) const { return dataEntry(errorMessage); } - - Json::Value dataEntryBase() const; - - //These functions convert to object and all to a storable json-representation that can be written to disk and loaded again. - virtual Json::Value convertToJSON() const; - static jaspObject * convertFromJSON(Json::Value in); - virtual void convertFromJSON_SetFields(Json::Value in); - - ///Gives nested name to avoid namingclashes - std::string getUniqueNestedName() const; - void getUniqueNestedNameVector(std::vector & names) const; - void setName(std::string name) { _name = name; } - - void childrenUpdatedCallback(bool ignoreSendTimer); - virtual void childFinalizedHandler(jaspObject * child) {} - void childFinalized(jaspObject * child); - void finalized(); - virtual void finalizedHandler() {} - - virtual Rcpp::List toRObject() /*const*/ { return R_NilValue; }; - - template static std::vector extractElementOrColumnNames(RCPP_CLASS rObj) - { - Rcpp::RObject colNamesRObject = Rcpp::colnames(rObj), kolnamesRObject = rObj.names(); - Rcpp::CharacterVector colNamesList; - std::vector colNamesVec; - - if(!colNamesRObject.isNULL() || !kolnamesRObject.isNULL()) - { - colNamesList = !colNamesRObject.isNULL() ? colNamesRObject : kolnamesRObject; - - for(size_t col=0; col(colNamesList[col])); - } - - return colNamesVec; - } - - static void destroyAllAllocatedObjects(); - - std::set & getChildren() { return children; } - - Rcpp::DataFrame convertFactorsToCharacters(Rcpp::DataFrame df); - - static Json::Value currentOptions; - - void notifyParentOfChanges(); ///let ancestors know about updates - - static int getCurrentTimeMs(); - static void setDeveloperMode(bool developerMode); - - bool connectedToJaspResults(); - - virtual jaspObject * getOldObjectFromUniqueNestedNameVector(const std::vector &uniqueName); - - std::vector RList_to_VectorJson(Rcpp::List obj); - - std::vector RcppVector_to_VectorJson(Rcpp::RObject obj, bool throwError=false) - { - if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::NumericVector) obj); - else if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::LogicalVector) obj); - else if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::IntegerVector) obj); - else if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::StringVector) obj); - else if(Rcpp::is(obj)) return RcppVector_to_VectorJson((Rcpp::CharacterVector) obj); - else if(isMixedRObject(obj)) return MixedRcppVector_to_VectorJson( (Rcpp::List) obj); - else if(Rcpp::is(obj)) return RList_to_VectorJson((Rcpp::List) obj); - else if(throwError) Rf_error("JASPjson::RcppVector_to_VectorJson received an SEXP that is not a Vector of some kind."); - - return std::vector({""}); - } - - std::vector MixedRcppVector_to_VectorJson(Rcpp::List obj) - { - std::vector vec; - for(int i=0; i std::vector RcppVector_to_VectorJson(Rcpp::Vector obj) - { - std::vector vec; - - for(int row=0; row inline Json::Value RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row) { return ""; } - - template inline Json::Value RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row) { return ""; } - - template std::vector> RcppMatrix_to_Vector2Json(Rcpp::Matrix obj) - { - std::vector> vecvec; - - for(int col=0; col vec; - - for(int row=0; row Json::Value RObject_to_JsonValue(Rcpp::Matrix obj) - { - Json::Value val(Json::arrayValue); - - for(int col=0; col Json::Value RObject_to_JsonValue(Rcpp::Vector obj) - { - Json::Value val(""); - - if(obj.size() == 1) - val = RVectorEntry_to_JsonValue(obj, 0); - else if(obj.size() > 1) - { - val = Json::Value(Json::arrayValue); - - for(int row=0; row set); - static std::set ArrayJson_to_SetJson(Json::Value arr); - static Json::Value VectorJson_to_ArrayJson(std::vector vec); - - -protected: - jaspObjectType _type; - std::string _errorMessage = ""; - bool _error = false, - _escapeHtml = true; // Used to escape Html characters when converting R object to Json. This is true per default, because the results of these objects are usually send to a Web Browser. - - std::vector _messages; - std::set _citations; - std::string _name; - - std::set nestedMustBes() const; - std::map> nestedMustContains() const; - std::map _optionMustContain; - std::map _optionMustBe; - std::map, Json::Value> _nestedOptionMustContain; - std::map, Json::Value> _nestedOptionMustBe; - - -//Should add dependencies somehow here? - -//Some basic administration of objecttree: - bool hasAncestor(jaspObject * ancestor) { return parent == ancestor || parent == NULL ? false : parent->hasAncestor(ancestor); } - void addChild(jaspObject * child); - - void removeChild(jaspObject * child); - - - jaspObject *parent = NULL; - std::set children; - - static std::set * allocatedObjects; - static bool _developerMode; - -private: - - Json::Value getObjectFromNestedOption(std::vector nestedKey, Json::Value ifNotFound = Json::nullValue) const; - std::string nestedKeyToString(const std::vector & nestedKey, const std::string & sep = "$!_SEP_!$") const; - std::vector stringToNestedKey(const std::string & nestedKey, const std::string & sep = "$!_SEP_!$") const; - bool isJsonSubArray(const Json::Value needle, const Json::Value haystack) const; - - bool _finalizedAlready = false; -}; - - - -#define TO_INFINITY_AND_BEYOND \ -{ \ - double val = static_cast(obj[row]); \ - return R_IsNA(val) ? "" : \ - R_IsNaN(val) ? "NaN" : \ - val == std::numeric_limits::infinity() ? "\u221E" : \ - val == -1 * std::numeric_limits::infinity() ? "-\u221E" : \ - Json::Value((double)(obj[row])); \ -} - -template<> inline Json::Value jaspObject::RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row) -{ - return obj[row] == NA_INTEGER ? "" : Json::Value((int)(obj[row])); -} - -template<> inline Json::Value jaspObject::RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row) -{ - return obj[row] == NA_LOGICAL ? "" : Json::Value((bool)(obj[row])); -} - -template<> inline Json::Value jaspObject::RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row) -{ - return obj[row] == NA_STRING ? "" : Json::Value(_escapeHtml ? stringUtils::escapeHtmlStuff(std::string(obj[row])) : std::string(obj[row])); -} - -template<> inline Json::Value jaspObject::RVectorEntry_to_JsonValue(Rcpp::Vector obj, int row) TO_INFINITY_AND_BEYOND - -template<> inline Json::Value jaspObject::RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row) { return obj[row] == NA_INTEGER ? "" : Json::Value((int)(obj[row])); } - -template<> inline Json::Value jaspObject::RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row) { return obj[row] == NA_LOGICAL ? "" : Json::Value((bool)(obj[row])); } - -template<> inline Json::Value jaspObject::RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row) { return obj[row] == NA_STRING ? "" : Json::Value(_escapeHtml ? stringUtils::escapeHtmlStuff(std::string(obj[row])) : std::string(obj[row])); } - -template<> inline Json::Value jaspObject::RMatrixColumnEntry_to_JsonValue(Rcpp::MatrixColumn obj, int row) TO_INFINITY_AND_BEYOND - - -#define JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(JASP_TYPE, PROP_TYPE, PROP_NAME, PROP_CAPITALIZED_NAME) \ - void set ## PROP_CAPITALIZED_NAME (PROP_TYPE new ## PROP_CAPITALIZED_NAME) { ((JASP_TYPE *)myJaspObject)->PROP_NAME = new ## PROP_CAPITALIZED_NAME; myJaspObject->notifyParentOfChanges(); } \ - PROP_TYPE get ## PROP_CAPITALIZED_NAME () { return ((JASP_TYPE *)myJaspObject)->PROP_NAME; } - -#define JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NO_NOTIFY(JASP_TYPE, PROP_TYPE, PROP_NAME, PROP_CAPITALIZED_NAME) \ - void set ## PROP_CAPITALIZED_NAME (PROP_TYPE new ## PROP_CAPITALIZED_NAME) { ((JASP_TYPE *)myJaspObject)->PROP_NAME = new ## PROP_CAPITALIZED_NAME; } \ - PROP_TYPE get ## PROP_CAPITALIZED_NAME () { return ((JASP_TYPE *)myJaspObject)->PROP_NAME; } - -#define JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NATIVE_STRING(JASP_TYPE, PROP_NAME, PROP_CAPITALIZED_NAME) \ -void set ## PROP_CAPITALIZED_NAME (Rcpp::String new ## PROP_CAPITALIZED_NAME) { ((JASP_TYPE *)myJaspObject)->PROP_NAME = new ## PROP_CAPITALIZED_NAME; myJaspObject->notifyParentOfChanges(); } \ -Rcpp::String get ## PROP_CAPITALIZED_NAME () { return ((JASP_TYPE *)myJaspObject)->PROP_NAME; } - - -class jaspObject_Interface -{ -public: - jaspObject_Interface(jaspObject * dataObj) : myJaspObject(dataObj) - { -#ifdef JASP_RESULTS_DEBUG_TRACES - std::cout << "Interface to " << dataObj->objectTitleString() << " is created!\n"<myJaspObject->objectTitleString() << " is copied!\n"<myJaspObject; - } - - void print() { myJaspObject->print(); } - void addMessage(Rcpp::String msg) { myJaspObject->addMessage(msg); } - std::string toHtml() { return myJaspObject->toHtml(); } - std::string type() { return myJaspObject->type(); } - void printHtml() { jaspPrint(myJaspObject->toHtml()); } - - void setOptionMustBeDependency(std::string optionName, Rcpp::RObject mustBeThis) { myJaspObject->setOptionMustBeDependency(optionName, mustBeThis); } - void setOptionMustContainDependency(std::string optionName, Rcpp::RObject mustContainThis) { myJaspObject->setOptionMustContainDependency(optionName, mustContainThis); } - void dependOnNestedOptions(Rcpp::CharacterVector optionName) { myJaspObject->dependOnNestedOptions(optionName); } - void setNestedOptionMustContainDependency(Rcpp::CharacterVector optionName, Rcpp::RObject mustContainThis) { myJaspObject->setNestedOptionMustContainDependency(optionName, mustContainThis); } - void dependOnOptions(Rcpp::CharacterVector listOptions) { myJaspObject->dependOnOptions(listOptions); } - void copyDependenciesFromJaspObject(jaspObject_Interface * other) { myJaspObject->copyDependenciesFromJaspObject(other->myJaspObject); } - void addCitation(Rcpp::String fullCitation) { myJaspObject->addCitation(fullCitation); } - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NATIVE_STRING(jaspObject, _title, Title) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NATIVE_STRING(jaspObject, _info, Info) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspObject, int, _position, Position) - - void setError(Rcpp::String message) { myJaspObject->setError(message); } - bool getError() { return myJaspObject->getError(); } - - Rcpp::List toRObject() { return myJaspObject->toRObject(); } - - jaspObject * returnMyJaspObject() { return myJaspObject; } - -protected: - jaspObject * myJaspObject = NULL; -}; - - -void jaspObjectFinalizer(jaspObject * obj); -#define JASP_OBJECT_FINALIZER_LAMBDA(JASP_TYPE) //.finalizer( [](JASP_TYPE * obj) { std::cout << "finalizerLambda " #JASP_TYPE " Called\n" << std::flush; jaspObjectFinalizer(obj); }) - -#define JASP_OBJECT_CREATOR_FUNCTIONNAME(JASP_TYPE) create_ ## JASP_TYPE -#define JASP_OBJECT_CREATOR_FUNCTIONNAME_STR(JASP_TYPE) "create_cpp_" #JASP_TYPE -#define JASP_OBJECT_CREATOR(JASP_TYPE) JASP_TYPE ## _Interface * JASP_OBJECT_CREATOR_FUNCTIONNAME(JASP_TYPE)(Rcpp::String title) { return new JASP_TYPE ## _Interface (new JASP_TYPE(title)); } -#define JASP_OBJECT_CREATOR_FUNCTIONREGISTRATION(JASP_TYPE) Rcpp::function(JASP_OBJECT_CREATOR_FUNCTIONNAME_STR(JASP_TYPE), &JASP_OBJECT_CREATOR_FUNCTIONNAME(JASP_TYPE)) -#define JASP_OBJECT_CREATOR_ARG(JASP_TYPE, EXTRA_ARG) JASP_TYPE ## _Interface * JASP_OBJECT_CREATOR_FUNCTIONNAME(JASP_TYPE)(Rcpp::String title, Rcpp::RObject EXTRA_ARG) { return new JASP_TYPE ## _Interface (new JASP_TYPE(title, EXTRA_ARG)); } - - -RCPP_EXPOSED_CLASS_NODECL(jaspObject_Interface) - -//#define JASP_R_INTERFACE_TIMERS - -#ifdef JASP_R_INTERFACE_TIMERS -#define JASP_OBJECT_TIMERBEGIN static int cumulativeTime = 0; int startSerialize = getCurrentTimeMs(); -#define JASP_OBJECT_TIMEREND(ACTIVITY) cumulativeTime += getCurrentTimeMs() - startSerialize; std::cout << jaspObjectTypeToString(getType()) << " spent " << cumulativeTime << "ms " #ACTIVITY "!" << std::endl; -#else -#define JASP_OBJECT_TIMERBEGIN /* Doin' nothing */ -#define JASP_OBJECT_TIMEREND(ACTIVITY) /* What you didn't start you need not stop */ -#endif - -#endif diff --git a/src/jaspPlot.cpp b/src/jaspPlot.cpp deleted file mode 100644 index 7f5d45d1..00000000 --- a/src/jaspPlot.cpp +++ /dev/null @@ -1,363 +0,0 @@ -#include "jaspPlot.h" -#include "jaspResults.h" - - -jaspPlot::~jaspPlot() -{ -#ifdef JASP_RESULTS_DEBUG_TRACES - jaspPrint("Destructor of JASPplot("+_title+") is called! "); -#endif - - finalizedHandler(); -} - -std::string jaspPlot::dataToString(std::string prefix) const -{ - std::stringstream out; - - out << - prefix << "aspectRatio: " << _aspectRatio << "\n" << - prefix << "dims: " << _width << "X" << _height << "\n" << - prefix << "error: '" << _error << "': '" << _errorMessage << "'\n" << - prefix << "filePath: " << _filePathPng << "\n" << - prefix << "status: " << _status << "\n" ;//<< - //prefix << "has plot: " << (_plotObjSerialized.size() > 0 ? "yes" : "no") << "\n"; - - return out.str(); -} - -Json::Value jaspPlot::dataEntry(std::string & errorMessage) const -{ - Json::Value data(jaspObject::dataEntry(errorMessage)); - - data["title"] = _title; - data["convertible"] = true; - data["data"] = _filePathPng; - data["height"] = _height; - data["width"] = _width; - data["aspectRatio"] = _aspectRatio; - data["status"] = _error ? "error" : _status; - data["revision"] = _revision; - data["name"] = getUniqueNestedName(); - data["editOptions"] = _editOptions; - data["reasonNotEditable"] = _editOptions.get("reasonNotEditable", "unknown reason"); - data["errorType"] = _editOptions.get("errorType", "fatalError"); - data["editable"] = !_editOptions.isNull() && data["errorType"] == "success"; - - data["interactive"] = _interactive; - data["interactiveConvertError"] = _interactiveConvertError; - data["interactiveJsonData"] = _interactiveJsonData; - - data["export"] = _export; - - return data; -} - -void jaspPlot::initEnvName() -{ - static int counter = 0; - - _envName = "plot_" + std::to_string(counter++); -} - -void jaspPlot::setPlotObject(Rcpp::RObject obj) -{ - Rcpp::List plotInfo = Rcpp::List::create(Rcpp::_["obj"] = obj, Rcpp::_["width"] = _width, Rcpp::_["height"] = _height, Rcpp::_["revision"] = _revision); - - if (!_editing) - _filePathPng = ""; - - jaspResults::setObjectInEnv(_envName, plotInfo); - - if (connectedToJaspResults()) - renderPlot(); - -} - -void jaspPlot::renderPlot() -{ - // if a png exists the plot was already rendered, unless we're editing it - if (_filePathPng != "" && !_editing) - return; - - // empty plots were added to the state - Rcpp::RObject plotInfoObj = jaspResults::getObjectFromEnv(_envName); - if (plotInfoObj.isNULL()) - return; - - Rcpp::List plotInfo = Rcpp::as(plotInfoObj); - Rcpp::RObject obj = plotInfo["obj"]; - - if(!obj.isNULL()) - { - - jaspPrint("Now rendering a plot with name: " + _name); - - static Rcpp::Function tryToWriteImage = Rcpp::Environment::namespace_env("jaspBase")["tryToWriteImageJaspResults"]; - Rcpp::List writeResult, oldPlotInfo; - if (_editing) - { - oldPlotInfo = Rcpp::List(); - _revision++; - writeResult = tryToWriteImage(Rcpp::_["width"] = _width, Rcpp::_["height"] = _height, Rcpp::_["plot"] = obj, Rcpp::_["oldPlotInfo"] = oldPlotInfo, Rcpp::_["relativePathpng"] = _filePathPng, Rcpp::_["relativePathJson"] = Rcpp::String(_interactiveJsonData)); - } - else - { - //getOldPlotInfo may update height & width - oldPlotInfo = getOldPlotInfo(plotInfo); - writeResult = tryToWriteImage(Rcpp::_["width"] = _width, Rcpp::_["height"] = _height, Rcpp::_["plot"] = obj, Rcpp::_["oldPlotInfo"] = oldPlotInfo, Rcpp::_["relativePathpng"] = R_NilValue); - } - - // we need to overwrite plot functions with their recordedplot result - if(Rcpp::is(obj) && writeResult.containsElementNamed("obj")) - plotInfo["obj"] = writeResult["obj"]; - - if(writeResult.containsElementNamed("png")) - _filePathPng = Rcpp::as(writeResult["png"]); - - _editOptions = Json::nullValue; - - if(writeResult.containsElementNamed("editOptions") && !Rf_isNull(writeResult["editOptions"])) - { - std::string editOptionsStr = Rcpp::as(writeResult["editOptions"]); - - if(editOptionsStr != "") - { - _editOptions = Json::objectValue; - Json::Reader().parse(editOptionsStr, _editOptions); - - // JSONCPP_STRING err; - // Json::CharReaderBuilder jsonReaderBuilder; - // std::unique_ptr const jsonReader(jsonReaderBuilder.newCharReader()); - - // jsonReader->parse(editOptionsStr.c_str(), editOptionsStr.c_str() + editOptionsStr.length(), &_editOptions, &err); - - - } - } - - if(writeResult.containsElementNamed("interactive")) - { - _interactive = Rcpp::as(writeResult["interactive"]); - if (_interactive) - { - if(writeResult.containsElementNamed("interactiveConvertError")) - { - _interactiveConvertError = Rcpp::as(writeResult["interactiveConvertError"]); - _interactiveJsonData = ""; - } - else if (writeResult.containsElementNamed("interactiveJsonData")) - { - std::string interactiveJsonDataStr = Rcpp::as(writeResult["interactiveJsonData"]); - _interactiveJsonData = interactiveJsonDataStr; - //Json::Reader().parse(interactiveJsonDataStr, _interactiveJsonData); - _interactiveConvertError = ""; - } - else - _interactiveConvertError = "Unknown error converting interactive plot to JSON"; - } - } - - - if(writeResult.containsElementNamed("error")) - { - _error = true; - _errorMessage = Rcpp::as(writeResult["error"]); - } - else - { - _error = false; - _errorMessage.clear(); - } - - complete(); - - jaspResults::setObjectInEnv(_envName, plotInfo); - } -} - -Rcpp::RObject jaspPlot::getPlotObject() const -{ - Rcpp::RObject plotInfo = jaspResults::getObjectFromEnv(_envName); - if (!plotInfo.isNULL() && Rcpp::is(plotInfo)) - { - - Rcpp::List plotInfoList = Rcpp::as(plotInfo); - if (plotInfoList.containsElementNamed("obj")) - return Rcpp::as(plotInfoList["obj"]); - - } - return R_NilValue; -} - -void jaspPlot::setUserPlotChangesFromRStateObject() -{ - Rcpp::RObject plotInfo = jaspResults::getObjectFromEnv(_envName); - if (plotInfo.isNULL() || !Rcpp::is(plotInfo)) - return; - - Rcpp::List plotInfoList = Rcpp::as(plotInfo); - - if (plotInfoList.containsElementNamed("width")) - _width = Rcpp::as(plotInfoList["width"]); - - if (plotInfoList.containsElementNamed("height")) - _height = Rcpp::as(plotInfoList["height"]); - - if (plotInfoList.containsElementNamed("revision")) - _revision = Rcpp::as(plotInfoList["revision"]); -} - -Rcpp::List jaspPlot::getOldPlotInfo(Rcpp::List & plotInfo) -{ - std::vector names; - getUniqueNestedNameVector(names); - jaspPlot * oldPlot = dynamic_cast(getOldObjectFromUniqueNestedNameVector(names)); - - if (oldPlot == nullptr) - { - jaspPrint("could not find an old plot"); - return Rcpp::List(); - } - jaspPrint("found a " + oldPlot->type() + " with name: " + oldPlot->_name + ". Resized by user: " + (oldPlot->_resizedByUser ? "yes" : "no")); - - if (oldPlot->_resizedByUser) - { - _width = oldPlot->_width; - _height = oldPlot->_height; - plotInfo["width"] = _width; - plotInfo["height"] = _height; - } - - if (oldPlot->_editOptions == Json::nullValue) - return Rcpp::List(); - else - return Rcpp::List::create( - Rcpp::_["editOptions"] = Rcpp::String(oldPlot->_editOptions.toStyledString()), - Rcpp::_["oldPlot"] = oldPlot->getPlotObject() - ); - -} - -Json::Value jaspPlot::convertToJSON() const -{ - Json::Value obj = jaspObject::convertToJSON(); - - obj["aspectRatio"] = _aspectRatio; - obj["width"] = _width; - obj["height"] = _height; - obj["status"] = _status; - obj["filePathPng"] = _filePathPng; - obj["revision"] = _revision; - obj["environmentName"] = _envName; - obj["editOptions"] = _editOptions; - obj["resizedByUser"] = _resizedByUser; - - obj["interactive"] = _interactive; - obj["interactiveConvertError"] = _interactiveConvertError; - obj["interactiveJsonData"] = _interactiveJsonData; - - obj["export"] = _export; - - return obj; -} - -void jaspPlot::convertFromJSON_SetFields(Json::Value in) -{ - jaspObject::convertFromJSON_SetFields(in); - - _aspectRatio = in.get("aspectRatio", 0.0f).asDouble(); - _width = in.get("width", -1).asInt(); - _height = in.get("height", -1).asInt(); - _revision = in.get("revision", 0).asInt(); - _status = in.get("status", "complete").asString(); - _filePathPng = in.get("filePathPng", "null").asString(); - _envName = in.get("environmentName", _envName).asString(); - _editOptions = in.get("editOptions", Json::nullValue); - _resizedByUser = in.get("resizedByUser", false).asBool(); - - _interactive = in.get("interactive", false).asBool(); - _interactiveConvertError = in.get("interactiveConvertError", "").asString(); - _interactiveJsonData = in.get("interactiveJsonData", "").asString(); - - _export = in.get("export", Json::nullValue); - - setUserPlotChangesFromRStateObject(); - - /*JASP_OBJECT_TIMERBEGIN - std::string jsonPlotObjStr = in.get("plotObjSerialized", "").asString(); - _plotObjSerialized = Rcpp::Vector(jsonPlotObjStr.begin(), jsonPlotObjStr.end()); - JASP_OBJECT_TIMEREND(converting from JSON)*/ -} - -std::string jaspPlot::toHtml() const -{ - std::stringstream out; - - out << "
" "\n" - << htmlTitle() << "\n"; - - if(_error || _errorMessage != "") - { - out << "

\n"; - if(_error ) out << "error: '" << _error << "'"; - if(_errorMessage != "") out << (_error ? " msg: '" : "errormessage: '") << _errorMessage << "'"; - out << "\n

"; - } - else - out << "\"a"; - - out << "
\n"; - - return out.str(); -} - -Rcpp::List jaspPlot::toRObject() -{ - Rcpp::List lst = Rcpp::List::create(Rcpp::Named("plotObject") = getPlotObject()); - lst.attr("title") = _title; - lst.attr("class") = Rcpp::CharacterVector({"jaspPlotWrapper", "jaspWrapper"}); - - // Include the export field so RoboReport and other RDS consumers can - // access machine-readable data (e.g., computed effect sizes) that the - // analysis author tagged onto the plot. Survives RDS stripping. - if (!_export.isNull()) - { - static Rcpp::Function fromJSON_export = Rcpp::Environment::namespace_env("jaspBase")["fromJSON"]; - Json::StreamWriterBuilder builder; - std::string exportJson = Json::writeString(builder, _export); - lst["export"] = Rcpp::as(fromJSON_export(exportJson)); - } - - // the reason this function is not const - Rcpp::Environment jaspObjectEnvironment = Rcpp::new_env(); - jaspObjectEnvironment.assign("jaspObject", Rcpp::as(Rcpp::wrap(jaspPlot_Interface(this)))); - lst.attr("jaspObjectEnvironment") = jaspObjectEnvironment; - - return lst; -} - -// ---- jaspPlot_Interface::setExport / getExport ---- -// Defined here (not inline in the header) because the conversions between -// Rcpp::List and Json::Value require non-trivial logic. - -void jaspPlot_Interface::setExport(Rcpp::List exportData) -{ - jaspPlot* plot = (jaspPlot*)myJaspObject; - plot->_export = plot->RObject_to_JsonValue(exportData); - myJaspObject->notifyParentOfChanges(); -} - -Rcpp::List jaspPlot_Interface::getExport() -{ - jaspPlot* plot = (jaspPlot*)myJaspObject; - if (plot->_export.isNull() || plot->_export.empty()) - return Rcpp::List(); - - // Convert Json::Value -> string -> R list via jsonlite (jaspBase dep). - static Rcpp::Function fromJSON_getExport = - Rcpp::Environment::namespace_env("jaspBase")["fromJSON"]; - Json::StreamWriterBuilder builder; - std::string jsonStr = Json::writeString(builder, plot->_export); - return Rcpp::as(fromJSON_getExport(jsonStr)); -} diff --git a/src/jaspPlot.h b/src/jaspPlot.h deleted file mode 100644 index 3e09aae5..00000000 --- a/src/jaspPlot.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once -#include "jaspObject.h" - -class jaspPlot : public jaspObject -{ -public: - jaspPlot(Rcpp::String title = "") : jaspObject(jaspObjectType::plot, title) { initEnvName(); } - - ~jaspPlot(); - - float _aspectRatio; - int _width, - _height, - _revision = 0; - bool _editing = false, - _resizedByUser = false, - _interactive = false; - std::string _filePathPng, - _status = "waiting", - _envName, - _interactiveConvertError = "", - _interactiveJsonData = ""; - Json::Value _editOptions = Json::nullValue; - - ///Machine-readable data exported by analysis authors for consumers - ///like RoboReport (e.g., median effect size, credible intervals, - ///BF at specific prior widths). Survives RDS stripping because it's - ///a plain JSON value, not an environment or ggplot object. - Json::Value _export = Json::nullValue; - - ///For safekeeping (aka state replacement?) - void setPlotObject(Rcpp::RObject plotSerialized); - void renderPlot(); - Rcpp::RObject getPlotObject() const; - - std::string dataToString(std::string prefix) const override; - - Json::Value metaEntry() const override { return constructMetaEntry("image"); } - Json::Value dataEntry(std::string & errorMessage) const override; - std::string toHtml() const override; - - Json::Value convertToJSON() const override; - void convertFromJSON_SetFields(Json::Value in) override; - - bool canShowErrorMessage() const override { return true; } - - void complete() { if(_status == "running" || _status == "waiting") _status = "complete"; } - void letRun() { _status = "running"; } - - Rcpp::List toRObject() /*const*/ override; - -private: - void initEnvName(); - void setUserPlotChangesFromRStateObject(); - - Rcpp::List getOldPlotInfo(Rcpp::List & plotInfo); - - //Rcpp::Vector _plotObjSerialized; -}; - - -class jaspPlot_Interface : public jaspObject_Interface -{ -public: - jaspPlot_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - void setPlotObject(Rcpp::RObject plotObject) { ((jaspPlot*)myJaspObject)->setPlotObject(plotObject); } - Rcpp::RObject getPlotObject() { return ((jaspPlot*)myJaspObject)->getPlotObject(); } - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, std::string, _filePathPng, FilePathPng) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, std::string, _status, Status) - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, float, _aspectRatio, AspectRatio) - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, int, _width, Width) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, int, _height, Height) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspPlot, int, _revision, Revision) - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NO_NOTIFY(jaspPlot, bool, _editing, Editing) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NO_NOTIFY(jaspPlot, bool, _resizedByUser, ResizedByUser) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR_NO_NOTIFY(jaspPlot, std::string, _interactiveJsonData, InteractiveJsonData) - - ///Set/export machine-readable data from R: - /// plot$export <- list(medianDelta = 0.45, ciLow = 0.12, ciHigh = 0.78) - ///Appears in both the JSON results and the RDS (survives stripping). - void setExport(Rcpp::List exportData); - Rcpp::List getExport(); -}; - -RCPP_EXPOSED_CLASS_NODECL(jaspPlot_Interface) diff --git a/src/jaspResults.h b/src/jaspResults.h deleted file mode 100644 index ee2e99b3..00000000 --- a/src/jaspResults.h +++ /dev/null @@ -1,155 +0,0 @@ -#pragma once -#include "jaspContainer.h" - -//copied from jasprcpp_interface.h -typedef void (*sendFuncDef)(const char *); -typedef bool (*pollMessagesFuncDef)(); - - -class jaspResults : public jaspContainer -{ -public: - jaspResults(Rcpp::String title, Rcpp::RObject oldState); - ~jaspResults(); - - //static functions to allow the values to be set before the constructor is called from R. Would be nicer to just run the constructor in C++ maybe? - static void setSendFunc(Rcpp::XPtr sendFunc); - static void setPollMessagesFunc(Rcpp::XPtr pollFunc); - static void setResponseData(int analysisID, int revision); - static void setSaveLocation(const std::string & root, const std::string & relativePath); - static void setWriteSealLocation(const std::string & root, const std::string & relativePath); - static void setBaseCitation(std::string baseCitation); - static void setInsideJASP(); - static bool isInsideJASP() { return _insideJASP; } - static Rcpp::String writeSealFilename() { return "jaspResultsFinishedWriting.txt"; } - - void send(std::string otherMsg = ""); - void checkForAnalysisChanged(); - void setStatus(std::string status); - std::string getStatus(); - - const char * constructResultJson(); - Json::Value metaEntry() const override; - Json::Value dataEntry(std::string & errorMessage) const override; - Json::Value dataEntry() const { std::string dummy(""); return dataEntry(dummy); } - - void childrenUpdatedCallbackHandler(bool ignoreSendTimer) override; - - void finalizedHandler() override { complete(); } - void complete(); - - void prepareForWriting(); - void finishWriting(); - bool lastWriteWorked() const; - void saveResults(); - - void loadResults(); - void setErrorMessage(Rcpp::String msg, std::string errorStatus); - void changeOptions(std::string opts); - void setOptions(std::string opts); - void pruneInvalidatedData(); - - Rcpp::List getOtherObjectsForState(); - Rcpp::List getPlotObjectsForState(); - Rcpp::List getPlotPathsForKeep(); - Rcpp::List getKeepList(); - std::string getResults() { return constructResultJson(); } - - std::string _relativePathKeep; - - Json::Value convertToJSON() const override; - void convertFromJSON_SetFields(Json::Value in) override; - - - void startProgressbar(int expectedTicks, Rcpp::String label); - void progressbarTick(); - - static void staticStartProgressbar(int expectedTicks, Rcpp::String label) { _jaspResults->startProgressbar(expectedTicks, label); } - static void staticProgressbarTick() { _jaspResults->progressbarTick(); } - - static Rcpp::RObject getObjectFromEnv(std::string envName); - static void setObjectInEnv(std::string envName, Rcpp::RObject obj); - static bool objectExistsInEnv(std::string envName); - static int analysisId() { return _analysisId; } ///< To pass analysisId to jaspReport easily - - jaspContainer * getOldResults() const { return _oldResults; } - - jaspObject * getOldObjectFromUniqueNestedNameVector(const std::vector& uniqueNames) override { return _oldResults == nullptr ? nullptr : _oldResults->findObjectWithNestedNameVector(uniqueNames); } ; - -private: - - // silences e.g., "./jaspResults.h:36:15: warning: 'jaspResults::dataEntry' hides overloaded virtual function [-Woverloaded-virtual]" - Json::Value metaEntry(jaspObject * ) const override { throw std::runtime_error("Don't call jaspResults::metaEntry(jaspObject * oldResult)"); }; - Json::Value dataEntry(jaspObject *, std::string & ) const override { throw std::runtime_error("Don't call jaspResults::dataEntry(jaspObject * oldResult, std::string & errorMsg)"); }; - - static jaspResults * _jaspResults; - static Rcpp::Environment * _RStorageEnv; //we need this environment to store R objects in a "named" fashion, because then the garbage collector doesn't throw away everything... - static Json::Value _response; - static sendFuncDef _ipccSendFunc; - static pollMessagesFuncDef _ipccPollFunc; - static std::string _saveResultsHere, - _saveResultsRoot, - _baseCitation, - _writeSealRoot, - _writeSealRelative; - static bool _insideJASP; - - std::string errorMessage = ""; - Json::Value _currentOptions = Json::nullValue, - _previousOptions = Json::nullValue; - - jaspContainer * _oldResults = nullptr; - - void addSerializedPlotObjsForStateFromJaspObject( jaspObject * obj, Rcpp::List & pngImgObj); - void addPlotPathsForKeepFromJaspObject( jaspObject * obj, Rcpp::List & pngPathImgObj); - void addSerializedOtherObjsForStateFromJaspObject( jaspObject * obj, Rcpp::List & cumulativeList); - void fillEnvironmentWithStateObjects(Rcpp::List state); - void storeOldResults(); - - static void setAnalysisId(int analysisId); - - - int _progressbarExpectedTicks = 100, - _progressbarLastUpdateTime = -1, - _progressbarTicks = 0, - _sendingFeedbackLastTime = -1, - _progressbarBetweenUpdatesTime = 500, - _sendingFeedbackInterval = 1000; - - static int _analysisId; -}; - -void JASPresultFinalizer(jaspResults * obj); - -Rcpp::RObject givejaspResultsModule(); - - -class jaspResults_Interface : public jaspContainer_Interface -{ -public: - jaspResults_Interface(jaspObject * dataObj) : jaspContainer_Interface(dataObj) {} - - void send() { ((jaspResults*)myJaspObject)->send(); } - void complete() { ((jaspResults*)myJaspObject)->complete(); } - void saveResults() { ((jaspResults*)myJaspObject)->saveResults(); } - void finishWriting() { ((jaspResults*)myJaspObject)->finishWriting(); } - Rcpp::List getOtherObjectsForState() { return ((jaspResults*)myJaspObject)->getOtherObjectsForState(); } - Rcpp::List getPlotObjectsForState() { return ((jaspResults*)myJaspObject)->getPlotObjectsForState(); } - Rcpp::List getKeepList() { return ((jaspResults*)myJaspObject)->getKeepList(); } - std::string getResults() { return ((jaspResults*)myJaspObject)->getResults(); } - - void setErrorMessage(Rcpp::String msg, std::string errorStatus) { ((jaspResults*)myJaspObject)->setErrorMessage(msg, errorStatus); } - - void setOptions(std::string opts) { ((jaspResults*)myJaspObject)->setOptions(opts); } - void changeOptions(std::string opts) { ((jaspResults*)myJaspObject)->changeOptions(opts); } - - void setStatus(std::string status) { ((jaspResults*)myJaspObject)->setStatus(status); } - std::string getStatus() { return ((jaspResults*)myJaspObject)->getStatus(); } - - void prepareForWriting() { ((jaspResults*)myJaspObject)->prepareForWriting(); } - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspResults, std::string, _relativePathKeep, RelativePathKeep) -}; - - -RCPP_EXPOSED_CLASS_NODECL(jaspResults_Interface) diff --git a/src/jaspState.h b/src/jaspState.h deleted file mode 100644 index 39058f44..00000000 --- a/src/jaspState.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include "jaspObject.h" - -class jaspState : public jaspObject -{ -public: - jaspState(std::string title = "") : jaspObject(jaspObjectType::state, title) { initEnvName(); } - - void setObject(Rcpp::RObject obj); - Rcpp::RObject getObject(); - - Json::Value convertToJSON() const override; - void convertFromJSON_SetFields(Json::Value in) override; - std::string dataToString(std::string prefix) const override; - std::string _envName; - -private: - void initEnvName(); -}; - - - -class jaspState_Interface : public jaspObject_Interface -{ -public: - jaspState_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - void setObject(Rcpp::RObject obj) { ((jaspState*)(myJaspObject))->setObject(obj); } - Rcpp::RObject getObject() { return ((jaspState*)(myJaspObject))->getObject(); } -}; - -RCPP_EXPOSED_CLASS_NODECL(jaspState_Interface) - diff --git a/src/jaspTable.h b/src/jaspTable.h deleted file mode 100644 index 950f052f..00000000 --- a/src/jaspTable.h +++ /dev/null @@ -1,461 +0,0 @@ -#pragma once -#include "jaspObject.h" -#include "jaspList.h" -#include - -struct jaspColRowCombination -{ - jaspColRowCombination(std::string name, std::string title, bool overwrite, bool removeSeparator, Json::Value colNames, Json::Value rowNames, Json::Value colOvertitles, Json::Value rowOvertitles) - : name(name), title(title), overwrite(overwrite), removeSeparator(removeSeparator), colNames(colNames), rowNames(rowNames), colOvertitles(colOvertitles), rowOvertitles(rowOvertitles) {} - - jaspColRowCombination(Json::Value convertFromThis) { throw std::runtime_error("Not implemented");} - - std::string name, title; - bool overwrite, removeSeparator; - Json::Value colNames, rowNames, colOvertitles, rowOvertitles; - - std::string toString(); - - Json::Value convertToJSON() const { throw std::runtime_error("Not implemented"); } - -}; - -namespace footnotesNamespace -{ - -struct tableFields -{ - - tableFields(std::set rows, std::set cols) : _rows(rows), _cols(cols) {} - - Json::Value rowsToJSON() const; - Json::Value colsToJSON() const; - - struct hasher //Special hash func obj to differentiate between different sets of tableFields - { - std::size_t operator()(tableFields const & tf) const noexcept - { - return std::hash{}(tf.getCompareString()); - } - }; - - struct comparer - { - bool operator()(const tableFields & lhs, const tableFields & rhs) const - { - return lhs.getCompareString() < rhs.getCompareString(); //Don't really care about the results logic - } - - }; - - std::string getCompareString() const { return rowsToJSON().toStyledString() + "<$>" + colsToJSON().toStyledString(); } - -private: - std::set _rows, - _cols; -}; - -inline bool operator==(const tableFields & lhs, const tableFields & rhs) -{ - return lhs.getCompareString() == rhs.getCompareString(); -} - -struct footnotes -{ - void insert(std::string text, std::string symbol, std::vector colNames, std::vector rowNames); - void convertFromJSON_SetFields(Json::Value footnotes); - Json::Value convertToJSON() const; - void convertToJSONOrdered(std::map rowNames, std::map colNames, Json::Value & fullList, Json::Value & mergedList) const; - Rcpp::List toRObject() const; - - private: - std::map >> _data; //text -> symbol -> rows+cols -}; - -} - -using footnotesNamespace::footnotes; - -class jaspTable : public jaspObject -{ -public: - jaspTable(Rcpp::String title = "") : jaspObject(jaspObjectType::table, title), _colNames("colNames"), _colTypes("colTypes"), _colTitles("colTitles"), _colOvertitles("colOvertitles"), _colFormats("colFormats"), _rowNames("rowNames"), _rowTitles("rowTitles") {} - - void setColNames(Rcpp::List newNames) { _colNames.setRows(newNames); } - jaspStringlist _colNames; - - void setColTypes(Rcpp::List newTypes) { _colTypes.setRows(newTypes); } - jaspStringlist _colTypes; - - void setColTitles(Rcpp::List newTitles) { _colTitles.setRows(newTitles); } - jaspStringlist _colTitles; - - void setColOvertitles(Rcpp::List newTitles) { _colOvertitles.setRows(newTitles); } - jaspStringlist _colOvertitles; - - void setColFormats(Rcpp::List newFormats) { _colFormats.setRows(newFormats); } - jaspStringlist _colFormats; - - void setColCombines(Rcpp::List newCombines) { _colCombines.setRows(newCombines); } - jaspBoollist _colCombines; - - void setRowNames(Rcpp::List newNames) { _rowNames.setRows(newNames); } - jaspStringlist _rowNames; - - void setRowTitles(Rcpp::List newTitles) { _rowTitles.setRows(newTitles); } - jaspStringlist _rowTitles; - - ///Going to assume it is called like addColumInfo(name=NULL, title=NULL, type=NULL, format=NULL, combine=NULL, overTitle=NULL) - void addColumnInfo(Rcpp::RObject name, Rcpp::RObject title, Rcpp::RObject type, Rcpp::RObject format, Rcpp::RObject combine, Rcpp::RObject overTitle); - - ///we are going to pretend that the arguments in R would be: addFootnote(message="", symbol=NULL, col_names=NULL, row_names=NULL) - void addFootnote(Rcpp::RObject message, Rcpp::RObject symbol, Rcpp::RObject col_names, Rcpp::RObject row_names); - - ///Accepts data.frame, list, matrix or vector. If the input is one-dimensional it is assumed to be the first row, if any names are set they are copied to colNames/rowNames as far as they aren't set yet. - void setData(Rcpp::RObject newData); - - ///Accepts data.frame, list, matrix or vector. If the input is one-dimensional it is assumed to be a single column, if two-dimensional then it will be assumed to be cols {cells/rows}, if three-dimensional or higher things probably break. - void addColumns(Rcpp::RObject newColumns); - - ///Accepts data.frame, list, matrix or vector. If the input is one-dimensional it is assumed to be a single row, if two-dimensional then it will be assumed to be rows {cells/cols}, if three-dimensional or higher things probably break. Also fills up each column up to the maximum length one with nulls. - void addRows(Rcpp::RObject newRows, Rcpp::CharacterVector _rowNames); - void addRow (Rcpp::RObject newRow, Rcpp::CharacterVector _rowName); - - void addRowsWithoutNames(Rcpp::RObject newRows) { addRows(newRows, Rcpp::CharacterVector()); } - void addRowWithoutNames (Rcpp::RObject newRow) { addRow (newRow, Rcpp::CharacterVector()); } - - void setColumn(std::string columnName, Rcpp::RObject column); - - std::string dataToString(std::string prefix) const override; - - void complete() { if(_status == "running") _status = "complete"; } - void letRun() { _status = "running"; } - - bool canShowErrorMessage() const override { return true; } - - Json::Value metaEntry() const override { return constructMetaEntry("table"); } - Json::Value dataEntry(std::string & errorMessage) const override; - std::string toHtml() const override; - - std::string defaultColName(size_t col) const { return "col"+ std::to_string(col); } - std::string defaultRowName(size_t row) const { return "row"+ std::to_string(row); } - std::string getRowName(size_t row) const { return _rowNames[row] == "" ? defaultRowName(row) : _rowNames[row]; } - std::string getColName(size_t col) const { return _colNames[col] == "" ? defaultColName(col) : _colNames[col]; } - std::string getColType(size_t col) const; - - bool isSpecialColumn(size_t col) const; - bool columnSpecified(size_t col) const { return _specifiedColumns.count(getColName(col)) > 0; } - bool columnSpecified(std::string col) const { return _specifiedColumns.count(col) > 0; } - - Json::Value getCell( size_t col, size_t row, size_t maxCol, size_t maxRow) const; - std::string getCellFormatted( size_t col, size_t row, size_t maxCol, size_t maxRow) const; - - void calculateMaxColRow(size_t & maxCol, size_t & maxRow) const; - - void setExpectedSize(size_t columns, size_t rows) { setExpectedRows(rows); setExpectedColumns(columns); } - void setExpectedRows(size_t rows) { _expectedRowCount = rows; } - void setExpectedColumns(size_t columns) { _expectedColumnCount = columns; } - - Rcpp::List toRObject() /*const*/ override; - -protected: - std::vector getDisplayableColTitles(bool normalizeLengths = true, bool onlySpecifiedColumns = true) const; - std::vector getDisplayableRowTitles(bool normalizeLengths = true) const; - void rectangularDataWithNamesToString( std::stringstream & out, std::string prefix, std::vector> vierkant, std::vector sideNames, std::vector topNames, std::map sideOvertitles, std::map topOvertitles) const; - void rectangularDataWithNamesToHtml( std::stringstream & out, std::vector> vierkant, std::vector sideNames, std::vector topNames, std::map sideOvertitles, std::map topOvertitles) const; - - - std::map getOvertitlesMap() const; - std::vector> dataToRectangularVector(bool normalizeColLengths = false, bool normalizeRowLengths = false) const; - static std::vector> transposeRectangularVector(const std::vector> & in); - std::map> getOvertitleRanges(std::vector names, std::map overtitles) const; - - int getDesiredColumnIndexFromNameForColumnAdding(std::string colName); - int getDesiredColumnIndexFromNameForRowAdding(std::string colName, int previouslyAddedUnnamed); - - Json::Value schemaJson(Json::Value tmpFootnotesFull) const; - Json::Value rowsJson(Json::Value tmpFootnotesFull) const; - jaspTableColumnType deriveColumnType(int col) const; - - std::map mapColNamesToIndices() const; - std::map mapRowNamesToIndices() const; - - Json::Value convertToJSON() const override; - void convertFromJSON_SetFields(Json::Value in) override; - - void addOrSetColumnInData(std::vector column, std::string colName=""); - int pushbackToColumnInData(std::vector column, std::string colName, int equalizedColumnsLength, int previouslyAddedUnnamed); - - template void setDataFromVector(Rcpp::Vector newData) - { - std::vector localColNames = extractElementOrColumnNames(newData); - extractRowNames(newData, true); - - _data.clear(); - auto cols = RcppVector_to_VectorJson(newData); - - for(int col=0; col({cols[col]}), localColNames.size() > col ? localColNames[col] : ""); - } - - void setDataFromList(Rcpp::List newData) - { - std::vector localColNames = extractElementOrColumnNames(newData); - extractRowNames(newData, true); - - _data.clear(); - for(size_t col=0; col col ? localColNames[col] : ""); - } - - template void setDataFromMatrix(Rcpp::Matrix newData) - { - std::vector localColNames = extractElementOrColumnNames(newData); - extractRowNames(newData, true); - - std::vector> jsonMat = RcppMatrix_to_Vector2Json(newData); - - _data.clear(); - for(size_t col=0; col col ? localColNames[col] : ""); - } - - void addColumnsFromList(Rcpp::List newData); - - template void addColumnFromVector(Rcpp::Vector newData) - { - setRowNamesWhereApplicable(extractElementOrColumnNames(newData)); - - _data.push_back(RcppVector_to_VectorJson(newData)); - } - - template void setColumnFromVector(Rcpp::Vector newData, size_t col) - { - setRowNamesWhereApplicable(extractElementOrColumnNames(newData)); - - if(_data.size() <= col) - _data.resize(col+1); - _data[col] = RcppVector_to_VectorJson(newData); - } - - void setColumnFromMixedVector(Rcpp::List newData, size_t col) - { - setRowNamesWhereApplicable(extractElementOrColumnNames(newData)); - - if(_data.size() <= col) - _data.resize(col+1); - _data[col] = MixedRcppVector_to_VectorJson(newData); - - } - - bool isMixedJson(const Json::Value &v) const { return v.isObject() && !v.get("value", Json::nullValue).isNull() && !v.get("type", Json::nullValue).isNull() && v.isMember("format"); } - - void setColumnFromList(Rcpp::List column, int colIndex); - - template void addColumnsFromMatrix(Rcpp::Matrix newData) - { - std::vector localColNames = extractElementOrColumnNames(newData); - extractRowNames(newData, true); - - std::vector> jsonMat = RcppMatrix_to_Vector2Json(newData); - - for(size_t col=0; col col ? localColNames[col] : ""); - } - - template void addRowFromVector(Rcpp::Vector newData, Rcpp::CharacterVector newRowNames) - { - std::vector localColNames = extractElementOrColumnNames(newData); - - auto row = RcppVector_to_VectorJson(newData); - - int equalizedColumnsLength = equalizeColumnsLengths(); - int previouslyAddedUnnamedCols = 0; - - for(int row=0; row({row[col]}), localColNames.size() > col ? localColNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamedCols); - - } - - - static size_t lengthFromRObject(Rcpp::RObject rObj); - static size_t lengthFromList(Rcpp::List list) { return list.size(); } - template static size_t lengthFromVector(Rcpp::Vector vec) { return vec.size(); } - - void addRowFromList(Rcpp::List newData, Rcpp::CharacterVector newRowNames); - void addRowsFromList(Rcpp::List newData, Rcpp::CharacterVector newRowNames); - - void addRowsFromDataFrame(Rcpp::DataFrame newData) - { - newData = convertFactorsToCharacters(newData); - int equalizedColumnsLength = equalizeColumnsLengths(); - int previouslyAddedUnnamedCols = 0; - - std::vector localColNames = extractElementOrColumnNames(newData); - - for(size_t col=0; col col ? localColNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamedCols); - } - - } - - template void addRowsFromMatrix(Rcpp::Matrix newData, Rcpp::CharacterVector newRowNames) - { - std::vector localColNames = extractElementOrColumnNames(newData); - // ??? something with rownames? extractRowNames(newData, true); - - int equalizedColumnsLength = equalizeColumnsLengths(); - int previouslyAddedUnnamedCols = 0; - - for(int row=0; row(newData); - - for(int col=0; col({jsonMatrix[col]}), localColNames.size() > col ? localColNames[col] : "", equalizedColumnsLength, previouslyAddedUnnamedCols); - } - - void setRowNamesWhereApplicable(std::vector rowNamesList) - { - for(size_t row=0; row std::vector extractElementOrColumnNames(RCPP_CLASS rObj, bool setColNamesInTable=false) - { - std::vector colNamesVec = jaspObject::extractElementOrColumnNames(rObj); - - for(size_t col=0; col std::vector extractRowNames(RCPP_CLASS rObj, bool setRowNamesInTable=false) - { - Rcpp::RObject rowNamesRObject = Rcpp::rownames(rObj), rijnamesRObject = rObj.attr("row.names"); - Rcpp::CharacterVector rowNamesList; - std::vector rowNamesVec; - - if(!rowNamesRObject.isNULL() || !rijnamesRObject.isNULL()) - { - rowNamesList = !rowNamesRObject.isNULL() ? rowNamesRObject : rijnamesRObject; - - for(size_t row=0; row(rowNamesList[row])); - - if(setRowNamesInTable && rowNamesList[row] != "" && (_rowNames.rowCount() <= row || _rowNames[row] == "")) //Add new rowNames or overwrite unset ones but if the user took the trouble to manually set it then just leave it I guess? - _rowNames[row] = rowNamesList[row]; - } - } - - return rowNamesVec; - } - - template void extractRowAndColumnNames(RCPP_CLASS rObj, int columnOffset = 0, int rowOffset = 0) - { - Rcpp::RObject colNamesRObject = Rcpp::colnames(rObj), rowNamesRObject = Rcpp::rownames(rObj), kolnamesRObject = rObj.names(), rijnamesRObject = rObj.attr("row.names"); - Rcpp::CharacterVector colNamesList, rowNamesList; - - if(!colNamesRObject.isNULL() || !kolnamesRObject.isNULL()) - { - colNamesList = !colNamesRObject.isNULL() ? colNamesRObject : kolnamesRObject; - - for(size_t col=0; col _specifiedColumns; - -private: - footnotes _footnotes; - std::vector> _data; //First columns, then rows. - std::vector _colRowCombinations; - size_t _expectedColumnCount = 0, - _expectedRowCount = 0; -}; - -class jaspTable_Interface : public jaspObject_Interface -{ -public: - jaspTable_Interface(jaspObject * dataObj) : jaspObject_Interface(dataObj) {} - - jaspStringlist_Interface getColNames() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colNames) ); } - jaspStringlist_Interface getColTypes() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colTypes) ); } - jaspStringlist_Interface getColTitles() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colTitles) ); } - jaspStringlist_Interface getColOvertitles() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colOvertitles) ); } - jaspStringlist_Interface getColFormats() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_colFormats) ); } - jaspBoollist_Interface getColCombines() { return jaspBoollist_Interface( &(((jaspTable*)myJaspObject)->_colCombines) ); } - jaspStringlist_Interface getRowNames() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_rowNames) ); } - jaspStringlist_Interface getRowTitles() { return jaspStringlist_Interface( &(((jaspTable*)myJaspObject)->_rowTitles) ); } - - void setColNames( Rcpp::List newNames) { ((jaspTable*)myJaspObject)->setColNames(newNames); } - void setColTypes( Rcpp::List newTypes) { ((jaspTable*)myJaspObject)->setColTypes(newTypes); } - void setColTitles( Rcpp::List newTitles) { ((jaspTable*)myJaspObject)->setColTitles(newTitles); } - void setColOvertitles( Rcpp::List newTitles) { ((jaspTable*)myJaspObject)->setColOvertitles(newTitles); } - void setColFormats( Rcpp::List newFormats) { ((jaspTable*)myJaspObject)->setColFormats(newFormats); } - void setColCombines( Rcpp::List newCombines) { ((jaspTable*)myJaspObject)->setColCombines(newCombines); } - void setRowNames( Rcpp::List newNames) { ((jaspTable*)myJaspObject)->setRowNames(newNames); } - void setRowTitles( Rcpp::List newTitles) { ((jaspTable*)myJaspObject)->setRowTitles(newTitles); } - - void addColumnInfo(Rcpp::RObject name, Rcpp::RObject title, Rcpp::RObject type, Rcpp::RObject format, Rcpp::RObject combine, Rcpp::RObject overtitle) { ((jaspTable*)myJaspObject)->addColumnInfo(name, title, type, format, combine, overtitle); } - void addFootnote(Rcpp::RObject message, Rcpp::RObject symbol, Rcpp::RObject col_names, Rcpp::RObject row_names) { ((jaspTable*)myJaspObject)->addFootnote(message, symbol, col_names, row_names); } - - void setData(Rcpp::RObject newData) { ((jaspTable*)myJaspObject)->setData(newData); } - void addColumns(Rcpp::RObject newColumns) { ((jaspTable*)myJaspObject)->addColumns(newColumns); } - - //void combineColumns(Rcpp::map_named_args named_args) { ((jaspTable*)myJaspObject)->combineColumns(named_args); } - //void combineRows(Rcpp::map_named_args named_args) { ((jaspTable*)myJaspObject)->combineRows(named_args); } - - void addRows( Rcpp::RObject newRows, Rcpp::CharacterVector rowNames) { ((jaspTable*)myJaspObject)->addRows(newRows, rowNames); } - void addRowsWithoutNames( Rcpp::RObject newRows) { ((jaspTable*)myJaspObject)->addRowsWithoutNames(newRows); } - void addRow( Rcpp::RObject newRow, Rcpp::CharacterVector rowNames) { ((jaspTable*)myJaspObject)->addRow(newRow, rowNames); } - void addRowWithoutNames( Rcpp::RObject newRow) { ((jaspTable*)myJaspObject)->addRowWithoutNames(newRow); } - void setColumn( std::string columnName, Rcpp::RObject column) { ((jaspTable*)myJaspObject)->setColumn(columnName, column); } - - void setExpectedSize(size_t columns, size_t rows) { ((jaspTable*)myJaspObject)->setExpectedSize(columns, rows); } - void setExpectedRows(size_t rows) { ((jaspTable*)myJaspObject)->setExpectedRows(rows); } - void setExpectedColumns(size_t columns) { ((jaspTable*)myJaspObject)->setExpectedColumns(columns); } - - - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspTable, bool, _transposeTable, TransposeTable) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspTable, bool, _transposeWithOvertitle, TransposeWithOvertitle) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspTable, std::string, _status, Status) - JASPOBJECT_INTERFACE_PROPERTY_FUNCTIONS_GENERATOR(jaspTable, bool, _showSpecifiedColumnsOnly, ShowSpecifiedColumnsOnly) -}; - -RCPP_EXPOSED_CLASS_NODECL(jaspTable_Interface) diff --git a/tests/equivalence/README.md b/tests/equivalence/README.md new file mode 100644 index 00000000..8922514d --- /dev/null +++ b/tests/equivalence/README.md @@ -0,0 +1,48 @@ +# Equivalence harness (jaspResults core/adapters refactor) + +Regression gate guaranteeing the R-visible behavior of jaspBase stays +**bit-identical** while the C++ is split into `src/core/` (R-free) and +`src/adapters/rcpp/` — see `tmp/plan-python-interface.md`. + +## Files + +- `moduleFingerprint.R` — dumps the full `RCPP_MODULE(jaspResults)` surface + (module functions, classes, methods, properties: names, signatures, + docstrings) from the loaded package. +- `goldenBaseline.R` — builds a rich results tree (html, table with all column + types + footnotes, nested container, plot, state, report, column, qmlSource, + option dependencies) and writes three JSONs: + - `golden_response.json` — `getResults()` (the viewer JSON: meta + dataEntry), + - `golden_pruned.json` — `getResults()` after `changeOptions()` invalidates a + dependency (exercises pruning + old-results merge; the pruned table's status + flips `complete`→`running`), + - `golden_saved.json` — the `saveResults()` file (`convertToJSON()` tree). +- `toRObjectBaseline.R` — structural fingerprint of the `toRObject()` conversion + tree (S3 classes, names, data.frame column types/titles). Locks down the R-side + object conversion, which the JSON goldens do not cover. +- `fixtures/` — committed baselines, generated from the pre-refactor build. +- `runGate.sh` — rebuilds jaspBase from the working tree into a temp library, + regenerates everything, and byte-compares against `fixtures/`. +- `tableGoldens.R` / `tableGoldens.py` — Phase-2 golden-table matrix: 20 paired + cases (cell types, NA/NaN/Inf, escaping, mixed columns, footnotes, addRow(s), + addColumns, transpose, expected-size, matrix/vector/DataFrame/Categorical + ingest) built through the R module and the Python `_jaspresults` extension + respectively; outputs (`*_results.json`, `*_toHtml.txt`) must be + byte-identical. +- `runTableGoldens.sh` — builds the Python extension (via the `python/` + package), runs both generators, and byte-compares every produced file. + +## Usage + +```sh +tests/equivalence/runGate.sh # rebuild + compare +JASP_EQUIV_RLIB=/path/to/lib tests/equivalence/runGate.sh --skip-build +``` + +Rules during the refactor: +- the gate must pass after every commit; +- fixtures are only regenerated by the commit that intentionally changes + R-visible behavior (the Phase-1 bug-fix commit), and that commit must say so. + +Note: the R library used must be built from the *current* `src/` — a stale +system-wide install will fail the goldens (it lacks recent `src/` changes). diff --git a/tests/equivalence/fixtures/golden_pruned.json b/tests/equivalence/fixtures/golden_pruned.json new file mode 100644 index 00000000..c97a2012 --- /dev/null +++ b/tests/equivalence/fixtures/golden_pruned.json @@ -0,0 +1,322 @@ +{ + "id" : 42, + "progress" : + { + "label" : "", + "value" : -1 + }, + "results" : + { + ".meta" : + [ + { + "info" : "some help text", + "name" : "intro", + "title" : "Introduction", + "type" : "htmlNode" + }, + { + "info" : "", + "name" : "desc", + "title" : "Descriptives", + "type" : "table" + }, + { + "info" : "", + "meta" : + [ + { + "info" : "", + "name" : "checks_homog", + "title" : "Homogeneity", + "type" : "htmlNode" + }, + { + "info" : "", + "name" : "checks_norm", + "title" : "Normality", + "type" : "htmlNode" + } + ], + "name" : "checks", + "title" : "Assumption Checks", + "type" : "collection" + }, + { + "info" : "", + "name" : "boxplot", + "title" : "Boxplot", + "type" : "image" + }, + { + "info" : "", + "name" : "report", + "title" : "Report", + "type" : "reportNode" + }, + { + "info" : "", + "name" : "col", + "title" : "jaspColumn for computedCol", + "type" : "column" + }, + { + "info" : "", + "name" : "qml", + "sourceID" : "priorWidthSlider", + "title" : "", + "type" : "qmlSource" + }, + { + "info" : "", + "name" : "kept", + "title" : "Kept", + "type" : "htmlNode" + }, + { + "info" : "", + "name" : "prunedTable", + "title" : "Pruned table", + "type" : "table" + } + ], + "boxplot" : + { + "aspectRatio" : 1.5, + "convertible" : true, + "data" : "state/figures/plot_0.png", + "editOptions" : null, + "editable" : false, + "errorType" : "fatalError", + "export" : null, + "height" : 320, + "interactive" : false, + "interactiveConvertError" : "", + "interactiveJsonData" : "", + "name" : "boxplot", + "reasonNotEditable" : "unknown reason", + "revision" : 0, + "status" : "complete", + "title" : "Boxplot", + "width" : 480 + }, + "checks" : + { + "collection" : + { + "checks_homog" : + { + "class" : "", + "elementType" : "p", + "maxWidth" : "15cm", + "name" : "checks_homog", + "rawtext" : "variances are equal", + "text" : "

variances are equal

", + "title" : "Homogeneity" + }, + "checks_norm" : + { + "class" : "", + "elementType" : "p", + "maxWidth" : "15cm", + "name" : "checks_norm", + "rawtext" : "normality looks fine", + "text" : "

normality looks fine

", + "title" : "Normality" + } + }, + "initCollapsed" : true, + "name" : "checks", + "title" : "Assumption Checks" + }, + "col" : + { + "columnName" : "computedCol", + "columnType" : "unknown", + "dataChanged" : false, + "removed" : false, + "typeChanged" : false + }, + "desc" : + { + "casesAcrossColumns" : false, + "data" : + [ + { + "int" : 1, + "lgl" : true, + "num" : 1.5, + "txt" : "a < b" + }, + { + ".footnotes" : + { + "txt" : + [ + 2 + ] + }, + "int" : 2, + "lgl" : false, + "num" : 2.5, + "txt" : "x & y" + }, + { + "int" : 3, + "lgl" : true, + "num" : "", + "txt" : "" + }, + { + "int" : 4, + "lgl" : "", + "num" : 4.5, + "txt" : "plain" + } + ], + "footnotes" : + [ + { + "cols" : null, + "myOrder" : 0, + "rows" : null, + "symbol" : 0, + "text" : "table-level note" + }, + { + "cols" : + [ + "num" + ], + "myOrder" : 8, + "rows" : null, + "symbol" : "*", + "text" : "column note" + }, + { + "cols" : + [ + "txt" + ], + "myOrder" : 18, + "rows" : + [ + "r2" + ], + "symbol" : "#", + "text" : "cell note" + } + ], + "name" : "desc", + "overTitle" : false, + "schema" : + { + "fields" : + [ + { + ".footnotes" : + [ + 1 + ], + "name" : "num", + "title" : "Numeric", + "type" : "number" + }, + { + "name" : "int", + "title" : "Integer", + "type" : "integer" + }, + { + "name" : "txt", + "title" : "Text", + "type" : "string" + }, + { + "name" : "lgl", + "title" : "Logical", + "type" : "logical" + } + ] + }, + "status" : "complete", + "title" : "Descriptives" + }, + "intro" : + { + "citation" : + [ + "JASP Team (2026). Golden baseline." + ], + "class" : "", + "elementType" : "h2", + "maxWidth" : "15cm", + "name" : "intro", + "rawtext" : "Intro with tags, a < b & c > d.", + "text" : "

Intro with tags, a < b & c > d.

", + "title" : "Introduction" + }, + "kept" : + { + "class" : "", + "elementType" : "p", + "maxWidth" : "15cm", + "name" : "kept", + "rawtext" : "depends on hypothesis", + "text" : "

depends on hypothesis

", + "title" : "Kept" + }, + "name" : "", + "prunedTable" : + { + "casesAcrossColumns" : false, + "data" : + [ + { + "v" : 1.0 + }, + { + "v" : 2.0 + }, + { + "v" : 3.0 + } + ], + "footnotes" : [], + "name" : "prunedTable", + "overTitle" : false, + "schema" : + { + "fields" : + [ + { + "name" : "v", + "title" : "v", + "type" : "number" + } + ] + }, + "status" : "running", + "title" : "Pruned table" + }, + "qml" : + { + "sourceID" : "priorWidthSlider" + }, + "report" : + { + "analysisId" : 42, + "html" : "

Report

A warning worth reporting.

", + "name" : "report", + "rawtext" : "A warning worth reporting.", + "report" : false, + "title" : "hide me", + "topHtml" : "", + "warningIndex" : 0, + "warningsTotal" : 0 + } + }, + "revision" : 7, + "status" : "running", + "typeRequest" : "analysis" +} diff --git a/tests/equivalence/fixtures/golden_response.json b/tests/equivalence/fixtures/golden_response.json new file mode 100644 index 00000000..5abfdd29 --- /dev/null +++ b/tests/equivalence/fixtures/golden_response.json @@ -0,0 +1,322 @@ +{ + "id" : 42, + "progress" : + { + "label" : "", + "value" : -1 + }, + "results" : + { + ".meta" : + [ + { + "info" : "some help text", + "name" : "intro", + "title" : "Introduction", + "type" : "htmlNode" + }, + { + "info" : "", + "name" : "desc", + "title" : "Descriptives", + "type" : "table" + }, + { + "info" : "", + "meta" : + [ + { + "info" : "", + "name" : "checks_homog", + "title" : "Homogeneity", + "type" : "htmlNode" + }, + { + "info" : "", + "name" : "checks_norm", + "title" : "Normality", + "type" : "htmlNode" + } + ], + "name" : "checks", + "title" : "Assumption Checks", + "type" : "collection" + }, + { + "info" : "", + "name" : "boxplot", + "title" : "Boxplot", + "type" : "image" + }, + { + "info" : "", + "name" : "report", + "title" : "Report", + "type" : "reportNode" + }, + { + "info" : "", + "name" : "col", + "title" : "jaspColumn for computedCol", + "type" : "column" + }, + { + "info" : "", + "name" : "qml", + "sourceID" : "priorWidthSlider", + "title" : "", + "type" : "qmlSource" + }, + { + "info" : "", + "name" : "kept", + "title" : "Kept", + "type" : "htmlNode" + }, + { + "info" : "", + "name" : "prunedTable", + "title" : "Pruned table", + "type" : "table" + } + ], + "boxplot" : + { + "aspectRatio" : 1.5, + "convertible" : true, + "data" : "state/figures/plot_0.png", + "editOptions" : null, + "editable" : false, + "errorType" : "fatalError", + "export" : null, + "height" : 320, + "interactive" : false, + "interactiveConvertError" : "", + "interactiveJsonData" : "", + "name" : "boxplot", + "reasonNotEditable" : "unknown reason", + "revision" : 0, + "status" : "complete", + "title" : "Boxplot", + "width" : 480 + }, + "checks" : + { + "collection" : + { + "checks_homog" : + { + "class" : "", + "elementType" : "p", + "maxWidth" : "15cm", + "name" : "checks_homog", + "rawtext" : "variances are equal", + "text" : "

variances are equal

", + "title" : "Homogeneity" + }, + "checks_norm" : + { + "class" : "", + "elementType" : "p", + "maxWidth" : "15cm", + "name" : "checks_norm", + "rawtext" : "normality looks fine", + "text" : "

normality looks fine

", + "title" : "Normality" + } + }, + "initCollapsed" : true, + "name" : "checks", + "title" : "Assumption Checks" + }, + "col" : + { + "columnName" : "computedCol", + "columnType" : "unknown", + "dataChanged" : false, + "removed" : false, + "typeChanged" : false + }, + "desc" : + { + "casesAcrossColumns" : false, + "data" : + [ + { + "int" : 1, + "lgl" : true, + "num" : 1.5, + "txt" : "a < b" + }, + { + ".footnotes" : + { + "txt" : + [ + 2 + ] + }, + "int" : 2, + "lgl" : false, + "num" : 2.5, + "txt" : "x & y" + }, + { + "int" : 3, + "lgl" : true, + "num" : "", + "txt" : "" + }, + { + "int" : 4, + "lgl" : "", + "num" : 4.5, + "txt" : "plain" + } + ], + "footnotes" : + [ + { + "cols" : null, + "myOrder" : 0, + "rows" : null, + "symbol" : 0, + "text" : "table-level note" + }, + { + "cols" : + [ + "num" + ], + "myOrder" : 8, + "rows" : null, + "symbol" : "*", + "text" : "column note" + }, + { + "cols" : + [ + "txt" + ], + "myOrder" : 18, + "rows" : + [ + "r2" + ], + "symbol" : "#", + "text" : "cell note" + } + ], + "name" : "desc", + "overTitle" : false, + "schema" : + { + "fields" : + [ + { + ".footnotes" : + [ + 1 + ], + "name" : "num", + "title" : "Numeric", + "type" : "number" + }, + { + "name" : "int", + "title" : "Integer", + "type" : "integer" + }, + { + "name" : "txt", + "title" : "Text", + "type" : "string" + }, + { + "name" : "lgl", + "title" : "Logical", + "type" : "logical" + } + ] + }, + "status" : "complete", + "title" : "Descriptives" + }, + "intro" : + { + "citation" : + [ + "JASP Team (2026). Golden baseline." + ], + "class" : "", + "elementType" : "h2", + "maxWidth" : "15cm", + "name" : "intro", + "rawtext" : "Intro with tags, a < b & c > d.", + "text" : "

Intro with tags, a < b & c > d.

", + "title" : "Introduction" + }, + "kept" : + { + "class" : "", + "elementType" : "p", + "maxWidth" : "15cm", + "name" : "kept", + "rawtext" : "depends on hypothesis", + "text" : "

depends on hypothesis

", + "title" : "Kept" + }, + "name" : "", + "prunedTable" : + { + "casesAcrossColumns" : false, + "data" : + [ + { + "v" : 1.0 + }, + { + "v" : 2.0 + }, + { + "v" : 3.0 + } + ], + "footnotes" : [], + "name" : "prunedTable", + "overTitle" : false, + "schema" : + { + "fields" : + [ + { + "name" : "v", + "title" : "v", + "type" : "number" + } + ] + }, + "status" : "complete", + "title" : "Pruned table" + }, + "qml" : + { + "sourceID" : "priorWidthSlider" + }, + "report" : + { + "analysisId" : 42, + "html" : "

Report

A warning worth reporting.

", + "name" : "report", + "rawtext" : "A warning worth reporting.", + "report" : false, + "title" : "hide me", + "topHtml" : "", + "warningIndex" : 0, + "warningsTotal" : 0 + } + }, + "revision" : 7, + "status" : "running", + "typeRequest" : "analysis" +} diff --git a/tests/equivalence/fixtures/golden_saved.json b/tests/equivalence/fixtures/golden_saved.json new file mode 100644 index 00000000..e0051762 --- /dev/null +++ b/tests/equivalence/fixtures/golden_saved.json @@ -0,0 +1,503 @@ +{ + "citations" : [], + "data" : + { + "boxplot" : + { + "aspectRatio" : 1.5, + "citations" : [], + "editOptions" : null, + "environmentName" : "plot_0", + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "export" : null, + "filePathPng" : "state/figures/plot_0.png", + "height" : 320, + "interactive" : false, + "interactiveConvertError" : "", + "interactiveJsonData" : "", + "messages" : [], + "name" : "boxplot", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "resizedByUser" : false, + "revision" : 0, + "status" : "complete", + "title" : "Boxplot", + "type" : "plot", + "width" : 480 + }, + "cache" : + { + "citations" : [], + "environmentName" : "state_0", + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "messages" : [], + "name" : "cache", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "title" : "model cache", + "type" : "state" + }, + "checks" : + { + "citations" : [], + "data" : + { + "homog" : + { + "citations" : [], + "class" : "", + "elementType" : "p", + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "maxWidth" : "15cm", + "messages" : [], + "name" : "homog", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 1, + "rawtext" : "variances are equal", + "text" : "

variances are equal

", + "title" : "Homogeneity", + "type" : "html" + }, + "norm" : + { + "citations" : [], + "class" : "", + "elementType" : "p", + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "maxWidth" : "15cm", + "messages" : [], + "name" : "norm", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 2, + "rawtext" : "normality looks fine", + "text" : "

normality looks fine

", + "title" : "Normality", + "type" : "html" + } + }, + "data_order" : + { + "homog" : 1, + "norm" : 0 + }, + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "initCollapsed" : true, + "messages" : [], + "name" : "checks", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "order_increment" : 2, + "position" : 9999, + "title" : "Assumption Checks", + "type" : "container" + }, + "col" : + { + "citations" : [], + "columnName" : "computedCol", + "columnType" : "unknown", + "encoded" : "", + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "messages" : [], + "name" : "col", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "title" : "jaspColumn for computedCol", + "type" : "column" + }, + "desc" : + { + "citations" : [], + "colCombines" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "fields" : {}, + "listType" : "bool", + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rows" : [], + "title" : "", + "type" : "list" + }, + "colFormats" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "fields" : {}, + "listType" : "string", + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rows" : [], + "title" : "colFormats", + "type" : "list" + }, + "colNames" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "fields" : {}, + "listType" : "string", + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rows" : + [ + "num", + "int", + "txt", + "lgl" + ], + "title" : "colNames", + "type" : "list" + }, + "colOvertitles" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "fields" : {}, + "listType" : "string", + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rows" : [], + "title" : "colOvertitles", + "type" : "list" + }, + "colRowCombinations" : [], + "colTitles" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "fields" : {}, + "listType" : "string", + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rows" : + [ + "Numeric", + "Integer", + "Text", + "Logical" + ], + "title" : "colTitles", + "type" : "list" + }, + "colTypes" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "fields" : {}, + "listType" : "string", + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rows" : + [ + "number", + "integer", + "string", + "logical" + ], + "title" : "colTypes", + "type" : "list" + }, + "data" : + [ + [ + 1.5, + 2.5, + "", + 4.5 + ], + [ + 1, + 2, + 3, + 4 + ], + [ + "a < b", + "x & y", + "", + "plain" + ], + [ + true, + false, + true, + "" + ] + ], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "expectedColumnCount" : 0, + "expectedRowCount" : 0, + "footnotes" : + [ + { + "cols" : + [ + "txt" + ], + "rows" : + [ + "r2" + ], + "symbol" : "#", + "text" : "cell note" + }, + { + "cols" : + [ + "num" + ], + "rows" : null, + "symbol" : "*", + "text" : "column note" + }, + { + "cols" : null, + "rows" : null, + "symbol" : "", + "text" : "table-level note" + } + ], + "messages" : [], + "name" : "desc", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rowNames" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "fields" : {}, + "listType" : "string", + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rows" : + [ + "r1", + "r2", + "r3", + "r4" + ], + "title" : "rowNames", + "type" : "list" + }, + "rowTitles" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "fields" : {}, + "listType" : "string", + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rows" : [], + "title" : "rowTitles", + "type" : "list" + }, + "showSpecifiedColumnsOnly" : false, + "specifiedColumns" : [], + "status" : "complete", + "title" : "Descriptives", + "transposeTable" : false, + "transposeWithOvertitle" : false, + "type" : "table" + }, + "intro" : + { + "citations" : + [ + "JASP Team (2026). Golden baseline." + ], + "class" : "", + "elementType" : "h2", + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "maxWidth" : "15cm", + "messages" : + [ + "a message that only shows in convertToJSON" + ], + "name" : "intro", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rawtext" : "Intro with tags, a < b & c > d.", + "text" : "

Intro with tags, a < b & c > d.

", + "title" : "Introduction", + "type" : "html" + }, + "qml" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : false, + "json" : + { + "changed" : true, + "value" : 0.70699999999999996 + }, + "messages" : [], + "name" : "qml", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "sourceID" : "priorWidthSlider", + "title" : "", + "type" : "qmlSource" + }, + "report" : + { + "citations" : [], + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "messages" : [], + "name" : "report", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "position" : 9999, + "rawtext" : "A warning worth reporting.", + "report" : false, + "title" : "Report", + "type" : "report", + "warningIndex" : 0, + "warnings" : 0 + } + }, + "data_order" : + { + "boxplot" : 3, + "cache" : 4, + "checks" : 2, + "col" : 6, + "desc" : 1, + "intro" : 0, + "qml" : 7, + "report" : 5 + }, + "error" : false, + "errorMessage" : "", + "escapeHtml" : true, + "initCollapsed" : false, + "messages" : [], + "name" : "", + "nestedOptionMustBe" : {}, + "nestedOptionMustContain" : {}, + "optionMustBe" : {}, + "optionMustContain" : {}, + "options" : + { + "hypothesis" : "less", + "pairs" : + [ + { + "a" : "x", + "b" : "y" + } + ], + "priorWidth" : 0.70699999999999996 + }, + "order_increment" : 10, + "position" : 9999, + "relativePathKeep" : "", + "title" : "Golden Baseline", + "type" : "results" +} \ No newline at end of file diff --git a/tests/equivalence/fixtures/moduleFingerprint.txt b/tests/equivalence/fixtures/moduleFingerprint.txt new file mode 100644 index 00000000..5b3f5336 --- /dev/null +++ b/tests/equivalence/fixtures/moduleFingerprint.txt @@ -0,0 +1,1945 @@ +# jaspResults RCPP_MODULE surface fingerprint +# jaspBase version: 0.20.4 + +## module functions +FUN columnDelete + signature: bool columnDelete(std::string) + docstring: +FUN columnExists + signature: bool columnExists(std::string) + docstring: +FUN columnIndexInData + signature: int columnIndexInData(std::string) + docstring: +FUN columnIsMine + signature: bool columnIsMine(std::string) + docstring: +FUN cpp_progressbarTick + signature: void cpp_progressbarTick() + docstring: +FUN cpp_startProgressbar + signature: void cpp_startProgressbar(int, Rcpp::String) + docstring: +FUN create_cpp_jaspColumn + signature: jaspColumn_Interface* create_cpp_jaspColumn(Rcpp::String, Rcpp::RObject_Impl) + docstring: +FUN create_cpp_jaspContainer + signature: jaspContainer_Interface* create_cpp_jaspContainer(Rcpp::String) + docstring: +FUN create_cpp_jaspHtml + signature: jaspHtml_Interface* create_cpp_jaspHtml(Rcpp::String) + docstring: +FUN create_cpp_jaspPlot + signature: jaspPlot_Interface* create_cpp_jaspPlot(Rcpp::String) + docstring: +FUN create_cpp_jaspQmlSource + signature: jaspQmlSource_Interface* create_cpp_jaspQmlSource(Rcpp::String) + docstring: +FUN create_cpp_jaspReport + signature: jaspReport_Interface* create_cpp_jaspReport(Rcpp::String) + docstring: +FUN create_cpp_jaspResults + signature: jaspResults_Interface* create_cpp_jaspResults(Rcpp::String, Rcpp::RObject_Impl) + docstring: +FUN create_cpp_jaspState + signature: jaspState_Interface* create_cpp_jaspState(Rcpp::String) + docstring: +FUN create_cpp_jaspTable + signature: jaspTable_Interface* create_cpp_jaspTable(Rcpp::String) + docstring: +FUN createColumnsCPP + signature: Rcpp::CharacterVector createColumnsCPP(Rcpp::CharacterVector) + docstring: +FUN destroyAllAllocatedObjects + signature: void destroyAllAllocatedObjects() + docstring: +FUN isInsideJASP + signature: bool isInsideJASP() + docstring: +FUN setBaseCitation + signature: void setBaseCitation(std::string) + docstring: +FUN setColumnFuncs + signature: void setColumnFuncs(Rcpp::XPtr, bool), Rcpp::PreserveStorage, &void Rcpp::standard_delete_finalizer, bool)>(bool (**)(std::string, Rcpp::RObject_Impl, bool)), false>, Rcpp::XPtr, bool), Rcpp::PreserveStorage, &void Rcpp::standard_delete_finalizer, bool)>(bool (**)(std::string, Rcpp::RObject_Impl, bool)), false>, Rcpp::XPtr, bool), Rcpp::PreserveStorage, &void Rcpp::standard_delete_finalizer, bool)>(bool (**)(std::string, Rcpp::RObject_Impl, bool)), false>, Rcpp::XPtr(columnType (**)(std::string)), false>, Rcpp::XPtr(int (**)(std::string)), false>, Rcpp::XPtr(int (**)(std::string)), false>, Rcpp::XPtr(std::string (**)(std::string, bool)), false>, Rcpp::XPtr(bool (**)(std::string)), false>, Rcpp::XPtr(bool (**)(std::string)), false>, Rcpp::XPtr(std::string (**)(std::string)), false>, Rcpp::XPtr(std::string (**)(std::string)), false>, Rcpp::XPtr(bool (**)(std::string)), false>, Rcpp::XPtr(bool (**)(std::string)), false>) + docstring: +FUN setDeveloperMode + signature: void setDeveloperMode(bool) + docstring: +FUN setInsideJasp + signature: void setInsideJasp() + docstring: +FUN setJaspLogFunction + signature: void setJaspLogFunction(Rcpp::XPtr(void (**)(std::string const&)), false>) + docstring: +FUN setPollMessagesFunc + signature: void setPollMessagesFunc(Rcpp::XPtr(bool (**)()), false>) + docstring: +FUN setResponseData + signature: void setResponseData(int, int) + docstring: +FUN setSaveLocation + signature: void setSaveLocation(std::string, std::string) + docstring: +FUN setSendFunc + signature: void setSendFunc(Rcpp::XPtr(void (**)(char const*)), false>) + docstring: +FUN setWriteSealLocation + signature: void setWriteSealLocation(std::string, std::string) + docstring: +FUN writeSealFilename + signature: Rcpp::String writeSealFilename() + docstring: + +## classes +CLASS jaspBoollist + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD [[ + nargs: 1 + void: FALSE + const: FALSE + signature: bool [[(Rcpp::RObject_Impl) + docstring: Access element by fieldname (string) or index (int) + METHOD [[<- + nargs: 2 + void: TRUE + const: FALSE + signature: void [[<-(Rcpp::RObject_Impl, bool) + docstring: Insert an element under index (int) or fieldname (string) + METHOD add + nargs: 1 + void: TRUE + const: FALSE + signature: void add(bool) + docstring: Add an element at the end of the indexable list + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD insert + nargs: 2 + void: TRUE + const: FALSE + signature: void insert(Rcpp::RObject_Impl, bool) + docstring: Insert an element under index (int) or fieldname (string) + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspColumn + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setNominal + nargs: 2 + void: FALSE + const: FALSE + signature: bool setNominal(Rcpp::RObject_Impl, bool) + docstring: Overwrite the contents of the specified column with nominal data. + METHOD setNominalText + nargs: 2 + void: FALSE + const: FALSE + signature: bool setNominalText(Rcpp::RObject_Impl, bool) + docstring: Overwrite the contents of the specified column with nominal text data. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD setOrdinal + nargs: 2 + void: FALSE + const: FALSE + signature: bool setOrdinal(Rcpp::RObject_Impl, bool) + docstring: Overwrite the contents of the specified column with ordinal data. + METHOD setScale + nargs: 2 + void: FALSE + const: FALSE + signature: bool setScale(Rcpp::RObject_Impl, bool) + docstring: Overwrite the contents of the specified column with scalar data. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspContainer + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD initCollapsed + cpp_class: bool + docstring: If this is set true the container will be collapsed initially. + FIELD length + cpp_class: int + docstring: Returns how many objects are stored in this container. + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD [[ + nargs: 1 + void: FALSE + const: FALSE + signature: Rcpp::RObject_Impl [[(std::string) + docstring: Retrieve an object from this container as specified under the fieldname. + METHOD [[<- + nargs: 2 + void: TRUE + const: FALSE + signature: void [[<-(std::string, Rcpp::RObject_Impl) + docstring: Insert an object into this container under a fieldname, if this object is a jaspObject and without a title it will get the fieldname as title. + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD findObjectWithUniqueNestedName + nargs: 1 + void: FALSE + const: FALSE + signature: Rcpp::RObject_Impl findObjectWithUniqueNestedName(std::string) + docstring: Find a jasp object from its unique name + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspDoublelist + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD [[ + nargs: 1 + void: FALSE + const: FALSE + signature: double [[(Rcpp::RObject_Impl) + docstring: Access element by fieldname (string) or index (int) + METHOD [[<- + nargs: 2 + void: TRUE + const: FALSE + signature: void [[<-(Rcpp::RObject_Impl, double) + docstring: Insert an element under index (int) or fieldname (string) + METHOD add + nargs: 1 + void: TRUE + const: FALSE + signature: void add(double) + docstring: Add an element at the end of the indexable list + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD insert + nargs: 2 + void: TRUE + const: FALSE + signature: void insert(Rcpp::RObject_Impl, double) + docstring: Insert an element under index (int) or fieldname (string) + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspHtml + docstring: + parents: Rcpp_jaspObject + FIELD class + cpp_class: std::string + docstring: The Css-class of this element, for monospace one could use jasp-code or simply leave it empty. + FIELD elementType + cpp_class: std::string + docstring: The type of this html element, default is 'p' but other useful values include 'H1', 'h2' etc. If you want to write your own html element completely set this to "" + FIELD html + cpp_class: std::string + docstring: The text of this element + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD maxWidth + cpp_class: std::string + docstring: The Css-max-width property. It will be set on a span around your html. + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD text + cpp_class: Rcpp::String + docstring: The text of this element + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspIntlist + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD [[ + nargs: 1 + void: FALSE + const: FALSE + signature: int [[(Rcpp::RObject_Impl) + docstring: Access element by fieldname (string) or index (int) + METHOD [[<- + nargs: 2 + void: TRUE + const: FALSE + signature: void [[<-(Rcpp::RObject_Impl, int) + docstring: Insert an element under index (int) or fieldname (string) + METHOD add + nargs: 1 + void: TRUE + const: FALSE + signature: void add(int) + docstring: Add an element at the end of the indexable list + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD insert + nargs: 2 + void: TRUE + const: FALSE + signature: void insert(Rcpp::RObject_Impl, int) + docstring: Insert an element under index (int) or fieldname (string) + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspObject + docstring: + parents: + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspPlot + docstring: + parents: Rcpp_jaspObject + FIELD aspectRatio + cpp_class: float + docstring: Stores the aspect ratio used to make the plot, will not redraw the plot on change. + FIELD editing + cpp_class: bool + docstring: If set to true will overwrite current png file when rendering a plot. + FIELD export + cpp_class: Rcpp::Vector<19, Rcpp::PreserveStorage> + docstring: Machine-readable context tagged onto this plot. + FIELD filePathPng + cpp_class: std::string + docstring: Stores the filepath to the image-file generated by the plot. + FIELD height + cpp_class: int + docstring: Stores the height used to make the plot, will not redraw the plot on change. + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD interactiveJsonData + cpp_class: std::string + docstring: Returns the relative path to the interactive plotly JSON data file. + FIELD plotObject + cpp_class: Rcpp::RObject_Impl + docstring: Stores the plotObj used to generate the graphic, will (should) be stored in a way that is later accesible to saveImage an editImage. + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD resizedByUser + cpp_class: bool + docstring: If set to true, a user resized the plot and its width and height may be recycled in future runs of this analysis. + FIELD revision + cpp_class: int + docstring: return the current revision of the plot. + FIELD status + cpp_class: std::string + docstring: Stores the status of the plot, default is complete, set to 'running' if it takes a long time to calculate it. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + FIELD width + cpp_class: int + docstring: Stores the width used to make the plot, will not redraw the plot on change. + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspQmlSource + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD sourceID + cpp_class: std::string + docstring: The name of the qml object for which this r-source is meant. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD getValue + nargs: 0 + void: FALSE + const: FALSE + signature: std::string getValue() + docstring: Get json string encoded value + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD setValue + nargs: 1 + void: TRUE + const: FALSE + signature: void setValue(Rcpp::RObject_Impl) + docstring: Set R object to value + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspReport + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD report + cpp_class: bool + docstring: Should a report be sent/made? + FIELD text + cpp_class: Rcpp::String + docstring: The text of this element + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspResultsClass + docstring: + parents: Rcpp_jaspContainer + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD initCollapsed + cpp_class: bool + docstring: If this is set true the container will be collapsed initially. + FIELD length + cpp_class: int + docstring: Returns how many objects are stored in this container. + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD relativePathKeep + cpp_class: std::string + docstring: The relative path to where state is kept + FIELD status + cpp_class: std::string + docstring: The status of the jaspResults object + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD [[ + nargs: 1 + void: FALSE + const: FALSE + signature: Rcpp::RObject_Impl [[(std::string) + docstring: Retrieve an object from this container as specified under the fieldname. + METHOD [[<- + nargs: 2 + void: TRUE + const: FALSE + signature: void [[<-(std::string, Rcpp::RObject_Impl) + docstring: Insert an object into this container under a fieldname, if this object is a jaspObject and without a title it will get the fieldname as title. + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD changeOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void changeOptions(std::string) + docstring: Changes the currently set options and removes all objects that depend on the changed options. Mostly useful for unit tests because this we we can simulate re-running the analysis. Should not be used in an analysis! + METHOD complete + nargs: 0 + void: TRUE + const: FALSE + signature: void complete() + docstring: Constructs the results/response-json and sends it to Desktop but sets status to complete first. + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD findObjectWithUniqueNestedName + nargs: 1 + void: FALSE + const: FALSE + signature: Rcpp::RObject_Impl findObjectWithUniqueNestedName(std::string) + docstring: Find a jasp object from its unique name + METHOD finishWriting + nargs: 0 + void: TRUE + const: FALSE + signature: void finishWriting() + docstring: Set seal for writing + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD getKeepList + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List getKeepList() + docstring: Builds a list of filenames to keep. + METHOD getOtherObjectsForState + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List getOtherObjectsForState() + docstring: Retrieves all non-plot objects to store them in state (currently only jaspState objects) . Makes a list with the envName of the object as name of the element. + METHOD getPlotObjectsForState + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List getPlotObjectsForState() + docstring: Retrieves all plot objects and stores them in a list with the filePath of the plot as name of the element. + METHOD getResults + nargs: 0 + void: FALSE + const: FALSE + signature: std::string getResults() + docstring: Returns the latest version of the results json as a string + METHOD prepareForWriting + nargs: 0 + void: TRUE + const: FALSE + signature: void prepareForWriting() + docstring: Remove seal for writing + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD saveResults + nargs: 0 + void: TRUE + const: FALSE + signature: void saveResults() + docstring: save results + METHOD send + nargs: 0 + void: TRUE + const: FALSE + signature: void send() + docstring: Constructs the results/response-json and sends it to Desktop, but only if jaspResults::setSendFunc was called with an appropriate sendFuncDef first. + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setErrorMessage + nargs: 2 + void: TRUE + const: FALSE + signature: void setErrorMessage(Rcpp::String, std::string) + docstring: Sets an errormessage on the results. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void setOptions(std::string) + docstring: Tells jaspResults which options are currently set, should not be used in an analysis! + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspState + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD object + cpp_class: Rcpp::RObject_Impl + docstring: The object that you might want to keep for the next revision of your analysis. + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspStringlist + docstring: + parents: Rcpp_jaspObject + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD [[ + nargs: 1 + void: FALSE + const: FALSE + signature: std::string [[(Rcpp::RObject_Impl) + docstring: Access element by fieldname (string) or index (int) + METHOD [[<- + nargs: 2 + void: TRUE + const: FALSE + signature: void [[<-(Rcpp::RObject_Impl, std::string) + docstring: Insert an element under index (int) or fieldname (string) + METHOD add + nargs: 1 + void: TRUE + const: FALSE + signature: void add(std::string) + docstring: Add an element at the end of the indexable list + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD insert + nargs: 2 + void: TRUE + const: FALSE + signature: void insert(Rcpp::RObject_Impl, std::string) + docstring: Insert an element under index (int) or fieldname (string) + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. +CLASS jaspTable + docstring: + parents: Rcpp_jaspObject + FIELD colCombines + cpp_class: jaspList_Interface + docstring: List of column combines, single elements can be get and set here directly through [['']] notation but setting all columncombines at once should be done through setColFormats + FIELD colFormats + cpp_class: jaspList_Interface + docstring: List of columnformats, single elements can be get and set here directly through [['']] notation but setting all columnformats at once should be done through setColFormats + FIELD colNames + cpp_class: jaspList_Interface + docstring: List of columnnames, single elements can be get and set here directly through [['']] notation but setting all columnnames at once should be done through setColNames + FIELD colOvertitles + cpp_class: jaspList_Interface + docstring: List of columnovertitles, single elements can be get and set here directly through [['']] notation but setting all columntitles at once should be done through setColTitles. If fieldnames are used to set the title (aka aTable$colOvertitles[['some text']]) then this will override any columntitle set by index for that specific columnname. What this means is that if the first column is named 'a' and you set both "colTitles[[0]] <- 'one'" and "colTitles[['a']] <- 'two'" than the first column will have 'two' as its title if it's name is 'a'. + FIELD colTitles + cpp_class: jaspList_Interface + docstring: List of columntitles, single elements can be get and set here directly through [['']] notation but setting all columntitles at once should be done through setColTitles. If fieldnames are used to set the title (aka aTable$colTitles[['some text']]) then this will override any columntitle set by index for that specific columnname. What this means is that if the first column is named 'a' and you set both "colTitles[[0]] <- 'one'" and "colTitles[['a']] <- 'two'" than the first column will have 'two' as its title if it's name is 'a'. + FIELD colTypes + cpp_class: jaspList_Interface + docstring: List of columntypes, single elements can be get and set here directly through [['']] notation but setting all columntypes at once should be done through setColTypes + FIELD info + cpp_class: Rcpp::String + docstring: Set info aka help MD for this object + FIELD position + cpp_class: int + docstring: Set the position of this object in it's container. By default this is at the end in the order of adding. You can specify any other value, they do not need to be next to each other or unique. The rule is: lower values (including negative) are higher in the container and when multiple objects in a container have the same position-value order is derived from adding-order. + FIELD rowNames + cpp_class: jaspList_Interface + docstring: List of rownames, single elements can be get and set here directly through [['']] notation but setting all rownames at once should be done through setRowNames + FIELD rowTitles + cpp_class: jaspList_Interface + docstring: List of rowtitles, single elements can be get and set here directly through [['']] notation but setting all rowtitles at once should be done through setRowTitles. This will respond in a similar manner to conflicts between an indexed title and a fieldnamed title. See documentation of colTitles. + FIELD showSpecifiedColumnsOnly + cpp_class: bool + docstring: If set to true will make only the specified columns (through addColumnInfo etc) show in the results. + FIELD status + cpp_class: std::string + docstring: The status of the table, usually (and by default) 'complete' + FIELD title + cpp_class: Rcpp::String + docstring: Set the title of this object + FIELD transpose + cpp_class: bool + docstring: If set to true will swap rows and columns in the results. + FIELD transposeWithOvertitle + cpp_class: bool + docstring: If set to true in combination with transpose == true it will use the first column of the data as overtitle. + FIELD type + cpp_class: std::string + docstring: The type of this jaspObject as a string, something like: container, table, plot, json, list, results, html, state + METHOD [[<- + nargs: 2 + void: TRUE + const: FALSE + signature: void [[<-(std::string, Rcpp::RObject_Impl) + docstring: Insert a single column into the table, if a string is used then it will look for an existing column name and set that column with the new data and otherwise will just add it at the end. If it is indexed by integer it will simply set it there. + METHOD addCitation + nargs: 1 + void: TRUE + const: FALSE + signature: void addCitation(Rcpp::String) + docstring: Add a citation to this object + METHOD addColumnInfoHelper + nargs: 6 + void: TRUE + const: FALSE + signature: void addColumnInfoHelper(Rcpp::RObject_Impl, Rcpp::RObject_Impl, Rcpp::RObject_Impl, Rcpp::RObject_Impl, Rcpp::RObject_Impl, Rcpp::RObject_Impl) + docstring: addColumnInfoHelper(name=NULL, title=NULL, type=NULL, format=NULL, combine=NULL) -> Adds column info, an entry to columName wether you specify it or not and if the others are not NULL then they are set for the column + METHOD addColumns + nargs: 1 + void: TRUE + const: FALSE + signature: void addColumns(Rcpp::RObject_Impl) + docstring: Add one or more columns to the object, this class accepts the same datatypes as setdata. Column- and rownames will be extracted as well but used only if the corresponding names aren't set yet. + METHOD addFootnoteHelper + nargs: 4 + void: TRUE + const: FALSE + signature: void addFootnoteHelper(Rcpp::RObject_Impl, Rcpp::RObject_Impl, Rcpp::RObject_Impl, Rcpp::RObject_Impl) + docstring: addFootnoteHelper(message=, symbol=NULL, column=NULL, row=NULL) === Add a footnote to the table, if column or row is not -1 it will be added to the specified column or row, if both are changed then it will be a footnote on a cell and otherwise it will just be a footnote of the entire table. A symbol may also be specified. + METHOD addMessage + nargs: 1 + void: TRUE + const: FALSE + signature: void addMessage(Rcpp::String) + docstring: Add a message to this object + METHOD addRow + nargs: 2 | 1 + void: TRUE | TRUE + const: FALSE | FALSE + signature: void addRow(Rcpp::RObject_Impl, Rcpp::CharacterVector) + signature: void addRow(Rcpp::RObject_Impl) + docstring: Add a row to the table, where 'rows' is a list (of values) or vector. Before the data is added all existing columns will be made the same length by appending null-values. If the new data contains more columns than currently present empty columns will be added. Columnnames will be extracted and used to place the data in the correct column, they can be specified through the elementnames of a list, names of a data.frame and colnames of a matrix. To also set the rownames you can fill pass a characterVector with the desired names in the second argument. + docstring: Add a row to the table, where 'rows' is a list (of values) or vector. Before the data is added all existing columns will be made the same length by appending null-values. If the new data contains more columns than currently present empty columns will be added. Columnnames will be extracted and used to place the data in the correct column, they can be specified through the elementnames of a list, names of a data.frame and colnames of a matrix. To also set the rownames you can fill pass a characterVector with the desired names in the second argument. + METHOD addRows + nargs: 2 | 1 + void: TRUE | TRUE + const: FALSE | FALSE + signature: void addRows(Rcpp::RObject_Impl, Rcpp::CharacterVector) + signature: void addRows(Rcpp::RObject_Impl) + docstring: Add rows to the table, where 'rows' is a list (of rows), dataframe or matrix. Before the data is added all existing columns will be made the same length by appending null-values. If the new data contains more columns than currently present empty columns will be added. Columnnames will be extracted and used to place the data in the correct column, they can be specified through the elementnames of a list, names of a data.frame and colnames of a matrix. To also set the rownames you can fill pass a characterVector with the desired names in the second argument. + docstring: Add rows to the table, where 'rows' is a list (of rows), dataframe or matrix. Before the data is added all existing columns will be made the same length by appending null-values. If the new data contains more columns than currently present empty columns will be added. Columnnames will be extracted and used to place the data in the correct column, they can be specified through the elementnames of a list, names of a data.frame and colnames of a matrix. To also set the rownames you can fill pass a characterVector with the desired names in the second argument. + METHOD copyDependenciesFromJaspObject + nargs: 1 + void: TRUE + const: FALSE + signature: void copyDependenciesFromJaspObject(jaspObject_Interface*) + docstring: Will make the object depend on whatever the other jaspObject depends. + METHOD dependOnNestedOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnNestedOptions(Rcpp::CharacterVector) + docstring: Same as dependOnOptions but the input vector is treated as a vector of keys that indicate a nested option. + METHOD dependOnOptions + nargs: 1 + void: TRUE + const: FALSE + signature: void dependOnOptions(Rcpp::CharacterVector) + docstring: Will make the object depend on the current values of the options specified in the charactervector. + METHOD getError + nargs: 0 + void: FALSE + const: FALSE + signature: bool getError() + docstring: Get the error status of this object. + METHOD print + nargs: 0 + void: TRUE + const: FALSE + signature: void print() + docstring: Prints the contents of the jaspObject + METHOD printHtml + nargs: 0 + void: TRUE + const: FALSE + signature: void printHtml() + docstring: Prints the contents of the jaspObject nicely formatted as html + METHOD setColCombiness + nargs: 1 + void: TRUE + const: FALSE + signature: void setColCombiness(Rcpp::List) + docstring: Accepts a list of logicals to be used as columncombines, if the elements are named they will be accessible later through fieldname. + METHOD setColFormats + nargs: 1 + void: TRUE + const: FALSE + signature: void setColFormats(Rcpp::List) + docstring: Accepts a list of strings to be used as columnformats, if the elements are named they will be accessible later through fieldname. + METHOD setColNames + nargs: 1 + void: TRUE + const: FALSE + signature: void setColNames(Rcpp::List) + docstring: Accepts a list of strings to be used as columnnames, if the elements are named they will be accessible later through fieldname. + METHOD setColOvertitles + nargs: 1 + void: TRUE + const: FALSE + signature: void setColOvertitles(Rcpp::List) + docstring: Accepts a list of strings to be used as columnovertitles, if the elements are named they will be accessible later through fieldname. + METHOD setColTitles + nargs: 1 + void: TRUE + const: FALSE + signature: void setColTitles(Rcpp::List) + docstring: Accepts a list of strings to be used as columntitles, if the elements are named they will be accessible later through fieldname. + METHOD setColTypes + nargs: 1 + void: TRUE + const: FALSE + signature: void setColTypes(Rcpp::List) + docstring: Accepts a list of strings to be used as columntypes, if the elements are named they will be accessible later through fieldname. + METHOD setData + nargs: 1 + void: TRUE + const: FALSE + signature: void setData(Rcpp::RObject_Impl) + docstring: Set the data of the table, this accepts lists, dataframes, matrices and vectors. If any column- or rownames are specified they are set on the object, but only if they aren't set yet. Any one-dimensional data given will be assumed to be a row. + METHOD setError + nargs: 1 + void: TRUE + const: FALSE + signature: void setError(Rcpp::String) + docstring: Set an error message on this object that which be shown in JASP. Errors set on jaspContainers or jaspResults are propagated to children, such that the first child shows the error and the others are greyed out. + METHOD setExpectedColumns + nargs: 1 + void: TRUE + const: FALSE + signature: void setExpectedColumns(unsigned long) + docstring: Set the expected size of this table to the specified columnCount. It will make your table show up, filled with dots, at this size and as you add data the dots will be replaced with it. + METHOD setExpectedRows + nargs: 1 + void: TRUE + const: FALSE + signature: void setExpectedRows(unsigned long) + docstring: Set the expected size of this table to the specified rowCount. It will make your table show up, filled with dots, at this size and as you add data the dots will be replaced with it. + METHOD setExpectedSize + nargs: 2 + void: TRUE + const: FALSE + signature: void setExpectedSize(unsigned long, unsigned long) + docstring: Set the expected size of this table to the specified columnCount and rowCount. It will make your table show up, filled with dots, at this size and as you add data the dots will be replaced with it. + METHOD setNestedOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setNestedOptionMustContainDependency(Rcpp::CharacterVector, Rcpp::RObject_Impl) + docstring: Same as setOptionMustContainDependency but the input vector is treated as a vector of keys that indicate a nested option. + METHOD setOptionMustBeDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustBeDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option and it's required value, if the analysis is restarted and this option is no longer defined (like that) it will automatically destroy the object. Otherwise it will keep it. + METHOD setOptionMustContainDependency + nargs: 2 + void: TRUE + const: FALSE + signature: void setOptionMustContainDependency(std::string, Rcpp::RObject_Impl) + docstring: Specifies an option that should define an array and a required value that should be in it, if the analysis is restarted and this option is no longer defined or no longer contains the specified value it will automatically destroy the object. Otherwise it will keep it. + METHOD setRowNames + nargs: 1 + void: TRUE + const: FALSE + signature: void setRowNames(Rcpp::List) + docstring: Accepts a list of strings to be used as rownames, if the elements are named they will be accessible later through fieldname. + METHOD setRowTitles + nargs: 1 + void: TRUE + const: FALSE + signature: void setRowTitles(Rcpp::List) + docstring: Accepts a list of strings to be used as rowtitles, if the elements are named they will be accessible later through fieldname. + METHOD toHtml + nargs: 0 + void: FALSE + const: FALSE + signature: std::string toHtml() + docstring: gives a string with the contents of the jaspObject nicely formatted as html + METHOD toRObject + nargs: 0 + void: FALSE + const: FALSE + signature: Rcpp::List toRObject() + docstring: convert this jaspResults object (and possibly it's children) to R objects. diff --git a/tests/equivalence/fixtures/stateStore_fingerprint.txt b/tests/equivalence/fixtures/stateStore_fingerprint.txt new file mode 100644 index 00000000..4b3dfeb7 --- /dev/null +++ b/tests/equivalence/fixtures/stateStore_fingerprint.txt @@ -0,0 +1,10 @@ +pre-results state roundtrip identical: TRUE +fresh plot dims: 0x0 aspectRatio=0 +state roundtrip identical: TRUE +state print says object stored yes: TRUE +empty state print says object stored no: TRUE +harvest names: state_1,state_2 +harvest length: 2 +harvest state_1 identical to val: TRUE +harvest state_2 identical to vec: TRUE +keep length: 3 diff --git a/tests/equivalence/fixtures/toRObject_fingerprint.txt b/tests/equivalence/fixtures/toRObject_fingerprint.txt new file mode 100644 index 00000000..527e2d99 --- /dev/null +++ b/tests/equivalence/fixtures/toRObject_fingerprint.txt @@ -0,0 +1,78 @@ +OBJECT intro (type=html) + class=jaspHtmlWrapper,jaspWrapper length=5 names=rawtext,text,class,maxWidth,elementType + title=Introduction + [rawtext] + class=character values=Intro with tags. + [text] + class=character values=

Intro with tags.

+ [class] + class=character values= + [maxWidth] + class=character values=15cm + [elementType] + class=character values=p + +OBJECT desc (type=table) + class=jaspTableWrapper,jaspWrapper,data.frame nrow=3 ncol=4 + columns: num:numeric | int:integer | txt:character | lgl:logical + has footnotes attr + title=Descriptives + +OBJECT inner (type=container) + class=jaspContainerWrapper,jaspWrapper length=1 names=nested + title=Inner + [nested] + class=jaspHtmlWrapper,jaspWrapper length=5 names=rawtext,text,class,maxWidth,elementType + title=Nested + [rawtext] + class=character values=nested + [text] + class=character values=

nested

+ [class] + class=character values= + [maxWidth] + class=character values=15cm + [elementType] + class=character values=p + +OBJECT report (type=report) + class=list length=0 + +OBJECT results (type=results) + class=jaspContainerWrapper,jaspWrapper length=3 names=intro,desc,inner + title=toRObject baseline + [intro] + class=jaspHtmlWrapper,jaspWrapper length=5 names=rawtext,text,class,maxWidth,elementType + title=Introduction + [rawtext] + class=character values=Intro with tags. + [text] + class=character values=

Intro with tags.

+ [class] + class=character values= + [maxWidth] + class=character values=15cm + [elementType] + class=character values=p + [desc] + class=jaspTableWrapper,jaspWrapper,data.frame nrow=3 ncol=4 + columns: num:numeric | int:integer | txt:character | lgl:logical + has footnotes attr + title=Descriptives + [inner] + class=jaspContainerWrapper,jaspWrapper length=1 names=nested + title=Inner + [nested] + class=jaspHtmlWrapper,jaspWrapper length=5 names=rawtext,text,class,maxWidth,elementType + title=Nested + [rawtext] + class=character values=nested + [text] + class=character values=

nested

+ [class] + class=character values= + [maxWidth] + class=character values=15cm + [elementType] + class=character values=p + diff --git a/tests/equivalence/goldenBaseline.R b/tests/equivalence/goldenBaseline.R new file mode 100644 index 00000000..76e73ceb --- /dev/null +++ b/tests/equivalence/goldenBaseline.R @@ -0,0 +1,138 @@ +#!/usr/bin/env Rscript +# Golden baseline generator for the core/adapters refactor (Phase 1 gate). +# +# Builds a rich jaspResults tree exercising every object type and both JSON +# serialisation paths, then writes: +# fixtures/golden_response.json <- results$getResults() (dataEntry/meta tree) +# fixtures/golden_saved.json <- saveResults() file (convertToJSON tree) +# +# After any refactor, rebuild jaspBase, re-run this script into a scratch dir and +# diff against the committed fixtures: they must be byte-identical. +# +# Usage: +# JASP_EQUIV_RLIB=/path/to/lib Rscript goldenBaseline.R [outdir] +# (outdir defaults to tests/equivalence/fixtures) + +if (Sys.getenv("JASP_EQUIV_RLIB") != "") + .libPaths(c(Sys.getenv("JASP_EQUIV_RLIB"), .libPaths())) + +suppressPackageStartupMessages(library(jaspBase)) + +ns <- asNamespace("jaspBase") +get0 <- function(n) get(n, envir=ns) + +outdir <- { + args <- commandArgs(trailingOnly=TRUE) + if (length(args) >= 1) args[[1]] else file.path(getwd(), "tests", "equivalence", "fixtures") +} +dir.create(outdir, showWarnings=FALSE, recursive=TRUE) + +get0("setDeveloperMode")(FALSE) + +results <- get0("create_cpp_jaspResults")("Golden Baseline", NULL) +get0("setResponseData")(42L, 7L) +results$setOptions('{"hypothesis":"twoSided","priorWidth":0.707,"pairs":[{"a":"x","b":"y"}]}') + +# --- html: escaping, citations, messages ------------------------------------- +h <- get0("create_cpp_jaspHtml")("Intro with tags, a < b & c > d.") +h$title <- "Introduction" +h$info <- "some help text" +h$elementType <- "h2" +h$addCitation("JASP Team (2026). Golden baseline.") +h$addMessage("a message that only shows in convertToJSON") +results[["intro"]] <- h + +# --- table: many column types, names, titles, footnotes ---------------------- +t <- get0("create_cpp_jaspTable")("Descriptives") +t$setData(list( + num = c(1.5, 2.5, NA, 4.5), + int = c(1L, 2L, 3L, 4L), + txt = c("a < b", "x & y", NA, "plain"), + lgl = c(TRUE, FALSE, TRUE, NA) +)) +t$setRowNames(list("r1", "r2", "r3", "r4")) +t$setColTitles(list("Numeric", "Integer", "Text", "Logical")) +t$setColTypes(list("number", "integer", "string", "logical")) +t$status <- "complete" +t$addFootnoteHelper("table-level note", NULL, NULL, NULL) +t$addFootnoteHelper("column note", "*", "num", NULL) +t$addFootnoteHelper("cell note", "#", "txt", "r2") +results[["desc"]] <- t + +# --- nested container with positions + collapsed ----------------------------- +cont <- get0("create_cpp_jaspContainer")("Assumption Checks") +cont$initCollapsed <- TRUE + +h2 <- get0("create_cpp_jaspHtml")("normality looks fine") +h2$title <- "Normality" +h2$position <- 2L +cont[["norm"]] <- h2 + +h3 <- get0("create_cpp_jaspHtml")("variances are equal") +h3$title <- "Homogeneity" +h3$position <- 1L +cont[["homog"]] <- h3 + +results[["checks"]] <- cont + +# --- plot -------------------------------------------------------------------- +p <- get0("create_cpp_jaspPlot")("Boxplot") +p$width <- 480L +p$height <- 320L +p$aspectRatio <- 1.5 +p$status <- "complete" +p$filePathPng <- "state/figures/plot_0.png" +results[["boxplot"]] <- p + +# --- state ------------------------------------------------------------------- +st <- get0("create_cpp_jaspState")("model cache") +st$object <- list(coef=1.23, se=0.1, nested=list(a=1L, b="two")) +results[["cache"]] <- st + +# --- report ------------------------------------------------------------------ +rep <- get0("create_cpp_jaspReport")("A warning worth reporting.", TRUE) +rep$title <- "Report" +results[["report"]] <- rep + +# --- column (no column funcs registered -> deterministic stand-alone path) --- +col <- get0("create_cpp_jaspColumn")("computedCol", FALSE) +results[["col"]] <- col + +# --- qmlSource --------------------------------------------------------------- +q <- get0("create_cpp_jaspQmlSource")("priorWidthSlider") +q$setValue(list(changed=TRUE, value=0.707)) +results[["qml"]] <- q + +# --- dependency that survives, and one that gets pruned by changeOptions ----- +keep <- get0("create_cpp_jaspHtml")("depends on hypothesis") +keep$title <- "Kept" +keep$dependOnOptions(c("hypothesis")) +results[["kept"]] <- keep + +# a dependent TABLE: when pruned, old-results merge runs letChildrenRun() which +# flips its status complete -> running, making the prune visibly observable. +ptable <- get0("create_cpp_jaspTable")("Pruned table") +ptable$setData(list(v = c(1.0, 2.0, 3.0))) +ptable$status <- "complete" +ptable$dependOnOptions(c("hypothesis")) +results[["prunedTable"]] <- ptable + +responseJson <- results$getResults() + +# --- option change -> dependency pruning + old-results merge ----------------- +results$changeOptions('{"hypothesis":"less","priorWidth":0.707,"pairs":[{"a":"x","b":"y"}]}') +prunedJson <- results$getResults() + +# --- saved-state path (convertToJSON) --------------------------------------- +tmpRoot <- tempfile("goldenSave") +dir.create(tmpRoot) +get0("setSaveLocation")(tmpRoot, "jaspResults.json") +results$saveResults() +savedJson <- paste(readLines(file.path(tmpRoot, "jaspResults.json"), warn=FALSE), collapse="\n") + +writeLines(responseJson, file.path(outdir, "golden_response.json"), sep="") +writeLines(prunedJson, file.path(outdir, "golden_pruned.json"), sep="") +writeLines(savedJson, file.path(outdir, "golden_saved.json"), sep="") + +cat("wrote golden_response.json (", nchar(responseJson), "), golden_pruned.json (", + nchar(prunedJson), "), golden_saved.json (", nchar(savedJson), "bytes ) to", outdir, "\n") diff --git a/tests/equivalence/moduleFingerprint.R b/tests/equivalence/moduleFingerprint.R new file mode 100644 index 00000000..f6b95319 --- /dev/null +++ b/tests/equivalence/moduleFingerprint.R @@ -0,0 +1,106 @@ +#!/usr/bin/env Rscript +# Dumps the complete RCPP_MODULE(jaspResults) surface of the loaded jaspBase: +# module functions, classes, methods, properties — names, signatures, docstrings. +# +# Used as a fingerprint to guarantee the R-visible API stays bit-identical +# across the core/adapters refactor (see tmp/plan-python-interface.md, Phase 1). +# +# Usage: +# JASP_EQUIV_RLIB=/path/to/lib Rscript moduleFingerprint.R [outfile] +# (JASP_EQUIV_RLIB optional; prepended to .libPaths so a freshly built jaspBase +# can be fingerprinted instead of the system-wide installation.) + +if (Sys.getenv("JASP_EQUIV_RLIB") != "") + .libPaths(c(Sys.getenv("JASP_EQUIV_RLIB"), .libPaths())) + +suppressPackageStartupMessages(library(jaspBase)) + +out <- character() +emit <- function(...) out <<- c(out, paste0(...)) + +norm <- function(x) +{ + if (is.null(x) || length(x) == 0) return("") + x <- paste(x, collapse=" | ") + # demangle libc++/libstdc++ std::string so fixtures are toolchain-stable + x <- gsub("std::__1::basic_string, std::__1::allocator >", "std::string", x, fixed=TRUE) + x <- gsub("std::__1::basic_string, std::__1::allocator>", "std::string", x, fixed=TRUE) + x <- gsub("std::__cxx11::basic_string, std::allocator >", "std::string", x, fixed=TRUE) + x <- gsub("std::__cxx11::basic_string, std::allocator>", "std::string", x, fixed=TRUE) + gsub("\\s+", " ", trimws(x)) +} + +envData <- function(obj) +{ + # C++OverloadedMethods / C++Field keep their data in a .xData environment + if (isS4(obj) && ".xData" %in% slotNames(obj)) + return(slot(obj, ".xData")) + NULL +} + +xdGet <- function(env, name) +{ + if (!is.null(env) && exists(name, envir=env, inherits=FALSE)) get(name, envir=env) else NULL +} + +ns <- asNamespace("jaspBase") +module <- get(".__Mod__jaspResults", envir=ns) +storage <- get("storage", envir=slot(module, ".xData")) + +emit("# jaspResults RCPP_MODULE surface fingerprint") +emit("# jaspBase version: ", as.character(packageVersion("jaspBase"))) +emit("") +emit("## module functions") + +for (name in sort(ls(storage))) +{ + obj <- get(name, envir=storage) + if (!inherits(obj, "C++Function")) next + emit("FUN ", name) + emit(" signature: ", norm(attr(obj, "signature"))) + emit(" docstring: ", norm(attr(obj, "docstring"))) +} + +emit("") +emit("## classes") + +for (name in sort(ls(storage))) +{ + obj <- get(name, envir=storage) + if (!inherits(obj, "C++Class")) next + + emit("CLASS ", name) + emit(" docstring: ", norm(slot(obj, "docstring"))) + emit(" parents: ", paste(sort(slot(obj, "parents")), collapse=", ")) + + fields <- slot(obj, "fields") + for (fname in sort(names(fields))) + { + fenv <- envData(fields[[fname]]) + emit(" FIELD ", fname) + emit(" cpp_class: ", norm(xdGet(fenv, "cpp_class"))) + emit(" docstring: ", norm(xdGet(fenv, "docstring"))) + } + + methods <- slot(obj, "methods") + for (mname in sort(names(methods))) + { + menv <- envData(methods[[mname]]) + emit(" METHOD ", mname) + emit(" nargs: ", norm(xdGet(menv, "nargs"))) + emit(" void: ", norm(xdGet(menv, "void"))) + emit(" const: ", norm(xdGet(menv, "const"))) + for (sig in xdGet(menv, "signatures")) emit(" signature: ", norm(sig)) + for (doc in xdGet(menv, "docstrings")) emit(" docstring: ", norm(doc)) + } +} + +result <- paste(out, collapse="\n") + +args <- commandArgs(trailingOnly=TRUE) +if (length(args) >= 1) +{ + writeLines(result, args[[1]]) + cat("fingerprint written to", args[[1]], "(", length(out), "lines )\n") +} else + cat(result, "\n") diff --git a/tests/equivalence/runGate.sh b/tests/equivalence/runGate.sh new file mode 100755 index 00000000..ee316eaa --- /dev/null +++ b/tests/equivalence/runGate.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Phase-1 equivalence gate. +# +# Rebuilds jaspBase from the CURRENT working tree into a temp library, regenerates +# the module fingerprint + golden JSONs, and diffs them against the committed +# fixtures in tests/equivalence/fixtures/. Exits non-zero on any difference. +# +# tests/equivalence/runGate.sh # full: rebuild + compare +# JASP_EQUIV_RLIB=/existing/lib tests/equivalence/runGate.sh --skip-build +# +# The fingerprint must be byte-identical; the goldens must be byte-identical. +set -euo pipefail + +EQ_DIR="$(cd "$(dirname "$0")" && pwd)" +PKG_ROOT="$(cd "$EQ_DIR/../.." && pwd)" +RLIB="${JASP_EQUIV_RLIB:-/tmp/opencode/jaspBaseLib}" +SCRATCH="$(mktemp -d /tmp/jaspEquiv.XXXXXX)" +trap 'rm -rf "$SCRATCH"' EXIT + +if [ "${1:-}" != "--skip-build" ]; then + echo "==> staging Common + installing jaspBase (working tree) into $RLIB" + mkdir -p "$PKG_ROOT/inst/include" + rm -rf "$PKG_ROOT/inst/include/Common" + cp -R "$PKG_ROOT/../../Common" "$PKG_ROOT/inst/include/Common" + # R's make does not track header dependencies, so stale .o files survive + # header-only changes (vtable/layout drift); build from scratch every time. + find "$PKG_ROOT/src" \( -name '*.o' -o -name '*.so' \) -delete + mkdir -p "$RLIB" + ( cd "$PKG_ROOT" && R CMD INSTALL --library="$RLIB" \ + --configure-vars="INCLUDE_DIR=$PKG_ROOT/inst/include/Common" . >/dev/null ) +fi + +export JASP_EQUIV_RLIB="$RLIB" + +echo "==> regenerating fingerprint + goldens into $SCRATCH" +Rscript "$EQ_DIR/moduleFingerprint.R" "$SCRATCH/moduleFingerprint.txt" >/dev/null +Rscript "$EQ_DIR/goldenBaseline.R" "$SCRATCH" >/dev/null +Rscript "$EQ_DIR/toRObjectBaseline.R" "$SCRATCH/toRObject_fingerprint.txt" >/dev/null +Rscript "$EQ_DIR/stateStoreBaseline.R" "$SCRATCH/stateStore_fingerprint.txt" >/dev/null + +echo "==> comparing against committed fixtures" +fail=0 +for f in moduleFingerprint.txt golden_response.json golden_pruned.json golden_saved.json toRObject_fingerprint.txt stateStore_fingerprint.txt; do + if cmp -s "$EQ_DIR/fixtures/$f" "$SCRATCH/$f"; then + echo " $f IDENTICAL" + else + echo " $f DIFFER:" + diff "$EQ_DIR/fixtures/$f" "$SCRATCH/$f" | head -25 + fail=1 + fi +done + +[ "$fail" -eq 0 ] && echo "GATE PASSED" || { echo "GATE FAILED"; exit 1; } diff --git a/tests/equivalence/runTableGoldens.sh b/tests/equivalence/runTableGoldens.sh new file mode 100755 index 00000000..0ea96ca8 --- /dev/null +++ b/tests/equivalence/runTableGoldens.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Phase-2 golden-table equivalence gate. +# +# Builds the Python extension (if needed), runs the R and Python golden-table +# generators, and byte-compares every produced file. Exits non-zero on any +# difference. +# +# tests/equivalence/runTableGoldens.sh # build + run + compare +# tests/equivalence/runTableGoldens.sh --skip-build +set -euo pipefail + +EQ_DIR="$(cd "$(dirname "$0")" && pwd)" +PKG_ROOT="$(cd "$EQ_DIR/../.." && pwd)" +RLIB="${JASP_EQUIV_RLIB:-/tmp/opencode/jaspBaseLib}" +PY_DIR="$PKG_ROOT/python" +PY_MODULE_DIR="$PY_DIR/build" +SPIKE_PY="$PKG_ROOT/tmp/spike/.venv/bin/python" +SCRATCH="$(mktemp -d /tmp/jaspTableGoldens.XXXXXX)" +trap 'rm -rf "$SCRATCH"' EXIT + +if [ "${1:-}" != "--skip-build" ]; then + echo "==> building python extension into $PY_MODULE_DIR" + cmake -S "$PY_DIR" -B "$PY_MODULE_DIR" -DCMAKE_BUILD_TYPE=Release \ + -Dpybind11_DIR="$("$SPIKE_PY" -m pybind11 --cmakedir)" >/dev/null + cmake --build "$PY_MODULE_DIR" -j >/dev/null +fi + +echo "==> generating R goldens" +JASP_EQUIV_RLIB="$RLIB" Rscript "$EQ_DIR/tableGoldens.R" "$SCRATCH/r" >/dev/null + +echo "==> generating Python goldens" +JASP_PY_MODULE_DIR="$PY_MODULE_DIR" "$SPIKE_PY" "$EQ_DIR/tableGoldens.py" "$SCRATCH/py" >/dev/null + +echo "==> comparing" +fail=0 +for f in "$SCRATCH/r"/*; do + base="$(basename "$f")" + if [ ! -f "$SCRATCH/py/$base" ]; then + echo " $base MISSING on python side" + fail=1 + continue + fi + if cmp -s "$f" "$SCRATCH/py/$base"; then + echo " $base IDENTICAL" + else + echo " $base DIFFER:" + diff "$f" "$SCRATCH/py/$base" | head -20 + fail=1 + fi +done + +# also flag python-only files +for f in "$SCRATCH/py"/*; do + base="$(basename "$f")" + [ -f "$SCRATCH/r/$base" ] || { echo " $base MISSING on R side"; fail=1; } +done + +[ "$fail" -eq 0 ] && echo "TABLE GOLDENS BYTE-IDENTICAL" || { echo "TABLE GOLDEN MISMATCH"; exit 1; } diff --git a/tests/equivalence/stateStoreBaseline.R b/tests/equivalence/stateStoreBaseline.R new file mode 100644 index 00000000..8da4a596 --- /dev/null +++ b/tests/equivalence/stateStoreBaseline.R @@ -0,0 +1,96 @@ +#!/usr/bin/env Rscript +# Object-store round-trip + harvest baseline. +# +# Covers the path the JSON goldens cannot: jaspState objects stored in the +# jaspResults object store (Rcpp::Environment _RStorageEnv), retrieved, and +# harvested by getOtherObjectsForState(). Emits a deterministic text summary +# (identical() results + harvest names) that is byte-compared by runGate.sh. +# +# This is the safety net for migrating the store onto jaspHost (Phase 1, commit +# "wire store + jaspState"). +# +# Usage: +# JASP_EQUIV_RLIB=/path/to/lib Rscript stateStoreBaseline.R [outfile] + +if (Sys.getenv("JASP_EQUIV_RLIB") != "") + .libPaths(c(Sys.getenv("JASP_EQUIV_RLIB"), .libPaths())) + +suppressPackageStartupMessages(library(jaspBase)) + +ns <- asNamespace("jaspBase") +get0 <- function(n) get(n, envir=ns) + +out <- character() +emit <- function(...) out <<- c(out, paste0(...)) + +get0("setDeveloperMode")(FALSE) + +# --- store access before any jaspResults exists ------------------------------ +# Used to dereference a null storage env (R build) / silently vanish. With the +# lazy store + jaspHost default store this must just work and round-trip. +pre <- get0("create_cpp_jaspState")("pre results") +preVal <- list(early=TRUE, n=1L) +pre$object <- preVal +emit("pre-results state roundtrip identical: ", identical(pre$object, preVal)) + +# --- fresh plot defaults (no render pass yet) -------------------------------- +pl <- get0("create_cpp_jaspPlot")("fresh dims") +emit("fresh plot dims: ", pl$width, "x", pl$height, " aspectRatio=", pl$aspectRatio) + +results <- get0("create_cpp_jaspResults")("store baseline", NULL) +get0("setResponseData")(1L, 0L) +results$setOptions('{"a":1}') + +# --- state round-trip -------------------------------------------------------- +st <- get0("create_cpp_jaspState")("cache") +val <- list(coef=1.23, n=42L, tags=c("a","b"), nested=list(x=TRUE)) +st$object <- val +got <- st$object +emit("state roundtrip identical: ", identical(val, got)) + +st2 <- get0("create_cpp_jaspState")("cache2") +vec <- c(1.5, 2.5, 3.5) +st2$object <- vec + +# The "object stored: yes/no" text of jaspState::dataToString goes through +# jaspPrint straight to the process stdout (not capturable inside R), so probe +# it in a subprocess. +rlib <- Sys.getenv("JASP_EQUIV_RLIB") +probe <- sprintf(paste0( + '.libPaths(c("%s", .libPaths())); suppressPackageStartupMessages(library(jaspBase));', + 'ns <- asNamespace("jaspBase"); get0 <- function(n) get(n, envir=ns);', + 'get0("setDeveloperMode")(FALSE);', + 'res <- get0("create_cpp_jaspResults")("probe", NULL);', + 'st <- get0("create_cpp_jaspState")("storedProbe"); st$object <- 42; st$print();', + 'get0("create_cpp_jaspState")("emptyProbe")$print()'), rlib) +printed <- system2("Rscript", c("-e", shQuote(probe)), stdout=TRUE, stderr=TRUE) +emit("state print says object stored yes: ", any(grepl("object stored: yes", printed, fixed=TRUE))) +emit("empty state print says object stored no: ", any(grepl("object stored: no", printed, fixed=TRUE))) + +results[["cache"]] <- st +results[["cache2"]] <- st2 + +# --- harvest other objects for state ---------------------------------------- +# keyed by envName (state_0 is taken by the pre-results object above, so the +# two states here get state_1/state_2 in construction order) +others <- results$getOtherObjectsForState() +emit("harvest names: ", paste(sort(names(others)), collapse=",")) +emit("harvest length: ", length(others)) + +# st was created first (val), st2 second (vec) +emit("harvest state_1 identical to val: ", identical(others[["state_1"]], val)) +emit("harvest state_2 identical to vec: ", identical(others[["state_2"]], vec)) + +# --- keep list --------------------------------------------------------------- +keep <- results$getKeepList() +emit("keep length: ", length(keep)) + +result <- paste(out, collapse="\n") + +args <- commandArgs(trailingOnly=TRUE) +if (length(args) >= 1) +{ + writeLines(result, args[[1]]) + cat("state-store baseline written to", args[[1]], "\n") +} else + cat(result, "\n") diff --git a/tests/equivalence/tableGoldens.R b/tests/equivalence/tableGoldens.R new file mode 100644 index 00000000..d84093c5 --- /dev/null +++ b/tests/equivalence/tableGoldens.R @@ -0,0 +1,169 @@ +#!/usr/bin/env Rscript +# Golden-table matrix generator (R side) for the Phase-2 Python equivalence work. +# +# Each case builds a jaspTable through the RCPP_MODULE ingest path (the same +# code real JASP modules use) and dumps the results JSON + toHtml. The Python +# twin (tableGoldens.py) builds the *same logical table* through the pybind +# adapter; the two outputs must be byte-identical (see runTableGoldens.sh). +# +# Usage: +# JASP_EQUIV_RLIB=/path/to/lib Rscript tableGoldens.R [outdir] + +if (Sys.getenv("JASP_EQUIV_RLIB") != "") + .libPaths(c(Sys.getenv("JASP_EQUIV_RLIB"), .libPaths())) + +suppressPackageStartupMessages(library(jaspBase)) + +ns <- asNamespace("jaspBase") +get0 <- function(n) get(n, envir=ns) + +outdir <- { + args <- commandArgs(trailingOnly=TRUE) + if (length(args) >= 1) args[[1]] else file.path(getwd(), "tests", "equivalence", "tableGoldens") +} +dir.create(outdir, showWarnings=FALSE, recursive=TRUE) + +get0("setDeveloperMode")(FALSE) + +# Each case fills `t` (a fresh jaspTable). The harness wraps it in a jaspResults +# so we exercise the same dataEntry/meta/JSON machinery as production. +runCase <- function(name, buildTable) +{ + get0("destroyAllAllocatedObjects")() + results <- get0("create_cpp_jaspResults")("goldens", NULL) + get0("setResponseData")(7L, 0L) + results$setOptions('{"case":"placeholder"}') + + t <- get0("create_cpp_jaspTable")(name) + buildTable(t) + results[["table"]] <- t + + writeLines(results$getResults(), file.path(outdir, paste0(name, "_results.json"))) + writeLines(t$toHtml(), file.path(outdir, paste0(name, "_toHtml.txt"))) + cat(" R case ok:", name, "\n") +} + +# --- cases ------------------------------------------------------------------- + +runCase("01_numeric_string", function(t) { + t$setData(list(x=c(1.5, NA, Inf), s=c("a columns c1=[1,2,3], c2=[4,5,6]). + m <- matrix(c(1, 2, 3, 4, 5, 6), nrow=3, ncol=2) + colnames(m) <- c("c1", "c2") + t$setData(m) + t$status <- "complete" +}) + +runCase("15_vector_row", function(t) { + t$setData(c(1.5, 2.5, 3.5)) + t$status <- "complete" +}) + +runCase("16_list_of_columns", function(t) { + t$setData(list(a=c(1,2,3), b=c("x","y","z"))) + t$status <- "complete" +}) + +runCase("17_escape_and_citation", function(t) { + t$setData(list(s=c("Tom & Jerry", "