use crate::owner::OwnerState;
use crate::r_api::{c_string, ParseStatus, RApi, Sexp, STRSXP, VECSXP};
use crate::work::{EvalOutput, PrintWork, StructuredError, WorkResult};
const PRINT_EVAL_SOURCE: &str = r#"
.rx_old_options <- list()
tryCatch({
if (!is.null(.rx_width)) {
.rx_old_options$width <- getOption("width")
options(width = as.integer(.rx_width))
}
if (!is.null(.rx_max_print)) {
.rx_old_options$max.print <- getOption("max.print")
options(max.print = as.integer(.rx_max_print))
}
print(.rx_value)
}, finally = {
if (length(.rx_old_options) > 0L) options(.rx_old_options)
})
"#;
pub fn do_print(state: &mut OwnerState, work: &PrintWork) -> WorkResult {
if let Some(failure) = state.terminal_init_failure() {
return WorkResult::NativeInitFailed(failure);
}
if !state.is_initialized() {
return WorkResult::Error("embedded R runtime is not initialized".to_owned());
}
let Some(api) = state.r.as_ref() else {
return WorkResult::Error("embedded R API is not initialized".to_owned());
};
let sexp = match crate::codec::live_resource_sexp(&work.resource) {
Ok(sexp) => sexp,
Err(message) => return WorkResult::Error(message),
};
unsafe { do_print_inner(api, sexp, work) }
}
unsafe fn do_print_inner(api: &RApi, sexp: Sexp, work: &PrintWork) -> WorkResult {
let helper_source =
c_string(PRINT_EVAL_SOURCE).expect("print helper source contains no NUL bytes");
let rx_value = c_string(".rx_value").expect(".rx_value contains no NUL bytes");
let rx_width = c_string(".rx_width").expect(".rx_width contains no NUL bytes");
let rx_max_print = c_string(".rx_max_print").expect(".rx_max_print contains no NUL bytes");
let mut protect_count = 0;
let env = (api.protect)((api.new_env)(*api.global_env, 1, 29));
protect_count += 1;
(api.define_var)((api.install)(rx_value.as_ptr()), sexp, env);
let width = if let Some(width) = work.width {
let width = (api.protect)((api.scalar_integer)(width));
protect_count += 1;
width
} else {
*api.nil_value
};
(api.define_var)((api.install)(rx_width.as_ptr()), width, env);
let max_print = if let Some(max_print) = work.max_print {
let max_print = (api.protect)((api.scalar_integer)(max_print));
protect_count += 1;
max_print
} else {
*api.nil_value
};
(api.define_var)((api.install)(rx_max_print.as_ptr()), max_print, env);
let source_sexp = (api.protect)((api.mk_string)(helper_source.as_ptr()));
protect_count += 1;
let mut parse_status = ParseStatus::Null;
let exprs = (api.protect)((api.parse_vector)(
source_sexp,
-1,
&mut parse_status,
*api.nil_value,
));
protect_count += 1;
if parse_status != ParseStatus::Ok || (api.xlength)(exprs) < 1 {
(api.unprotect)(protect_count);
return WorkResult::Error("R print helper parse failed".to_owned());
}
let helper = match crate::eval::build_eval_helper(api) {
Ok(helper) => helper,
Err(message) => {
(api.unprotect)(protect_count);
return WorkResult::Error(message);
}
};
let call = (api.protect)((api.lang3)(helper, exprs, env));
protect_count += 1;
(api.release_object)(helper);
let mut error_occurred = 0;
let helper_result = (api.protect)((api.try_eval)(call, *api.global_env, &mut error_occurred));
protect_count += 1;
if error_occurred != 0
|| (api.type_of)(helper_result) != VECSXP
|| (api.xlength)(helper_result) < 8
{
(api.unprotect)(protect_count);
return WorkResult::Error("R print failed".to_owned());
}
if crate::eval::helper_result_ok(api, helper_result) {
match output_from_print_helper_result(api, helper_result) {
Ok(output) => {
(api.unprotect)(protect_count);
WorkResult::PrintSuccess(output)
}
Err(message) => {
(api.unprotect)(protect_count);
WorkResult::Error(message)
}
}
} else {
let error = match structured_print_error_from_helper_result(api, helper_result) {
Ok(error) => error,
Err(message) => {
(api.unprotect)(protect_count);
return WorkResult::Error(message);
}
};
(api.unprotect)(protect_count);
WorkResult::StructuredError(error)
}
}
unsafe fn structured_print_error_from_helper_result(
api: &RApi,
helper_result: Sexp,
) -> Result<StructuredError, String> {
let mut message =
crate::eval::copy_optional_character(api, (api.vector_elt)(helper_result, 2))?
.unwrap_or_else(|| b"R print failed".to_vec());
if message.is_empty() {
message = b"R print failed".to_vec();
}
Ok(StructuredError {
message,
r_class: crate::eval::copy_character_vector(api, (api.vector_elt)(helper_result, 3))?,
call: crate::eval::copy_optional_character(api, (api.vector_elt)(helper_result, 4))?,
output: output_from_print_helper_result(api, helper_result)?,
})
}
unsafe fn output_from_print_helper_result(
api: &RApi,
helper_result: Sexp,
) -> Result<EvalOutput, String> {
Ok(EvalOutput {
stdout: copy_character_vector_join(api, (api.vector_elt)(helper_result, 5), b"\n")?,
messages: copy_character_vector_join(api, (api.vector_elt)(helper_result, 6), b"")?,
warnings: copy_character_vector_join(api, (api.vector_elt)(helper_result, 7), b"")?,
})
}
unsafe fn copy_character_vector_join(
api: &RApi,
value: Sexp,
separator: &[u8],
) -> Result<Vec<u8>, String> {
if value == *api.nil_value {
return Ok(Vec::new());
}
if (api.type_of)(value) != STRSXP {
return Err("native print helper returned invalid character vector".to_owned());
}
let len = (api.xlength)(value);
let mut output = Vec::new();
for index in 0..len {
if (!output.is_empty() || index > 0) && !separator.is_empty() {
output.extend_from_slice(separator);
}
let string = crate::codec::copy_string(api, (api.string_elt)(value, index))?;
output.extend_from_slice(&string);
}
Ok(output)
}