use crate::diagnostics::{Error as DiagnosticError, Result};
use crate::eval::value::{
Value, PrimitiveProcedure, PrimitiveImpl, ThreadSafeEnvironment,
Port, PortImpl, StandardPort, PortFileHandle
};
use crate::effects::Effect;
use crate::parser::Parser;
use crate::lexer::Lexer;
use std::sync::Arc;
use std::fs::File;
use std::io::{BufReader, BufWriter};
mod current_ports {
use super::*;
use crate::eval::Parameter;
use once_cell::sync::Lazy;
static CURRENT_INPUT_PARAM: Lazy<Arc<Parameter>> = Lazy::new(|| {
let stdin_port = Value::Port(Arc::new(Port::new_standard(StandardPort::Stdin)));
Arc::new(Parameter::with_name(stdin_port, None, "current-input-port".to_string()))
});
static CURRENT_OUTPUT_PARAM: Lazy<Arc<Parameter>> = Lazy::new(|| {
let stdout_port = Value::Port(Arc::new(Port::new_standard(StandardPort::Stdout)));
Arc::new(Parameter::with_name(stdout_port, None, "current-output-port".to_string()))
});
static CURRENT_ERROR_PARAM: Lazy<Arc<Parameter>> = Lazy::new(|| {
let stderr_port = Value::Port(Arc::new(Port::new_standard(StandardPort::Stderr)));
Arc::new(Parameter::with_name(stderr_port, None, "current-error-port".to_string()))
});
pub fn get_current_input_port() -> Value {
CURRENT_INPUT_PARAM.get()
}
pub fn get_current_output_port() -> Value {
CURRENT_OUTPUT_PARAM.get()
}
#[allow(dead_code)]
pub fn get_current_error_port() -> Value {
CURRENT_ERROR_PARAM.get()
}
pub fn get_parameter_objects() -> (Value, Value, Value) {
(
Value::parameter((**CURRENT_INPUT_PARAM).clone()),
Value::parameter((**CURRENT_OUTPUT_PARAM).clone()),
Value::parameter((**CURRENT_ERROR_PARAM).clone())
)
}
}
fn create_standard_port_parameters() -> (Value, Value, Value) {
current_ports::get_parameter_objects()
}
pub fn create_io_bindings(env: &Arc<ThreadSafeEnvironment>) {
bind_port_predicates(env);
bind_current_ports(env);
bind_file_operations(env);
bind_string_bytevector_ports(env);
bind_input_operations(env);
bind_output_operations(env);
bind_eof_operations(env);
bind_utility_operations(env);
}
fn bind_port_predicates(env: &Arc<ThreadSafeEnvironment>) {
env.define("input-port?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "input-port?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_input_port_p),
effects: vec![Effect::Pure],
})));
env.define("output-port?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "output-port?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_output_port_p),
effects: vec![Effect::Pure],
})));
env.define("textual-port?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "textual-port?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_textual_port_p),
effects: vec![Effect::Pure],
})));
env.define("binary-port?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "binary-port?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_binary_port_p),
effects: vec![Effect::Pure],
})));
env.define("port?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "port?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_port_p),
effects: vec![Effect::Pure],
})));
env.define("port-open?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "port-open?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_port_open_p),
effects: vec![Effect::Pure],
})));
}
fn bind_current_ports(env: &Arc<ThreadSafeEnvironment>) {
let (input_param, output_param, error_param) = create_standard_port_parameters();
env.define("current-input-port".to_string(), input_param);
env.define("current-output-port".to_string(), output_param);
env.define("current-error-port".to_string(), error_param);
}
fn bind_file_operations(env: &Arc<ThreadSafeEnvironment>) {
env.define("open-input-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "open-input-file".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_open_input_file),
effects: vec![Effect::IO],
})));
env.define("open-output-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "open-output-file".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_open_output_file),
effects: vec![Effect::IO],
})));
env.define("open-binary-input-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "open-binary-input-file".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_open_binary_input_file),
effects: vec![Effect::IO],
})));
env.define("open-binary-output-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "open-binary-output-file".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_open_binary_output_file),
effects: vec![Effect::IO],
})));
env.define("close-port".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "close-port".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_close_port),
effects: vec![Effect::IO],
})));
env.define("close-input-port".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "close-input-port".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_close_input_port),
effects: vec![Effect::IO],
})));
env.define("close-output-port".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "close-output-port".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_close_output_port),
effects: vec![Effect::IO],
})));
env.define("with-input-from-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "with-input-from-file".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_with_input_from_file),
effects: vec![Effect::IO],
})));
env.define("with-output-to-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "with-output-to-file".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_with_output_to_file),
effects: vec![Effect::IO],
})));
env.define("call-with-input-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "call-with-input-file".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_call_with_input_file),
effects: vec![Effect::IO],
})));
env.define("call-with-output-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "call-with-output-file".to_string(),
arity_min: 2,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_call_with_output_file),
effects: vec![Effect::IO],
})));
}
fn bind_string_bytevector_ports(env: &Arc<ThreadSafeEnvironment>) {
env.define("open-input-string".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "open-input-string".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_open_input_string),
effects: vec![Effect::Pure],
})));
env.define("open-output-string".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "open-output-string".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_open_output_string),
effects: vec![Effect::Pure],
})));
env.define("get-output-string".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "get-output-string".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_get_output_string),
effects: vec![Effect::Pure],
})));
env.define("open-input-bytevector".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "open-input-bytevector".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_open_input_bytevector),
effects: vec![Effect::Pure],
})));
env.define("open-output-bytevector".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "open-output-bytevector".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_open_output_bytevector),
effects: vec![Effect::Pure],
})));
env.define("get-output-bytevector".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "get-output-bytevector".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_get_output_bytevector),
effects: vec![Effect::Pure],
})));
}
fn bind_input_operations(env: &Arc<ThreadSafeEnvironment>) {
env.define("read".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "read".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_read),
effects: vec![Effect::IO],
})));
env.define("read-char".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "read-char".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_read_char),
effects: vec![Effect::IO],
})));
env.define("peek-char".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "peek-char".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_peek_char),
effects: vec![Effect::IO],
})));
env.define("read-line".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "read-line".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_read_line),
effects: vec![Effect::IO],
})));
env.define("read-string".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "read-string".to_string(),
arity_min: 1,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_read_string),
effects: vec![Effect::IO],
})));
env.define("read-u8".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "read-u8".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_read_u8),
effects: vec![Effect::IO],
})));
env.define("peek-u8".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "peek-u8".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_peek_u8),
effects: vec![Effect::IO],
})));
env.define("read-bytevector".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "read-bytevector".to_string(),
arity_min: 1,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_read_bytevector),
effects: vec![Effect::IO],
})));
env.define("read-bytevector!".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "read-bytevector!".to_string(),
arity_min: 1,
arity_max: Some(4),
implementation: PrimitiveImpl::RustFn(primitive_read_bytevector_bang),
effects: vec![Effect::IO],
})));
env.define("char-ready?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "char-ready?".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_char_ready_p),
effects: vec![Effect::IO],
})));
env.define("u8-ready?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "u8-ready?".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_u8_ready_p),
effects: vec![Effect::IO],
})));
}
fn bind_output_operations(env: &Arc<ThreadSafeEnvironment>) {
env.define("write".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "write".to_string(),
arity_min: 1,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_write),
effects: vec![Effect::IO],
})));
env.define("write-shared".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "write-shared".to_string(),
arity_min: 1,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_write_shared),
effects: vec![Effect::IO],
})));
env.define("write-simple".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "write-simple".to_string(),
arity_min: 1,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_write_simple),
effects: vec![Effect::IO],
})));
env.define("display".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "display".to_string(),
arity_min: 1,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_display),
effects: vec![Effect::IO],
})));
env.define("newline".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "newline".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_newline),
effects: vec![Effect::IO],
})));
env.define("write-char".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "write-char".to_string(),
arity_min: 1,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_write_char),
effects: vec![Effect::IO],
})));
env.define("write-string".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "write-string".to_string(),
arity_min: 1,
arity_max: Some(4),
implementation: PrimitiveImpl::RustFn(primitive_write_string),
effects: vec![Effect::IO],
})));
env.define("write-u8".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "write-u8".to_string(),
arity_min: 1,
arity_max: Some(2),
implementation: PrimitiveImpl::RustFn(primitive_write_u8),
effects: vec![Effect::IO],
})));
env.define("write-bytevector".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "write-bytevector".to_string(),
arity_min: 1,
arity_max: Some(4),
implementation: PrimitiveImpl::RustFn(primitive_write_bytevector),
effects: vec![Effect::IO],
})));
env.define("flush-output-port".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "flush-output-port".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_flush_output_port),
effects: vec![Effect::IO],
})));
}
fn bind_eof_operations(env: &Arc<ThreadSafeEnvironment>) {
env.define("eof-object".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "eof-object".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_eof_object),
effects: vec![Effect::Pure],
})));
env.define("eof-object?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "eof-object?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_eof_object_p),
effects: vec![Effect::Pure],
})));
}
fn bind_utility_operations(env: &Arc<ThreadSafeEnvironment>) {
env.define("file-exists?".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "file-exists?".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_file_exists_p),
effects: vec![Effect::IO],
})));
env.define("delete-file".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "delete-file".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_delete_file),
effects: vec![Effect::IO],
})));
}
pub fn primitive_input_port_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("input-port? expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => Ok(Value::boolean(port.is_input())),
_ => Ok(Value::boolean(false)),
}
}
pub fn primitive_output_port_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("output-port? expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => Ok(Value::boolean(port.is_output())),
_ => Ok(Value::boolean(false)),
}
}
pub fn primitive_textual_port_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("textual-port? expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => Ok(Value::boolean(port.is_textual())),
_ => Ok(Value::boolean(false)),
}
}
pub fn primitive_binary_port_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("binary-port? expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => Ok(Value::boolean(port.is_binary())),
_ => Ok(Value::boolean(false)),
}
}
pub fn primitive_port_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("port? expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
Ok(Value::boolean(args[0].is_port()))
}
pub fn primitive_port_open_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("port-open? expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => Ok(Value::boolean(port.is_open())),
_ => Err(Box::new(DiagnosticError::runtime_error(
"port-open? requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_open_input_file(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("open-input-file expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
let filename = extract_string(&args[0], "open-input-file")?;
let port = Port::new_file_input(filename, false);
if let PortImpl::File { path, handle } = &port.implementation {
match File::open(path) {
Ok(file) => {
let reader = BufReader::new(file);
*handle.write().unwrap() = Some(PortFileHandle::TextReader(reader));
}
Err(e) => {
return Err(Box::new(DiagnosticError::runtime_error(
format!("Cannot open file '{path}': {e}"),
None,
)));
}
}
}
Ok(Value::Port(Arc::new(port)))
}
pub fn primitive_open_output_file(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("open-output-file expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
let filename = extract_string(&args[0], "open-output-file")?;
let port = Port::new_file_output(filename, false);
if let PortImpl::File { path, handle } = &port.implementation {
match File::create(path) {
Ok(file) => {
let writer = BufWriter::new(file);
*handle.write().unwrap() = Some(PortFileHandle::TextWriter(writer));
}
Err(e) => {
return Err(Box::new(DiagnosticError::runtime_error(
format!("Cannot create file '{path}': {e}"),
None,
)));
}
}
}
Ok(Value::Port(Arc::new(port)))
}
pub fn primitive_open_binary_input_file(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("open-binary-input-file expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
let filename = extract_string(&args[0], "open-binary-input-file")?;
let port = Port::new_file_input(filename, true);
if let PortImpl::File { path, handle } = &port.implementation {
match File::open(path) {
Ok(file) => {
let reader = BufReader::new(file);
*handle.write().unwrap() = Some(PortFileHandle::BinaryReader(reader));
}
Err(e) => {
return Err(Box::new(DiagnosticError::runtime_error(
format!("Cannot open binary file '{path}': {e}"),
None,
)));
}
}
}
Ok(Value::Port(Arc::new(port)))
}
pub fn primitive_open_binary_output_file(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("open-binary-output-file expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
let filename = extract_string(&args[0], "open-binary-output-file")?;
let port = Port::new_file_output(filename, true);
if let PortImpl::File { path, handle } = &port.implementation {
match File::create(path) {
Ok(file) => {
let writer = BufWriter::new(file);
*handle.write().unwrap() = Some(PortFileHandle::BinaryWriter(writer));
}
Err(e) => {
return Err(Box::new(DiagnosticError::runtime_error(
format!("Cannot create binary file '{path}': {e}"),
None,
)));
}
}
}
Ok(Value::Port(Arc::new(port)))
}
pub fn primitive_close_port(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("close-port expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => {
port.close();
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"close-port requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_close_input_port(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("close-input-port expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => {
if !port.is_input() {
return Err(Box::new(DiagnosticError::runtime_error(
"close-input-port requires an input port".to_string(),
None,
)));
}
port.close();
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"close-input-port requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_close_output_port(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("close-output-port expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => {
if !port.is_output() {
return Err(Box::new(DiagnosticError::runtime_error(
"close-output-port requires an output port".to_string(),
None,
)));
}
port.close();
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"close-output-port requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_with_input_from_file(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("with-input-from-file expects 2 arguments, got {}", args.len()),
None,
)));
}
let filename = extract_string(&args[0], "with-input-from-file")?;
let thunk = args[1].clone();
let port = Port::new_file_input(filename.clone(), false);
let port_value = Value::Port(Arc::new(port));
let current_input_param = current_ports::get_parameter_objects().0;
if let Value::Parameter(param) = ¤t_input_param {
let mut bindings = std::collections::HashMap::new();
bindings.insert(param.id(), port_value);
crate::eval::parameter::ParameterBinding::with_bindings(bindings, || {
Err(Box::new(DiagnosticError::runtime_error(
"with-input-from-file: requires evaluator support (not yet implemented in primitive context)".to_string(),
None,
)))
})
} else {
Err(Box::new(DiagnosticError::runtime_error(
"with-input-from-file: internal error - current input port is not a parameter".to_string(),
None,
)))
}
}
pub fn primitive_with_output_to_file(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("with-output-to-file expects 2 arguments, got {}", args.len()),
None,
)));
}
let filename = extract_string(&args[0], "with-output-to-file")?;
let thunk = args[1].clone();
let port = Port::new_file_output(filename.clone(), false);
let port_value = Value::Port(Arc::new(port));
let current_output_param = current_ports::get_parameter_objects().1;
if let Value::Parameter(param) = ¤t_output_param {
let mut bindings = std::collections::HashMap::new();
bindings.insert(param.id(), port_value);
crate::eval::parameter::ParameterBinding::with_bindings(bindings, || {
Err(Box::new(DiagnosticError::runtime_error(
"with-output-to-file: requires evaluator support (not yet implemented in primitive context)".to_string(),
None,
)))
})
} else {
Err(Box::new(DiagnosticError::runtime_error(
"with-output-to-file: internal error - current output port is not a parameter".to_string(),
None,
)))
}
}
pub fn primitive_call_with_input_file(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("call-with-input-file expects 2 arguments, got {}", args.len()),
None,
)));
}
let filename = extract_string(&args[0], "call-with-input-file")?;
let proc = args[1].clone();
let port = Port::new_file_input(filename.clone(), false);
let port_value = Value::Port(Arc::new(port));
let result = match &proc {
Value::Procedure(_procedure) => {
Err(Box::new(DiagnosticError::runtime_error(
"call-with-input-file: procedure calls require evaluator support (not yet implemented in primitive context)".to_string(),
None,
)))
},
Value::Primitive(prim) => {
match &prim.implementation {
crate::eval::value::PrimitiveImpl::RustFn(f) => f(&[port_value.clone()]),
_ => Err(Box::new(DiagnosticError::runtime_error(
"call-with-input-file: unsupported primitive type".to_string(),
None,
))),
}
},
_ => Err(Box::new(DiagnosticError::runtime_error(
"call-with-input-file: second argument must be a procedure".to_string(),
None,
))),
};
if let Value::Port(port) = &port_value {
port.close();
}
result
}
pub fn primitive_call_with_output_file(args: &[Value]) -> Result<Value> {
if args.len() != 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("call-with-output-file expects 2 arguments, got {}", args.len()),
None,
)));
}
let filename = extract_string(&args[0], "call-with-output-file")?;
let proc = args[1].clone();
let port = Port::new_file_output(filename.clone(), false);
let port_value = Value::Port(Arc::new(port));
let result = match &proc {
Value::Procedure(_procedure) => {
Err(Box::new(DiagnosticError::runtime_error(
"call-with-output-file: procedure calls require evaluator support (not yet implemented in primitive context)".to_string(),
None,
)))
},
Value::Primitive(prim) => {
match &prim.implementation {
crate::eval::value::PrimitiveImpl::RustFn(f) => f(&[port_value.clone()]),
_ => Err(Box::new(DiagnosticError::runtime_error(
"call-with-output-file: unsupported primitive type".to_string(),
None,
))),
}
},
_ => Err(Box::new(DiagnosticError::runtime_error(
"call-with-output-file: second argument must be a procedure".to_string(),
None,
))),
};
if let Value::Port(port) = &port_value {
port.close();
}
result
}
pub fn primitive_open_input_string(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("open-input-string expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
let string = extract_string(&args[0], "open-input-string")?;
let port = Port::new_string_input(string);
Ok(Value::Port(Arc::new(port)))
}
pub fn primitive_open_output_string(_args: &[Value]) -> Result<Value> {
let port = Port::new_string_output();
Ok(Value::Port(Arc::new(port)))
}
pub fn primitive_get_output_string(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("get-output-string expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => {
if !port.is_output() {
return Err(Box::new(DiagnosticError::runtime_error(
"get-output-string requires an output port".to_string(),
None,
)));
}
match &port.implementation {
PortImpl::String { content, .. } => {
let result = content.read().unwrap().clone();
content.write().unwrap().clear();
Ok(Value::string(result))
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"get-output-string requires a string output port".to_string(),
None,
))),
}
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"get-output-string requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_open_input_bytevector(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("open-input-bytevector expects 1 argument, got {args_len}", args_len = args.len()),
None,
)));
}
let bytevector = extract_bytevector(&args[0], "open-input-bytevector")?;
let port = Port::new_bytevector_input(bytevector);
Ok(Value::Port(Arc::new(port)))
}
pub fn primitive_open_output_bytevector(_args: &[Value]) -> Result<Value> {
let port = Port::new_bytevector_output();
Ok(Value::Port(Arc::new(port)))
}
pub fn primitive_get_output_bytevector(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("get-output-bytevector expects 1 argument, got {}", args.len()),
None,
)));
}
match &args[0] {
Value::Port(port) => {
if !port.is_output() {
return Err(Box::new(DiagnosticError::runtime_error(
"get-output-bytevector requires an output port".to_string(),
None,
)));
}
match &port.implementation {
PortImpl::Bytevector { content, .. } => {
let result = content.read().unwrap().clone();
content.write().unwrap().clear();
Ok(Value::bytevector(result))
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"get-output-bytevector requires a bytevector output port".to_string(),
None,
))),
}
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"get-output-bytevector requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_read(args: &[Value]) -> Result<Value> {
let port = if args.is_empty() {
current_ports::get_current_input_port()
} else if args.len() == 1 {
args[0].clone()
} else {
return Err(Box::new(DiagnosticError::runtime_error(
format!("read expects 0 or 1 arguments, got {}", args.len()),
None,
)));
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() {
return Err(Box::new(DiagnosticError::runtime_error(
"read requires an input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"read from closed port".to_string(),
None,
)));
}
match read_text_from_port(&port_ref) {
Ok(Some(text)) => {
let mut lexer = Lexer::new(&text, None);
let tokens = lexer.tokenize().map_err(|e| {
DiagnosticError::runtime_error(
format!("read: lexer error: {e}"),
None,
)
})?;
let mut parser = Parser::new(tokens);
match parser.parse_expression() {
Ok(expr) => {
expr_to_value(expr.inner)
}
Err(_) => Ok(eof_value()),
}
}
Ok(None) => Ok(eof_value()),
Err(e) => Err(e),
}
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_read_char(args: &[Value]) -> Result<Value> {
let port = if args.is_empty() {
current_ports::get_current_input_port()
} else if args.len() == 1 {
args[0].clone()
} else {
return Err(Box::new(DiagnosticError::runtime_error(
format!("read-char expects 0 or 1 arguments, got {}", args.len()),
None,
)));
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-char requires a textual input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-char from closed port".to_string(),
None,
)));
}
read_char_from_port(&port_ref, false)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-char requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_peek_char(args: &[Value]) -> Result<Value> {
let port = if args.is_empty() {
current_ports::get_current_input_port()
} else if args.len() == 1 {
args[0].clone()
} else {
return Err(Box::new(DiagnosticError::runtime_error(
format!("peek-char expects 0 or 1 arguments, got {}", args.len()),
None,
)));
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"peek-char requires a textual input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"peek-char from closed port".to_string(),
None,
)));
}
read_char_from_port(&port_ref, true)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"peek-char requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_read_line(args: &[Value]) -> Result<Value> {
let port = if args.is_empty() {
current_ports::get_current_input_port()
} else if args.len() == 1 {
args[0].clone()
} else {
return Err(Box::new(DiagnosticError::runtime_error(
format!("read-line expects 0 or 1 arguments, got {}", args.len()),
None,
)));
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-line requires a textual input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-line from closed port".to_string(),
None,
)));
}
read_line_from_port(&port_ref)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-line requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_read_string(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("read-string expects 1 or 2 arguments, got {}", args.len()),
None,
)));
}
let k = extract_integer(&args[0], "read-string")? as usize;
let port = if args.len() == 1 {
current_ports::get_current_input_port()
} else {
args[1].clone()
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-string requires a textual input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-string from closed port".to_string(),
None,
)));
}
read_string_from_port(&port_ref, k)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-string requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_read_u8(args: &[Value]) -> Result<Value> {
let port = if args.is_empty() {
current_ports::get_current_input_port()
} else if args.len() == 1 {
args[0].clone()
} else {
return Err(Box::new(DiagnosticError::runtime_error(
format!("read-u8 expects 0 or 1 arguments, got {}", args.len()),
None,
)));
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() || !port_ref.is_binary() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-u8 requires a binary input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-u8 from closed port".to_string(),
None,
)));
}
read_u8_from_port(&port_ref, false)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-u8 requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_peek_u8(args: &[Value]) -> Result<Value> {
let port = if args.is_empty() {
current_ports::get_current_input_port()
} else if args.len() == 1 {
args[0].clone()
} else {
return Err(Box::new(DiagnosticError::runtime_error(
format!("peek-u8 expects 0 or 1 arguments, got {}", args.len()),
None,
)));
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() || !port_ref.is_binary() {
return Err(Box::new(DiagnosticError::runtime_error(
"peek-u8 requires a binary input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"peek-u8 from closed port".to_string(),
None,
)));
}
read_u8_from_port(&port_ref, true)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"peek-u8 requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_read_bytevector(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("read-bytevector expects 1 or 2 arguments, got {}", args.len()),
None,
)));
}
let k = extract_integer(&args[0], "read-bytevector")? as usize;
let port = if args.len() == 1 {
current_ports::get_current_input_port()
} else {
args[1].clone()
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() || !port_ref.is_binary() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector requires a binary input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector from closed port".to_string(),
None,
)));
}
read_bytevector_from_port(&port_ref, k)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_read_bytevector_bang(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 4 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("read-bytevector! expects 1 to 4 arguments, got {}", args.len()),
None,
)));
}
let bytevector = extract_bytevector(&args[0], "read-bytevector!")?;
let port = if args.len() == 1 {
current_ports::get_current_input_port()
} else {
args[1].clone()
};
let start = if args.len() >= 3 {
extract_integer(&args[2], "read-bytevector!")? as usize
} else {
0
};
let end = if args.len() >= 4 {
extract_integer(&args[3], "read-bytevector!")? as usize
} else {
bytevector.len()
};
if start > end || end > bytevector.len() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector!: invalid start/end indices".to_string(),
None,
)));
}
match port {
Value::Port(port_ref) => {
if !port_ref.is_input() || !port_ref.is_binary() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector! requires a binary input port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector! from closed port".to_string(),
None,
)));
}
let mut bytevector_copy = bytevector.clone();
read_bytevector_bang_from_port(&port_ref, &mut bytevector_copy, start, end)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector! requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_char_ready_p(_args: &[Value]) -> Result<Value> {
Ok(Value::boolean(true))
}
pub fn primitive_u8_ready_p(_args: &[Value]) -> Result<Value> {
Ok(Value::boolean(true))
}
pub fn primitive_write(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("write expects 1 or 2 arguments, got {}", args.len()),
None,
)));
}
let value = &args[0];
let port = if args.len() == 1 {
current_ports::get_current_output_port()
} else {
args[1].clone()
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_output() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"write requires a textual output port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"write to closed port".to_string(),
None,
)));
}
let output = format!("{value}");
write_string_to_port(&port_ref, &output)?;
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"write requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_write_shared(args: &[Value]) -> Result<Value> {
primitive_write(args)
}
pub fn primitive_write_simple(args: &[Value]) -> Result<Value> {
primitive_write(args)
}
pub fn primitive_display(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("display expects 1 or 2 arguments, got {}", args.len()),
None,
)));
}
let value = &args[0];
let port = if args.len() == 1 {
current_ports::get_current_output_port()
} else {
args[1].clone()
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_output() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"display requires a textual output port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"display to closed port".to_string(),
None,
)));
}
let output = display_value(value);
write_string_to_port(&port_ref, &output)?;
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"display requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_newline(args: &[Value]) -> Result<Value> {
if args.len() > 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("newline expects 0 or 1 arguments, got {}", args.len()),
None,
)));
}
let port = if args.is_empty() {
current_ports::get_current_output_port()
} else {
args[0].clone()
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_output() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"newline requires a textual output port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"newline to closed port".to_string(),
None,
)));
}
write_string_to_port(&port_ref, "\n")?;
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"newline requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_write_char(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("write-char expects 1 or 2 arguments, got {}", args.len()),
None,
)));
}
let ch = extract_character(&args[0], "write-char")?;
let port = if args.len() == 1 {
current_ports::get_current_output_port()
} else {
args[1].clone()
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_output() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-char requires a textual output port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-char to closed port".to_string(),
None,
)));
}
write_string_to_port(&port_ref, &ch.to_string())?;
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"write-char requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_write_string(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 4 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("write-string expects 1 to 4 arguments, got {}", args.len()),
None,
)));
}
let string = extract_string(&args[0], "write-string")?;
let port = if args.len() == 1 {
current_ports::get_current_output_port()
} else {
args[1].clone()
};
let start = if args.len() >= 3 {
extract_integer(&args[2], "write-string")? as usize
} else {
0
};
let end = if args.len() >= 4 {
extract_integer(&args[3], "write-string")? as usize
} else {
string.len()
};
if start > end || end > string.len() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-string: invalid start/end indices".to_string(),
None,
)));
}
match port {
Value::Port(port_ref) => {
if !port_ref.is_output() || !port_ref.is_textual() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-string requires a textual output port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-string to closed port".to_string(),
None,
)));
}
let substring = &string[start..end];
write_string_to_port(&port_ref, substring)?;
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"write-string requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_write_u8(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 2 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("write-u8 expects 1 or 2 arguments, got {}", args.len()),
None,
)));
}
let byte = extract_integer(&args[0], "write-u8")? as u8;
let port = if args.len() == 1 {
current_ports::get_current_output_port()
} else {
args[1].clone()
};
match port {
Value::Port(port_ref) => {
if !port_ref.is_output() || !port_ref.is_binary() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-u8 requires a binary output port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-u8 to closed port".to_string(),
None,
)));
}
write_u8_to_port(&port_ref, byte)?;
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"write-u8 requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_write_bytevector(args: &[Value]) -> Result<Value> {
if args.is_empty() || args.len() > 4 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("write-bytevector expects 1 to 4 arguments, got {}", args.len()),
None,
)));
}
let bytevector = extract_bytevector(&args[0], "write-bytevector")?;
let port = if args.len() == 1 {
current_ports::get_current_output_port()
} else {
args[1].clone()
};
let start = if args.len() >= 3 {
extract_integer(&args[2], "write-bytevector")? as usize
} else {
0
};
let end = if args.len() >= 4 {
extract_integer(&args[3], "write-bytevector")? as usize
} else {
bytevector.len()
};
if start > end || end > bytevector.len() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-bytevector: invalid start/end indices".to_string(),
None,
)));
}
match port {
Value::Port(port_ref) => {
if !port_ref.is_output() || !port_ref.is_binary() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-bytevector requires a binary output port".to_string(),
None,
)));
}
if !port_ref.is_open() {
return Err(Box::new(DiagnosticError::runtime_error(
"write-bytevector to closed port".to_string(),
None,
)));
}
write_bytevector_to_port(&port_ref, &bytevector[start..end])?;
Ok(Value::Unspecified)
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"write-bytevector requires a port argument".to_string(),
None,
))),
}
}
pub fn primitive_flush_output_port(_args: &[Value]) -> Result<Value> {
Ok(Value::Unspecified)
}
pub fn primitive_eof_object(_args: &[Value]) -> Result<Value> {
Ok(eof_value())
}
pub fn primitive_eof_object_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("eof-object? expects 1 argument, got {}", args.len()),
None,
)));
}
Ok(Value::boolean(is_eof_value(&args[0])))
}
pub fn primitive_file_exists_p(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("file-exists? expects 1 argument, got {}", args.len()),
None,
)));
}
let filename = extract_string(&args[0], "file-exists?")?;
Ok(Value::boolean(std::path::Path::new(&filename).exists()))
}
pub fn primitive_delete_file(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("delete-file expects 1 argument, got {}", args.len()),
None,
)));
}
let filename = extract_string(&args[0], "delete-file")?;
match std::fs::remove_file(&filename) {
Ok(()) => Ok(Value::Unspecified),
Err(e) => Err(Box::new(DiagnosticError::runtime_error(
format!("Cannot delete file '{filename}': {e}"),
None,
))),
}
}
fn extract_string(value: &Value, operation: &str) -> Result<String> {
match value {
Value::Literal(crate::ast::Literal::String(s)) => Ok(s.clone()),
_ => Err(Box::new(DiagnosticError::runtime_error(
format!("{operation} requires string arguments"),
None,
))),
}
}
fn extract_character(value: &Value, operation: &str) -> Result<char> {
match value {
Value::Literal(crate::ast::Literal::Character(c)) => Ok(*c),
_ => Err(Box::new(DiagnosticError::runtime_error(
format!("{operation} requires character arguments"),
None,
))),
}
}
fn extract_integer(value: &Value, operation: &str) -> Result<i64> {
match value {
Value::Literal(lit) => {
if let Some(i) = lit.to_i64() {
Ok(i)
} else {
Err(Box::new(DiagnosticError::runtime_error(
format!("{operation} requires integer arguments"),
None,
)))
}
}
_ => Err(Box::new(DiagnosticError::runtime_error(
format!("{operation} requires integer arguments"),
None,
))),
}
}
fn extract_bytevector(value: &Value, operation: &str) -> Result<Vec<u8>> {
match value {
Value::Literal(crate::ast::Literal::Bytevector(bv)) => Ok(bv.clone()),
_ => Err(Box::new(DiagnosticError::runtime_error(
format!("{operation} requires bytevector arguments"),
None,
))),
}
}
fn eof_value() -> Value {
Value::Symbol(crate::utils::intern_symbol("*eof*"))
}
fn is_eof_value(value: &Value) -> bool {
match value {
Value::Symbol(id) => {
crate::utils::symbol_name(*id).map(|name| name == "*eof*").unwrap_or(false)
}
_ => false,
}
}
fn expr_to_value(expr: crate::ast::Expr) -> Result<Value> {
match expr {
crate::ast::Expr::Literal(lit) => Ok(Value::Literal(lit)),
crate::ast::Expr::Identifier(name) | crate::ast::Expr::Symbol(name) => Ok(Value::symbol_from_str(name)),
crate::ast::Expr::Quote(quoted) => {
match quoted.inner {
crate::ast::Expr::Literal(lit) => Ok(Value::Literal(lit)),
crate::ast::Expr::Identifier(name) | crate::ast::Expr::Symbol(name) => Ok(Value::symbol_from_str(name)),
crate::ast::Expr::List(elements) => {
let mut values = Vec::new();
for elem in elements {
values.push(expr_to_value(elem.inner)?);
}
Ok(Value::list(values))
}
other => expr_to_value(other), }
}
crate::ast::Expr::List(elements) => {
let mut values = Vec::new();
for elem in elements {
values.push(expr_to_value(elem.inner)?);
}
Ok(Value::list(values))
}
crate::ast::Expr::Application { .. } |
crate::ast::Expr::If { .. } |
crate::ast::Expr::Let { .. } |
crate::ast::Expr::LetStar { .. } |
crate::ast::Expr::LetRec { .. } |
crate::ast::Expr::Lambda { .. } |
crate::ast::Expr::CaseLambda { .. } |
crate::ast::Expr::Define { .. } |
crate::ast::Expr::DefineSyntax { .. } |
crate::ast::Expr::Begin { .. } |
crate::ast::Expr::Set { .. } |
crate::ast::Expr::Cond { .. } |
crate::ast::Expr::Case { .. } |
crate::ast::Expr::And { .. } |
crate::ast::Expr::Or { .. } |
crate::ast::Expr::CallCC { .. } |
crate::ast::Expr::SyntaxRules { .. } |
crate::ast::Expr::Parameterize { .. } |
crate::ast::Expr::Guard { .. } |
crate::ast::Expr::Keyword(_) |
crate::ast::Expr::Quasiquote(_) |
crate::ast::Expr::Unquote(_) |
crate::ast::Expr::UnquoteSplicing(_) |
crate::ast::Expr::Primitive { .. } |
crate::ast::Expr::TypeAnnotation { .. } |
crate::ast::Expr::Import { .. } |
crate::ast::Expr::DefineLibrary { .. } |
crate::ast::Expr::Pair { .. } |
crate::ast::Expr::When { .. } |
crate::ast::Expr::Unless { .. } => {
Err(Box::new(DiagnosticError::runtime_error(
"read: complex expressions require evaluation".to_string(),
None,
)))
}
}
}
fn read_text_from_port(port: &Port) -> Result<Option<String>> {
match &port.implementation {
PortImpl::String { content, position } => {
let content_guard = content.read().unwrap();
let mut pos_guard = position.write().unwrap();
if *pos_guard >= content_guard.len() {
return Ok(None); }
let remaining = &content_guard[*pos_guard..];
if let Some(end) = find_sexp_boundary(remaining) {
let text = remaining[..end].trim().to_string();
*pos_guard += end;
while *pos_guard < content_guard.len() && content_guard.chars().nth(*pos_guard).is_some_and(|c| c.is_whitespace()) {
*pos_guard += 1;
}
Ok(Some(text))
} else {
let text = remaining.trim().to_string();
*pos_guard = content_guard.len();
if text.is_empty() {
Ok(None)
} else {
Ok(Some(text))
}
}
}
PortImpl::Standard(StandardPort::Stdin) => {
use std::io::{self, BufRead};
let stdin = io::stdin();
let mut handle = stdin.lock();
let mut line = String::new();
match handle.read_line(&mut line) {
Ok(0) => Ok(None), Ok(_) => Ok(Some(line.trim().to_string())),
Err(e) => Err(Box::new(DiagnosticError::runtime_error(
format!("Error reading from stdin: {e}"),
None,
))),
}
}
PortImpl::File { handle, .. } => {
use std::io::{BufRead, BufReader};
if let Some(file_handle) = handle.write().unwrap().as_mut() {
match file_handle {
PortFileHandle::TextReader(reader) => {
let mut line = String::new();
match reader.read_line(&mut line) {
Ok(0) => Ok(None), Ok(_) => Ok(Some(line.trim().to_string())),
Err(e) => Err(Box::new(DiagnosticError::runtime_error(
format!("Error reading from file: {e}"),
None,
))),
}
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"Invalid file handle for text reading".to_string(),
None,
))),
}
} else {
Err(Box::new(DiagnosticError::runtime_error(
"File handle not initialized".to_string(),
None,
)))
}
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read_text_from_port: unsupported port type".to_string(),
None,
))),
}
}
fn find_sexp_boundary(text: &str) -> Option<usize> {
let mut depth = 0;
let mut in_string = false;
let mut escape_next = false;
let mut chars = text.char_indices().peekable();
let mut start_found = false;
while let Some((i, ch)) = chars.next() {
if escape_next {
escape_next = false;
continue;
}
if in_string {
match ch {
'\\' => escape_next = true,
'"' => in_string = false,
_ => {}
}
continue;
}
match ch {
'"' => in_string = true,
'(' | '[' => {
depth += 1;
start_found = true;
}
')' | ']' => {
depth -= 1;
if start_found && depth == 0 {
return Some(i + ch.len_utf8());
}
}
ch if ch.is_whitespace() => {
if start_found && depth == 0 {
return Some(i);
}
}
_ => {
if !start_found {
start_found = true;
if depth == 0 {
let mut j = i + ch.len_utf8();
for (k, next_ch) in chars {
match next_ch {
'(' | ')' | '[' | ']' | '"' => return Some(k),
ch if ch.is_whitespace() => return Some(k),
_ => j = k + next_ch.len_utf8(),
}
}
return Some(j);
}
}
}
}
}
if start_found && depth == 0 {
Some(text.len())
} else {
None
}
}
fn read_char_from_port(port: &Port, peek: bool) -> Result<Value> {
match &port.implementation {
PortImpl::String { content, position } => {
let content_guard = content.read().unwrap();
let mut pos_guard = position.write().unwrap();
if *pos_guard >= content_guard.len() {
return Ok(eof_value());
}
let remaining = &content_guard[*pos_guard..];
if let Some(ch) = remaining.chars().next() {
if !peek {
*pos_guard += ch.len_utf8();
}
Ok(Value::Literal(crate::ast::Literal::Character(ch)))
} else {
Ok(eof_value())
}
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-char: unsupported port type".to_string(),
None,
))),
}
}
fn read_line_from_port(port: &Port) -> Result<Value> {
match &port.implementation {
PortImpl::String { content, position } => {
let content_guard = content.read().unwrap();
let mut pos_guard = position.write().unwrap();
if *pos_guard >= content_guard.len() {
return Ok(eof_value());
}
let remaining = &content_guard[*pos_guard..];
if let Some(newline_pos) = remaining.find('\n') {
let line = remaining[..newline_pos].to_string();
*pos_guard += newline_pos + 1; Ok(Value::string(line))
} else {
let line = remaining.to_string();
*pos_guard = content_guard.len();
Ok(Value::string(line))
}
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-line: unsupported port type".to_string(),
None,
))),
}
}
fn read_string_from_port(port: &Port, k: usize) -> Result<Value> {
match &port.implementation {
PortImpl::String { content, position } => {
let content_guard = content.read().unwrap();
let mut pos_guard = position.write().unwrap();
if *pos_guard >= content_guard.len() {
return Ok(eof_value());
}
let remaining = &content_guard[*pos_guard..];
let to_read = std::cmp::min(k, remaining.len());
let result = remaining[..to_read].to_string();
*pos_guard += to_read;
Ok(Value::string(result))
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-string: unsupported port type".to_string(),
None,
))),
}
}
fn read_u8_from_port(port: &Port, peek: bool) -> Result<Value> {
match &port.implementation {
PortImpl::Bytevector { content, position } => {
let content_guard = content.read().unwrap();
let mut pos_guard = position.write().unwrap();
if *pos_guard >= content_guard.len() {
return Ok(eof_value());
}
let byte = content_guard[*pos_guard];
if !peek {
*pos_guard += 1;
}
Ok(Value::integer(byte as i64))
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-u8: unsupported port type".to_string(),
None,
))),
}
}
fn write_string_to_port(port: &Port, s: &str) -> Result<()> {
match &port.implementation {
PortImpl::String { content, .. } => {
content.write().unwrap().push_str(s);
Ok(())
}
PortImpl::Standard(StandardPort::Stdout) => {
print!("{s}");
Ok(())
}
PortImpl::Standard(StandardPort::Stderr) => {
eprint!("{s}");
Ok(())
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"write-string: unsupported port type".to_string(),
None,
))),
}
}
fn write_u8_to_port(port: &Port, byte: u8) -> Result<()> {
match &port.implementation {
PortImpl::Bytevector { content, .. } => {
content.write().unwrap().push(byte);
Ok(())
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"write-u8: unsupported port type".to_string(),
None,
))),
}
}
fn read_bytevector_from_port(port: &Port, k: usize) -> Result<Value> {
match &port.implementation {
PortImpl::Bytevector { content, position } => {
let content_guard = content.read().unwrap();
let mut pos_guard = position.write().unwrap();
if *pos_guard >= content_guard.len() {
return Ok(eof_value());
}
let remaining = &content_guard[*pos_guard..];
let to_read = std::cmp::min(k, remaining.len());
let result = remaining[..to_read].to_vec();
*pos_guard += to_read;
Ok(Value::bytevector(result))
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector: unsupported port type".to_string(),
None,
))),
}
}
fn read_bytevector_bang_from_port(port: &Port, bytevector: &mut [u8], start: usize, end: usize) -> Result<Value> {
match &port.implementation {
PortImpl::Bytevector { content, position } => {
let content_guard = content.read().unwrap();
let mut pos_guard = position.write().unwrap();
if *pos_guard >= content_guard.len() {
return Ok(eof_value());
}
let available = &content_guard[*pos_guard..];
let to_read = std::cmp::min(end - start, available.len());
for i in 0..to_read {
if start + i < bytevector.len() {
bytevector[start + i] = available[i];
}
}
*pos_guard += to_read;
Ok(Value::integer(to_read as i64))
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"read-bytevector!: unsupported port type".to_string(),
None,
))),
}
}
fn write_bytevector_to_port(port: &Port, bytes: &[u8]) -> Result<()> {
match &port.implementation {
PortImpl::Bytevector { content, .. } => {
content.write().unwrap().extend_from_slice(bytes);
Ok(())
}
_ => Err(Box::new(DiagnosticError::runtime_error(
"write-bytevector: unsupported port type".to_string(),
None,
))),
}
}
fn display_value(value: &Value) -> String {
value.display_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_port_creation() {
let port = Port::new_string_input("hello".to_string());
assert!(port.is_textual());
assert!(port.is_input());
assert!(port.is_open());
}
#[test]
fn test_port_predicates() {
let args = vec![Value::integer(42)];
let result = primitive_port_p(&args).unwrap();
assert_eq!(result, Value::boolean(false));
let port = Value::Port(Arc::new(Port::new_string_input("test".to_string())));
let args = vec![port];
let result = primitive_input_port_p(&args).unwrap();
assert_eq!(result, Value::boolean(true));
}
#[test]
fn test_eof_object() {
let eof = primitive_eof_object(&[]).unwrap();
let args = vec![eof.clone()];
let result = primitive_eof_object_p(&args).unwrap();
assert_eq!(result, Value::boolean(true));
let args = vec![Value::integer(42)];
let result = primitive_eof_object_p(&args).unwrap();
assert_eq!(result, Value::boolean(false));
}
#[test]
fn test_display_value_r7rs_compliance() {
let string_value = Value::string("Hello World");
let result = display_value(&string_value);
assert_eq!(result, "Hello World");
let char_value = Value::Literal(crate::ast::Literal::Character('x'));
let result = display_value(&char_value);
assert_eq!(result, "x");
let number_value = Value::integer(42);
let result = display_value(&number_value);
assert_eq!(result, "42");
}
#[test]
fn test_primitive_display_function() {
use std::sync::{Arc, RwLock};
use crate::eval::value::{Port, PortImpl};
let output_port = Arc::new(Port::new_string_output());
let string_value = Value::string("Hello World");
let args = vec![string_value, Value::Port(output_port.clone())];
let result = primitive_display(&args);
assert!(result.is_ok());
if let PortImpl::String { content, .. } = &output_port.implementation {
let captured = content.read().unwrap();
assert_eq!(*captured, "Hello World"); } else {
panic!("Expected string port");
}
}
#[test]
fn test_display_vs_write_r7rs_compliance() {
use std::sync::Arc;
use crate::eval::value::{Port, PortImpl};
let display_port = Arc::new(Port::new_string_output());
let string_value = Value::string("Hello World");
let char_value = Value::Literal(crate::ast::Literal::Character('x'));
let result = primitive_display(&[string_value.clone(), Value::Port(display_port.clone())]);
assert!(result.is_ok());
let result = primitive_display(&[char_value.clone(), Value::Port(display_port.clone())]);
assert!(result.is_ok());
if let PortImpl::String { content, .. } = &display_port.implementation {
let captured = content.read().unwrap();
assert_eq!(*captured, "Hello Worldx"); } else {
panic!("Expected string port");
}
let write_port = Arc::new(Port::new_string_output());
let result = primitive_write(&[string_value, Value::Port(write_port.clone())]);
assert!(result.is_ok());
let result = primitive_write(&[char_value, Value::Port(write_port.clone())]);
assert!(result.is_ok());
if let PortImpl::String { content, .. } = &write_port.implementation {
let captured = content.read().unwrap();
assert_eq!(*captured, "\"Hello World\"#\\x"); } else {
panic!("Expected string port");
}
}
#[test]
fn test_r7rs_string_port_complete_workflow() {
let out_port = Arc::new(Port::new_string_output());
let out_port_value = Value::Port(out_port.clone());
assert!(primitive_write(&[Value::string("hello"), out_port_value.clone()]).is_ok());
assert!(primitive_write_char(&[Value::Literal(crate::ast::Literal::Character(' ')), out_port_value.clone()]).is_ok());
assert!(primitive_write(&[Value::string("world"), out_port_value.clone()]).is_ok());
let result = primitive_get_output_string(&[out_port_value.clone()]).unwrap();
assert_eq!(result.as_string().unwrap(), "\"hello\" \"world\"");
let input_content = "abc";
let in_port = Arc::new(Port::new_string_input(input_content.to_string()));
let in_port_value = Value::Port(in_port.clone());
let ch1 = primitive_read_char(&[in_port_value.clone()]).unwrap();
assert_eq!(ch1, Value::Literal(crate::ast::Literal::Character('a')));
let ch2 = primitive_peek_char(&[in_port_value.clone()]).unwrap();
assert_eq!(ch2, Value::Literal(crate::ast::Literal::Character('b')));
let ch3 = primitive_read_char(&[in_port_value.clone()]).unwrap();
assert_eq!(ch3, Value::Literal(crate::ast::Literal::Character('b')));
let ch4 = primitive_read_char(&[in_port_value.clone()]).unwrap();
assert_eq!(ch4, Value::Literal(crate::ast::Literal::Character('c')));
let eof = primitive_read_char(&[in_port_value.clone()]).unwrap();
assert!(is_eof_value(&eof));
}
#[test]
fn test_r7rs_port_predicates() {
let string_in_port = Value::Port(Arc::new(Port::new_string_input("test".to_string())));
let string_out_port = Value::Port(Arc::new(Port::new_string_output()));
let bytevector_in_port = Value::Port(Arc::new(Port::new_bytevector_input(vec![1, 2, 3])));
let non_port = Value::integer(42);
assert_eq!(primitive_port_p(&[string_in_port.clone()]).unwrap(), Value::boolean(true));
assert_eq!(primitive_port_p(&[non_port.clone()]).unwrap(), Value::boolean(false));
assert_eq!(primitive_input_port_p(&[string_in_port.clone()]).unwrap(), Value::boolean(true));
assert_eq!(primitive_input_port_p(&[string_out_port.clone()]).unwrap(), Value::boolean(false));
assert_eq!(primitive_output_port_p(&[string_out_port.clone()]).unwrap(), Value::boolean(true));
assert_eq!(primitive_output_port_p(&[string_in_port.clone()]).unwrap(), Value::boolean(false));
assert_eq!(primitive_textual_port_p(&[string_in_port.clone()]).unwrap(), Value::boolean(true));
assert_eq!(primitive_textual_port_p(&[bytevector_in_port.clone()]).unwrap(), Value::boolean(false));
assert_eq!(primitive_binary_port_p(&[bytevector_in_port.clone()]).unwrap(), Value::boolean(true));
assert_eq!(primitive_binary_port_p(&[string_in_port.clone()]).unwrap(), Value::boolean(false));
}
#[test]
fn test_r7rs_character_io_operations() {
let test_string = "Hello\nWorld!";
let in_port = Value::Port(Arc::new(Port::new_string_input(test_string.to_string())));
assert_eq!(primitive_read_char(&[in_port.clone()]).unwrap(),
Value::Literal(crate::ast::Literal::Character('H')));
assert_eq!(primitive_read_char(&[in_port.clone()]).unwrap(),
Value::Literal(crate::ast::Literal::Character('e')));
let peeked = primitive_peek_char(&[in_port.clone()]).unwrap();
let read = primitive_read_char(&[in_port.clone()]).unwrap();
assert_eq!(peeked, read);
assert_eq!(read, Value::Literal(crate::ast::Literal::Character('l')));
assert_eq!(primitive_char_ready_p(&[in_port.clone()]).unwrap(), Value::boolean(true));
let _remaining_chars = primitive_read_char(&[in_port.clone()]).unwrap(); let _remaining_chars = primitive_read_char(&[in_port.clone()]).unwrap(); let _remaining_chars = primitive_read_char(&[in_port.clone()]).unwrap(); let line = primitive_read_line(&[in_port.clone()]).unwrap();
assert_eq!(line.as_string().unwrap(), "World!");
}
#[test]
fn test_r7rs_string_io_operations() {
let test_string = "The quick brown fox";
let in_port = Value::Port(Arc::new(Port::new_string_input(test_string.to_string())));
let result = primitive_read_string(&[Value::integer(3), in_port.clone()]).unwrap();
assert_eq!(result.as_string().unwrap(), "The");
let result = primitive_read_string(&[Value::integer(6), in_port.clone()]).unwrap();
assert_eq!(result.as_string().unwrap(), " quick");
let out_port = Value::Port(Arc::new(Port::new_string_output()));
primitive_write_string(&[Value::string("Testing"), out_port.clone()]).unwrap();
primitive_write_string(&[Value::string(" 123"), out_port.clone(), Value::integer(0), Value::integer(4)]).unwrap();
let result = primitive_get_output_string(&[out_port.clone()]).unwrap();
assert_eq!(result.as_string().unwrap(), "Testing 123");
}
#[test]
fn test_exact_r7rs_small_example() {
let out = Arc::new(Port::new_string_output());
let out_port_value = Value::Port(out.clone());
primitive_write(&[Value::string("hello"), out_port_value.clone()]).unwrap();
primitive_write_char(&[Value::Literal(crate::ast::Literal::Character(' ')), out_port_value.clone()]).unwrap();
primitive_write(&[Value::string("world"), out_port_value.clone()]).unwrap();
let result = primitive_get_output_string(&[out_port_value]).unwrap();
assert_eq!(result.as_string().unwrap(), "\"hello\" \"world\"");
let in_port = Value::Port(Arc::new(Port::new_string_input("abc".to_string())));
let ch1 = primitive_read_char(&[in_port.clone()]).unwrap();
assert_eq!(ch1, Value::Literal(crate::ast::Literal::Character('a')));
let ch2_peek = primitive_peek_char(&[in_port.clone()]).unwrap();
assert_eq!(ch2_peek, Value::Literal(crate::ast::Literal::Character('b')));
let ch2_read = primitive_read_char(&[in_port.clone()]).unwrap();
assert_eq!(ch2_read, Value::Literal(crate::ast::Literal::Character('b')));
assert_eq!(ch2_peek, ch2_read);
}
#[test]
fn test_call_with_input_file_bindings() {
let env = Arc::new(ThreadSafeEnvironment::new(None, 0));
bind_file_operations(&env);
let proc = env.lookup("call-with-input-file").unwrap();
if let Value::Primitive(prim) = proc {
assert_eq!(prim.name, "call-with-input-file");
assert_eq!(prim.arity_min, 2);
assert_eq!(prim.arity_max, Some(2));
} else {
panic!("call-with-input-file should be a primitive procedure");
}
}
#[test]
fn test_call_with_output_file_bindings() {
let env = Arc::new(ThreadSafeEnvironment::new(None, 0));
bind_file_operations(&env);
let proc = env.lookup("call-with-output-file").unwrap();
if let Value::Primitive(prim) = proc {
assert_eq!(prim.name, "call-with-output-file");
assert_eq!(prim.arity_min, 2);
assert_eq!(prim.arity_max, Some(2));
} else {
panic!("call-with-output-file should be a primitive procedure");
}
}
#[test]
fn test_with_input_from_file_bindings() {
let env = Arc::new(ThreadSafeEnvironment::new(None, 0));
bind_file_operations(&env);
let proc = env.lookup("with-input-from-file").unwrap();
if let Value::Primitive(prim) = proc {
assert_eq!(prim.name, "with-input-from-file");
assert_eq!(prim.arity_min, 2);
assert_eq!(prim.arity_max, Some(2));
} else {
panic!("with-input-from-file should be a primitive procedure");
}
}
#[test]
fn test_with_output_to_file_bindings() {
let env = Arc::new(ThreadSafeEnvironment::new(None, 0));
bind_file_operations(&env);
let proc = env.lookup("with-output-to-file").unwrap();
if let Value::Primitive(prim) = proc {
assert_eq!(prim.name, "with-output-to-file");
assert_eq!(prim.arity_min, 2);
assert_eq!(prim.arity_max, Some(2));
} else {
panic!("with-output-to-file should be a primitive procedure");
}
}
#[test]
fn test_call_with_input_file_error_handling() {
let result = primitive_call_with_input_file(&[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expects 2 arguments"));
let result = primitive_call_with_input_file(&[Value::string("test.txt")]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expects 2 arguments"));
let result = primitive_call_with_input_file(&[
Value::integer(42),
Value::string("dummy_procedure")
]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("requires string arguments"));
}
#[test]
fn test_call_with_output_file_error_handling() {
let result = primitive_call_with_output_file(&[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expects 2 arguments"));
let result = primitive_call_with_output_file(&[
Value::integer(42),
Value::string("dummy_procedure")
]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("requires string arguments"));
}
#[test]
fn test_with_input_from_file_error_handling() {
let result = primitive_with_input_from_file(&[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expects 2 arguments"));
let result = primitive_with_input_from_file(&[
Value::integer(42),
Value::string("dummy_thunk")
]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("requires string arguments"));
}
#[test]
fn test_with_output_to_file_error_handling() {
let result = primitive_with_output_to_file(&[]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("expects 2 arguments"));
let result = primitive_with_output_to_file(&[
Value::integer(42),
Value::string("dummy_thunk")
]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("requires string arguments"));
}
#[test]
fn test_file_operations_require_evaluator_support() {
use std::collections::HashMap;
use crate::ast::Formals;
let dummy_procedure = crate::eval::value::Procedure {
formals: Formals::Fixed(vec![]),
body: vec![], environment: Arc::new(ThreadSafeEnvironment::new(None, 0)),
name: Some("dummy".to_string()),
metadata: HashMap::new(),
source: None,
};
let proc_value = Value::Procedure(Arc::new(dummy_procedure));
let result = primitive_call_with_input_file(&[
Value::string("/tmp/test.txt"),
proc_value
]);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("procedure calls require evaluator support"));
}
#[test]
fn test_all_r7rs_file_operations_bound() {
let env = Arc::new(ThreadSafeEnvironment::new(None, 0));
bind_file_operations(&env);
let required_procedures = [
"open-input-file",
"open-output-file",
"close-port",
"close-input-port",
"close-output-port",
"with-input-from-file",
"with-output-to-file",
"call-with-input-file",
"call-with-output-file",
];
for proc_name in &required_procedures {
let value = env.lookup(proc_name);
assert!(value.is_some(), "Procedure {} should be bound", proc_name);
if let Some(Value::Primitive(prim)) = value {
assert_eq!(prim.name, *proc_name);
assert!(prim.arity_min >= 1);
} else {
panic!("Procedure {} should be a primitive", proc_name);
}
}
}
}