-
Notifications
You must be signed in to change notification settings - Fork 3
Expose Desktop encoder context for result decoding #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
boutinb
merged 11 commits into
jasp-stats:master
from
FBartos:fix/decode-embedded-result-column-names
Jun 10, 2026
Merged
Changes from 7 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
1ffa343
Decode embedded column names in analysis results
FBartos f7733ab
Expose SyntaxInterface verbose toggle
FBartos 816c758
Accept bridge verbosity levels
FBartos aa1c330
Let verbose parameter control syntax replay defaults
FBartos 3ed55fa
Propagate option-parsing errors from SyntaxInterface to R
boutinb e147751
Decode column names also for no preloadData anlayses
boutinb 1790279
Delegate result decoding to native column decoder
FBartos 2b2d73f
Require native column decoder for token decoding
FBartos b9796f7
Use Desktop encoder context for column decoding
FBartos e506f58
Bump native bridge API version
FBartos 0be0811
Add native column context regression tests
FBartos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| #' Capture a Native Column Decoder | ||
| #' | ||
| #' Captures the current SyntaxInterface `ColumnEncoder` decode mapping in a | ||
| #' serializable object. The captured decoder can be reused after the active | ||
| #' native dataset changes. | ||
| #' | ||
| #' @param columnMapping Optional named character vector mapping encoded column | ||
| #' tokens to decoded user-facing names. This is mainly for tests and for | ||
| #' callers that already captured the mapping before native state changed. | ||
| #' | ||
| #' @return A serializable column decoder object. | ||
| #' | ||
| #' @export | ||
| columnDecoderSnapshot <- function(columnMapping = NULL) { | ||
| if (!is.null(columnMapping)) { | ||
| return(.columnDecoderSnapshotFromMapping(columnMapping)) | ||
| } | ||
|
|
||
| rawSnapshot <- columnDecoderSnapshotNative() | ||
| .columnDecoderSnapshotFromJson(rawSnapshot) | ||
| } | ||
|
|
||
| #' Decode Text With a Native Column Decoder | ||
| #' | ||
| #' Decodes embedded JASP column tokens using SyntaxInterface's native | ||
| #' `ColumnEncoder` replacement rules. | ||
| #' | ||
| #' @param text Character vector to decode. | ||
| #' @param decoderSnapshot Optional decoder returned by `columnDecoderSnapshot()`. | ||
| #' When omitted, the current native bridge decoder is used. | ||
| #' | ||
| #' @return A character vector with native column tokens decoded. | ||
| #' | ||
| #' @export | ||
| decodeColumnText <- function(text, decoderSnapshot = NULL) { | ||
| if (!is.character(text)) { | ||
| stop("`text` must be a character vector", call. = FALSE) | ||
| } | ||
|
|
||
| snapshotJson <- .columnDecoderSnapshotJson(decoderSnapshot) | ||
| decoded <- tryCatch( | ||
| decodeColumnTextNative(text, snapshotJson), | ||
| error = function(e) { | ||
| if (.hasColumnDecoderFallback(decoderSnapshot)) { | ||
| return(.decodeColumnTextFallback(text, decoderSnapshot)) | ||
| } | ||
| stop(e) | ||
| } | ||
| ) | ||
| if (!is.character(decoded) || length(decoded) != length(text)) { | ||
| stop("Native column decoder returned an invalid result.", call. = FALSE) | ||
| } | ||
|
|
||
| names(decoded) <- names(text) | ||
| decoded | ||
| } | ||
|
|
||
| .columnDecoderSnapshotFromMapping <- function(columnMapping) { | ||
| columnMapping <- .validateAnalysisResultColumnMapping(columnMapping) | ||
| if (is.null(columnMapping)) { | ||
| columnMapping <- stats::setNames(character(), character()) | ||
| } | ||
|
|
||
| columns <- unname(Map( | ||
| function(encoded, decoded) list(encoded = encoded, decoded = decoded), | ||
| names(columnMapping), | ||
| unname(columnMapping) | ||
| )) | ||
|
|
||
| rawSnapshot <- as.character(jsonlite::toJSON( | ||
| list(version = 1L, columns = columns), | ||
| auto_unbox = TRUE, | ||
| null = "null" | ||
| )) | ||
|
|
||
| .newColumnDecoderSnapshot(rawSnapshot, columnMapping) | ||
| } | ||
|
|
||
| .columnDecoderSnapshotFromJson <- function(rawSnapshot) { | ||
| if (!is.character(rawSnapshot) || length(rawSnapshot) != 1L || is.na(rawSnapshot)) { | ||
| stop("Native column decoder snapshot must be a single JSON string.", call. = FALSE) | ||
| } | ||
|
|
||
| parsed <- jsonlite::fromJSON(rawSnapshot, simplifyVector = FALSE) | ||
| columns <- parsed[["columns"]] | ||
| if (is.null(columns) || length(columns) == 0L) { | ||
| return(.newColumnDecoderSnapshot( | ||
| rawSnapshot, | ||
| stats::setNames(character(), character()) | ||
| )) | ||
| } | ||
|
|
||
| encoded <- vapply(columns, `[[`, character(1L), "encoded", USE.NAMES = FALSE) | ||
| decoded <- vapply(columns, `[[`, character(1L), "decoded", USE.NAMES = FALSE) | ||
| mapping <- stats::setNames(decoded, encoded) | ||
|
|
||
| .newColumnDecoderSnapshot(rawSnapshot, mapping) | ||
| } | ||
|
|
||
| .newColumnDecoderSnapshot <- function(rawSnapshot, columnMapping) { | ||
| structure( | ||
| list( | ||
| version = 1L, | ||
| columns = .validateAnalysisResultColumnMapping(columnMapping), | ||
| native = rawSnapshot | ||
| ), | ||
| class = "jaspSyntaxColumnDecoder" | ||
| ) | ||
| } | ||
|
|
||
| .columnDecoderSnapshotJson <- function(decoderSnapshot = NULL) { | ||
| if (is.null(decoderSnapshot)) { | ||
| return("") | ||
| } | ||
|
|
||
| if (inherits(decoderSnapshot, "jaspSyntaxColumnDecoder")) { | ||
| return(decoderSnapshot[["native"]]) | ||
| } | ||
|
|
||
| if (is.character(decoderSnapshot) && length(decoderSnapshot) == 1L && !is.na(decoderSnapshot)) { | ||
| return(decoderSnapshot) | ||
| } | ||
|
|
||
| if (is.character(decoderSnapshot) && !is.null(names(decoderSnapshot))) { | ||
| return(.columnDecoderSnapshotFromMapping(decoderSnapshot)[["native"]]) | ||
| } | ||
|
|
||
| stop("`decoderSnapshot` must be a native decoder snapshot or named column mapping.", call. = FALSE) | ||
| } | ||
|
|
||
| .decodeColumnTextWithMapping <- function(text, columnMapping) { | ||
| decodeColumnText(text, .columnDecoderSnapshotFromMapping(columnMapping)) | ||
| } | ||
|
|
||
| .decodeColumnTextFallback <- function(text, decoderSnapshot = NULL) { | ||
| mapping <- .columnDecoderSnapshotMapping(decoderSnapshot) | ||
| if (length(mapping) == 0L) { | ||
| return(text) | ||
| } | ||
|
|
||
| tokens <- names(mapping) | ||
| tokens <- tokens[order(nchar(tokens), decreasing = TRUE)] | ||
| for (token in tokens) { | ||
| text <- gsub(token, unname(mapping[[token]]), text, fixed = TRUE) | ||
| } | ||
|
|
||
| text | ||
| } | ||
|
|
||
| .hasColumnDecoderFallback <- function(decoderSnapshot = NULL) { | ||
| inherits(decoderSnapshot, "jaspSyntaxColumnDecoder") || | ||
| (is.character(decoderSnapshot) && !is.null(names(decoderSnapshot))) | ||
| } | ||
|
|
||
| .columnDecoderSnapshotMapping <- function(decoderSnapshot = NULL) { | ||
| if (inherits(decoderSnapshot, "jaspSyntaxColumnDecoder")) { | ||
| return(.validateAnalysisResultColumnMapping(decoderSnapshot[["columns"]])) | ||
| } | ||
|
|
||
| if (is.character(decoderSnapshot) && !is.null(names(decoderSnapshot))) { | ||
| return(.validateAnalysisResultColumnMapping(decoderSnapshot)) | ||
| } | ||
|
|
||
| stats::setNames(character(), character()) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| .normalizeVerboseParameter <- function(value) { | ||
| if (is.null(value) || length(value) == 0L || is.na(value[[1L]])) | ||
| stop("`verbose` must be one of 'all', 'analysis', 'jasp', 'none', TRUE, or FALSE.", call. = FALSE) | ||
|
|
||
| value <- value[[1L]] | ||
| if (is.logical(value)) | ||
| return(if (isTRUE(value)) "all" else "analysis") | ||
|
|
||
| if (is.character(value)) { | ||
| value <- tolower(trimws(value)) | ||
| if (value %in% c("true", "yes", "on", "1")) | ||
| return("all") | ||
| if (value %in% c("false", "no", "off", "0")) | ||
| return("analysis") | ||
| if (value %in% c("all", "analysis", "jasp", "none")) | ||
| return(value) | ||
| } | ||
|
|
||
| stop("`verbose` must be one of 'all', 'analysis', 'jasp', 'none', TRUE, or FALSE.", call. = FALSE) | ||
| } | ||
|
|
||
| .verboseParameterShowsNativeOutput <- function(verbose) { | ||
| verbose %in% c("all", "jasp") | ||
| } | ||
|
|
||
| #' @export | ||
| setParameter <- function(name, value) { | ||
| if (identical(as.character(name), "verbose")) { | ||
| verbose <- .normalizeVerboseParameter(value) | ||
| result <- setParameterNative(name, .verboseParameterShowsNativeOutput(verbose)) | ||
| options(jaspSyntax.verbose = verbose) | ||
| return(result) | ||
| } | ||
|
|
||
| setParameterNative(name, value) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good:
decodeColumnTextNativegenuinely call the C++ decoding functionality!Bad:
.decodeColumnTextFallback. This reimplements the functionality in R. Do we even want this function to exist?@FBartos & @boutinb what do you think? If we always create the snapshotJson on the fly, then I think we should be able to guarantee this? IF we can guarantee this then I think the whole fallback idea is somewhat overengineered and also as a liability. If the fallback is triggered, it means we have "broken" the guarantee which should just error. If the fallback is triggered, it does the decoding differently from how JASP does it. That can result in silently incorrect decoding of results, which I find far worse than showing e.g., encoded results as a fallback with a warning or so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I agree, better an error than a wrong result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed with this direction. The fallback is gone now:
decodeColumnText()always dispatches encoded-token decoding to native SyntaxInterface, and native failures are treated as bugs instead of triggering an R replacement path. The old snapshot/mapping contract was replaced with an opaque DesktopColumnEncodercontext, so jaspSyntax carries provenance but does not implement token replacement itself.