use crate::error::{VBError, VBResult};
use crate::state::file;
use crate::value::VBVariant;
pub fn print_statement(file_number: i16, values: &[VBVariant], newline: bool) -> VBResult<()> {
if !(file::MIN_FILE_NUMBER..=file::MAX_FILE_NUMBER).contains(&file_number) {
return Err(VBError::with_description(
52, format!("Bad file name or number: {}", file_number),
));
}
if !file::is_file_open(file_number) {
return Err(VBError::with_description(
52, format!("File not open: #{}", file_number),
));
}
let width = file::with_file_mut(file_number, |file| file.width).unwrap_or(0);
let mut output = String::new();
for value in values.iter() {
match value {
VBVariant::Empty => {
}
VBVariant::Null => {
output.push_str("Null");
}
VBVariant::Boolean(b) => {
output.push_str(if *b { "True" } else { "False" });
}
VBVariant::Long(v) => {
if *v >= 0 {
output.push(' ');
}
output.push_str(&v.to_string());
output.push(' ');
}
VBVariant::Integer(v) => {
if *v >= 0 {
output.push(' ');
}
output.push_str(&v.to_string());
output.push(' ');
}
VBVariant::Byte(v) => {
output.push(' ');
output.push_str(&v.to_string());
output.push(' ');
}
VBVariant::Double(v) => {
if *v >= 0.0 {
output.push(' ');
}
output.push_str(&format_f64(*v));
output.push(' ');
}
VBVariant::Single(v) => {
if *v >= 0.0 {
output.push(' ');
}
output.push_str(&format_f64(*v as f64));
output.push(' ');
}
VBVariant::Currency(v) => {
let formatted = format_currency(*v);
if !formatted.starts_with('-') {
output.push(' ');
}
output.push_str(&formatted);
output.push(' ');
}
VBVariant::Date(v) => {
let formatted = crate::value::date_serial_to_string(*v);
output.push_str(&formatted);
}
VBVariant::String(s) => {
output.push_str(s.as_str());
}
VBVariant::Error(e) => {
output.push_str(&format!("Error {}", e.number));
}
_ => {
return Err(VBError::with_description(
13, "Type mismatch in Print #",
));
}
}
}
if width > 0 && !output.is_empty() {
if output.len() > width as usize {
output.push('\n');
}
}
let char_count = output.chars().count();
if newline {
output.push('\r');
output.push('\n');
}
file::write_file(file_number, output.as_bytes()).map_err(|e| {
VBError::with_description(
57, e.to_string(),
)
})?;
if newline {
file::reset_print_column(file_number);
} else {
file::advance_print_column(file_number, char_count);
}
Ok(())
}
fn format_f64(v: f64) -> String {
if v == v.floor() && v.abs() < 1e15 {
format!("{}", v as i64)
} else {
format!("{}", v)
}
}
fn format_currency(v: i64) -> String {
let sign = if v < 0 { "-" } else { "" };
let abs_val = v.unsigned_abs();
let dollars = abs_val / 10000;
let cents = abs_val % 10000;
format!("{}{}.{:04}", sign, dollars, cents)
}
pub fn print_statement_with_newline(file_number: i16, values: &[VBVariant]) -> VBResult<()> {
print_statement(file_number, values, true)
}
pub fn print_statement_without_newline(file_number: i16, values: &[VBVariant]) -> VBResult<()> {
print_statement(file_number, values, false)
}
pub fn print_blank_line(file_number: i16) -> VBResult<()> {
print_statement(file_number, &[], true)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::file::{self, AccessMode, LockMode, OpenMode};
use vb6core::error::err_number;
#[test]
fn print_writes_string() {
let _guard = crate::state::test_support::lock_test();
let _ = file::close_all_files();
let dir = tempfile::tempdir().unwrap();
file::set_root(dir.path());
let path = std::path::PathBuf::from("test.txt");
file::open_file(
&path,
OpenMode::Output,
AccessMode::Write,
LockMode::Shared,
0,
1,
)
.unwrap();
print_statement(1, &[VBVariant::from_string("Hello")], true).unwrap();
file::close_file(1).unwrap();
let content = std::fs::read_to_string(dir.path().join("test.txt")).unwrap();
assert_eq!(content, "Hello\r\n");
let _ = file::close_all_files();
}
#[test]
fn print_writes_number() {
let _guard = crate::state::test_support::lock_test();
let _ = file::close_all_files();
let dir = tempfile::tempdir().unwrap();
file::set_root(dir.path());
let path = std::path::PathBuf::from("test.txt");
file::open_file(
&path,
OpenMode::Output,
AccessMode::Write,
LockMode::Shared,
0,
1,
)
.unwrap();
print_statement(1, &[VBVariant::Long(42)], true).unwrap();
file::close_file(1).unwrap();
let content = std::fs::read_to_string(dir.path().join("test.txt")).unwrap();
assert_eq!(content, " 42 \r\n");
let _ = file::close_all_files();
}
#[test]
fn print_writes_multiple_values() {
let _guard = crate::state::test_support::lock_test();
let _ = file::close_all_files();
let dir = tempfile::tempdir().unwrap();
file::set_root(dir.path());
let path = std::path::PathBuf::from("test.txt");
file::open_file(
&path,
OpenMode::Output,
AccessMode::Write,
LockMode::Shared,
0,
1,
)
.unwrap();
print_statement(
1,
&[
VBVariant::from_string("Name:"),
VBVariant::from_string("John"),
VBVariant::Long(25),
],
true,
)
.unwrap();
file::close_file(1).unwrap();
let content = std::fs::read_to_string(dir.path().join("test.txt")).unwrap();
assert_eq!(content, "Name:John 25 \r\n");
let _ = file::close_all_files();
}
#[test]
fn print_without_newline() {
let _guard = crate::state::test_support::lock_test();
let _ = file::close_all_files();
let dir = tempfile::tempdir().unwrap();
file::set_root(dir.path());
let path = std::path::PathBuf::from("test.txt");
file::open_file(
&path,
OpenMode::Output,
AccessMode::Write,
LockMode::Shared,
0,
1,
)
.unwrap();
print_statement(1, &[VBVariant::from_string("Hello")], false).unwrap();
print_statement(1, &[VBVariant::from_string(" World")], true).unwrap();
file::close_file(1).unwrap();
let content = std::fs::read_to_string(dir.path().join("test.txt")).unwrap();
assert_eq!(content, "Hello World\r\n");
let _ = file::close_all_files();
}
#[test]
fn print_blank_line_test() {
let _guard = crate::state::test_support::lock_test();
let _ = file::close_all_files();
let dir = tempfile::tempdir().unwrap();
file::set_root(dir.path());
let path = std::path::PathBuf::from("test.txt");
file::open_file(
&path,
OpenMode::Output,
AccessMode::Write,
LockMode::Shared,
0,
1,
)
.unwrap();
print_statement(1, &[VBVariant::from_string("Line 1")], true).unwrap();
super::print_blank_line(1).unwrap();
print_statement(1, &[VBVariant::from_string("Line 3")], true).unwrap();
file::close_file(1).unwrap();
let content = std::fs::read_to_string(dir.path().join("test.txt")).unwrap();
assert_eq!(content, "Line 1\r\n\r\nLine 3\r\n");
let _ = file::close_all_files();
}
#[test]
fn print_writes_boolean() {
let _guard = crate::state::test_support::lock_test();
let _ = file::close_all_files();
let dir = tempfile::tempdir().unwrap();
file::set_root(dir.path());
let path = std::path::PathBuf::from("test.txt");
file::open_file(
&path,
OpenMode::Output,
AccessMode::Write,
LockMode::Shared,
0,
1,
)
.unwrap();
print_statement(1, &[VBVariant::Boolean(true)], true).unwrap();
print_statement(1, &[VBVariant::Boolean(false)], true).unwrap();
file::close_file(1).unwrap();
let content = std::fs::read_to_string(dir.path().join("test.txt")).unwrap();
assert_eq!(content, "True\r\nFalse\r\n");
let _ = file::close_all_files();
}
#[test]
fn print_rejects_invalid_file_number() {
let _guard = crate::state::test_support::lock_test();
let result = print_statement(0, &[VBVariant::from_string("test")], true);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().number,
err_number::BAD_FILE_NAME_OR_NUMBER
);
let _ = file::close_all_files();
}
#[test]
fn print_rejects_closed_file() {
let _guard = crate::state::test_support::lock_test();
let _ = file::close_all_files();
let result = print_statement(1, &[VBVariant::from_string("test")], true);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().number,
err_number::BAD_FILE_NAME_OR_NUMBER
);
let _ = file::close_all_files();
}
}