Skip to main content

native/rx_rust_nif/src/print.rs

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)
}