use crate::eval::value::{Value, PrimitiveProcedure, PrimitiveImpl, ThreadSafeEnvironment};
use crate::effects::Effect;
use crate::diagnostics::{Error as DiagnosticError, Result};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH, Instant};
static SYSTEM_STATE: OnceLock<Arc<Mutex<SystemState>>> = OnceLock::new();
#[derive(Debug)]
struct SystemState {
command_line_args: Vec<String>,
start_time: Instant,
}
impl SystemState {
fn new() -> Self {
Self {
command_line_args: Vec::new(),
start_time: Instant::now(),
}
}
}
pub fn initialize_system_state(args: Vec<String>) {
let state = Arc::new(Mutex::new(SystemState {
command_line_args: args,
start_time: Instant::now(),
}));
let _ = SYSTEM_STATE.set(state);
}
fn get_system_state() -> Arc<Mutex<SystemState>> {
SYSTEM_STATE.get_or_init(|| {
Arc::new(Mutex::new(SystemState::new()))
}).clone()
}
pub fn create_system_bindings(env: &Arc<ThreadSafeEnvironment>) {
env.define("exit".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "exit".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_exit),
effects: vec![Effect::IO],
})));
env.define("emergency-exit".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "emergency-exit".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_emergency_exit),
effects: vec![Effect::IO],
})));
env.define("command-line".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "command-line".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_command_line),
effects: vec![Effect::Pure],
})));
env.define("get-environment-variable".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "get-environment-variable".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_get_environment_variable),
effects: vec![Effect::IO],
})));
env.define("get-environment-variables".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "get-environment-variables".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_get_environment_variables),
effects: vec![Effect::IO],
})));
env.define("current-second".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "current-second".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_current_second),
effects: vec![Effect::IO],
})));
env.define("current-jiffy".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "current-jiffy".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_current_jiffy),
effects: vec![Effect::IO],
})));
env.define("jiffies-per-second".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "jiffies-per-second".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_jiffies_per_second),
effects: vec![Effect::Pure],
})));
env.define("features".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "features".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_features),
effects: vec![Effect::Pure],
})));
}
pub fn bind_system_procedures_cow(env: &Arc<ThreadSafeEnvironment>) -> Arc<ThreadSafeEnvironment> {
env.define_cow("exit".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "exit".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_exit),
effects: vec![Effect::IO],
})))
.define_cow("emergency-exit".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "emergency-exit".to_string(),
arity_min: 0,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_emergency_exit),
effects: vec![Effect::IO],
})))
.define_cow("command-line".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "command-line".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_command_line),
effects: vec![Effect::Pure],
})))
.define_cow("get-environment-variable".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "get-environment-variable".to_string(),
arity_min: 1,
arity_max: Some(1),
implementation: PrimitiveImpl::RustFn(primitive_get_environment_variable),
effects: vec![Effect::IO],
})))
.define_cow("get-environment-variables".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "get-environment-variables".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_get_environment_variables),
effects: vec![Effect::IO],
})))
.define_cow("current-second".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "current-second".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_current_second),
effects: vec![Effect::IO],
})))
.define_cow("current-jiffy".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "current-jiffy".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_current_jiffy),
effects: vec![Effect::IO],
})))
.define_cow("jiffies-per-second".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "jiffies-per-second".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_jiffies_per_second),
effects: vec![Effect::Pure],
})))
.define_cow("features".to_string(), Value::Primitive(Arc::new(PrimitiveProcedure {
name: "features".to_string(),
arity_min: 0,
arity_max: Some(0),
implementation: PrimitiveImpl::RustFn(primitive_features),
effects: vec![Effect::Pure],
})))
}
pub fn primitive_exit(args: &[Value]) -> Result<Value> {
if args.len() > 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("exit expects 0 or 1 arguments, got {}", args.len()),
None,
)));
}
let exit_code = if args.is_empty() {
0 } else {
match &args[0] {
Value::Literal(crate::ast::Literal::Boolean(true)) => 0,
Value::Literal(crate::ast::Literal::Boolean(false)) => 1,
Value::Literal(literal) if literal.is_number() => {
if let Some(n) = literal.to_f64() {
(n as i64).clamp(0, 255) as i32
} else {
0 }
}
Value::Literal(crate::ast::Literal::Rational { numerator, denominator }) => {
let value = (*numerator as f64 / *denominator as f64) as i64;
value.clamp(0, 255) as i32
}
_ => 1,
}
};
Err(Box::new(DiagnosticError::runtime_error(
format!("Program exit requested with code {exit_code}"),
None,
)))
}
pub fn primitive_emergency_exit(args: &[Value]) -> Result<Value> {
if args.len() > 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("emergency-exit expects 0 or 1 arguments, got {}", args.len()),
None,
)));
}
let exit_code = if args.is_empty() {
0 } else {
match &args[0] {
Value::Literal(crate::ast::Literal::Boolean(true)) => 0,
Value::Literal(crate::ast::Literal::Boolean(false)) => 1,
Value::Literal(literal) if literal.is_number() => {
if let Some(n) = literal.to_f64() {
(n as i64).clamp(0, 255) as i32
} else {
0
}
}
Value::Literal(crate::ast::Literal::Rational { numerator, denominator }) => {
let value = (*numerator as f64 / *denominator as f64) as i64;
value.clamp(0, 255) as i32
}
_ => 1,
}
};
Err(Box::new(DiagnosticError::runtime_error(
format!("Emergency exit requested with code {exit_code}"),
None,
)))
}
pub fn primitive_command_line(_args: &[Value]) -> Result<Value> {
let state = get_system_state();
let state_guard = state.lock().map_err(|_| {
Box::new(DiagnosticError::runtime_error("Failed to access system state".to_string(), None))
})?;
let mut result = Value::Nil;
for arg in state_guard.command_line_args.iter().rev() {
result = Value::Pair(
Arc::new(Value::string(arg.clone())),
Arc::new(result),
);
}
Ok(result)
}
pub fn primitive_get_environment_variable(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Box::new(DiagnosticError::runtime_error(
format!("get-environment-variable expects 1 argument, got {}", args.len()),
None,
)));
}
let var_name = match &args[0] {
Value::Literal(crate::ast::Literal::String(s)) => s.clone(),
_ => {
return Err(Box::new(DiagnosticError::runtime_error(
"get-environment-variable requires a string argument".to_string(),
None,
)));
}
};
match std::env::var(&var_name) {
Ok(value) => Ok(Value::string(value)),
Err(_) => Ok(Value::Literal(crate::ast::Literal::Boolean(false))), }
}
pub fn primitive_get_environment_variables(_args: &[Value]) -> Result<Value> {
let mut result = Value::Nil;
let vars: Vec<_> = std::env::vars().collect();
for (key, value) in vars.into_iter().rev() {
let pair = Value::Pair(
Arc::new(Value::string(key)),
Arc::new(Value::string(value)),
);
result = Value::Pair(Arc::new(pair), Arc::new(result));
}
Ok(result)
}
pub fn primitive_current_second(_args: &[Value]) -> Result<Value> {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(duration) => {
let seconds = duration.as_secs();
let nanos = duration.subsec_nanos();
let fractional = seconds as f64 + (nanos as f64 / 1_000_000_000.0);
Ok(Value::number(fractional))
}
Err(_) => {
Err(Box::new(DiagnosticError::runtime_error(
"Failed to get current time".to_string(),
None,
)))
}
}
}
pub fn primitive_current_jiffy(_args: &[Value]) -> Result<Value> {
let state = get_system_state();
let state_guard = state.lock().map_err(|_| {
Box::new(DiagnosticError::runtime_error("Failed to access system state".to_string(), None))
})?;
let elapsed = state_guard.start_time.elapsed();
let jiffies = elapsed.as_nanos() as i64;
Ok(Value::integer(jiffies))
}
pub fn primitive_jiffies_per_second(_args: &[Value]) -> Result<Value> {
Ok(Value::integer(1_000_000_000))
}
pub fn primitive_features(_args: &[Value]) -> Result<Value> {
let features = vec![
"r7rs", "exact-closed", "exact-complex", "ieee-float", "full-unicode", "ratios",
"lambdust", "gradual-typing", "effect-system", "call/cc", "threads",
#[cfg(unix)]
"posix",
#[cfg(windows)]
"windows",
#[cfg(target_pointer_width = "64")]
"64bit",
#[cfg(target_pointer_width = "32")]
"32bit",
"port-position", "r7rs-io",
"bytevectors", "hashtables", "records", ];
let mut result = Value::Nil;
for feature in features.iter().rev() {
let symbol_id = crate::utils::symbol::intern_symbol(feature.to_string());
result = Value::Pair(
Arc::new(Value::symbol(symbol_id)),
Arc::new(result),
);
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_env() -> Arc<ThreadSafeEnvironment> {
Arc::new(ThreadSafeEnvironment::new(None, 0))
}
#[test]
fn test_system_bindings() {
let env = create_test_env();
create_system_bindings(&env);
let result = primitive_jiffies_per_second(&[]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), Value::integer(1_000_000_000));
let result = primitive_features(&[]);
assert!(result.is_ok());
match result.unwrap() {
Value::Pair(_, _) | Value::Nil => {
}
_ => panic!("features should return a list"),
}
}
#[test]
fn test_command_line() {
initialize_system_state(vec![
"lambdust".to_string(),
"--version".to_string(),
"test.scm".to_string(),
]);
let result = primitive_command_line(&[]).unwrap();
match result {
Value::Pair(car, _) => {
match car.as_ref() {
Value::Literal(crate::ast::Literal::String(s)) => {
assert_eq!(s, "lambdust");
}
_ => panic!("Expected string"),
}
}
Value::Nil => {
}
_ => panic!("Expected list"),
}
}
#[test]
fn test_get_environment_variable() {
let args = vec![Value::string("PATH")];
let result = primitive_get_environment_variable(&args).unwrap();
match result {
Value::Literal(crate::ast::Literal::String(_)) => {
}
Value::Literal(crate::ast::Literal::Boolean(false)) => {
}
_ => panic!("Expected string or #f"),
}
let args = vec![Value::string("NONEXISTENT_VAR_12345")];
let result = primitive_get_environment_variable(&args).unwrap();
assert_eq!(result, Value::Literal(crate::ast::Literal::Boolean(false)));
}
#[test]
fn test_get_environment_variables() {
let result = primitive_get_environment_variables(&[]).unwrap();
match result {
Value::Nil => {
}
Value::Pair(_, _) => {
}
_ => panic!("Expected association list"),
}
}
#[test]
fn test_time_functions() {
let result = primitive_current_second(&[]).unwrap();
match result {
Value::Literal(crate::ast::Literal::Number(seconds)) => {
assert!(seconds > 1_600_000_000.0);
}
_ => panic!("Expected number"),
}
let result = primitive_jiffies_per_second(&[]).unwrap();
assert_eq!(result, Value::integer(1_000_000_000));
let result = primitive_current_jiffy(&[]).unwrap();
match result {
Value::Literal(crate::ast::Literal::Number(_)) => {
}
_ => panic!("Expected number"),
}
}
#[test]
fn test_features() {
let result = primitive_features(&[]).unwrap();
let mut current = &result;
let mut found_r7rs = false;
let mut found_lambdust = false;
while let Value::Pair(car, cdr) = current {
if let Value::Symbol(sym_id) = car.as_ref() {
if let Some(name) = crate::utils::symbol::symbol_name(*sym_id) {
if name == "r7rs" {
found_r7rs = true;
}
if name == "lambdust" {
found_lambdust = true;
}
}
}
current = cdr;
}
assert!(found_r7rs, "Should include 'r7rs' feature");
assert!(found_lambdust, "Should include 'lambdust' feature");
}
#[test]
fn test_exit_semantics() {
let result = primitive_exit(&[]);
assert!(result.is_err());
let args = vec![Value::boolean(true)];
let result = primitive_exit(&args);
assert!(result.is_err());
let args = vec![Value::boolean(false)];
let result = primitive_exit(&args);
assert!(result.is_err());
let args = vec![Value::integer(42)];
let result = primitive_exit(&args);
assert!(result.is_err());
}
#[test]
fn test_error_conditions() {
let result = primitive_get_environment_variable(&[]);
assert!(result.is_err());
let result = primitive_get_environment_variable(&[Value::integer(1), Value::integer(2)]);
assert!(result.is_err());
let result = primitive_get_environment_variable(&[Value::integer(42)]);
assert!(result.is_err());
}
}