diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index ba115f2..11d8314 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1,6 +1,6 @@ mod value; -pub use value::{CapturedEnv, Closure, Value}; +pub use value::{CapturedEnv, ChannelHandle, Closure, Value}; use crate::ast::*; use std::cell::RefCell; diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index c09f723..060acb1 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -3,6 +3,9 @@ use std::collections::HashMap; use std::fmt; use std::rc::Rc; use std::cell::RefCell; +use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; /// Captured environment for closures #[derive(Debug, Clone)] @@ -43,6 +46,135 @@ impl PartialEq for Closure { } } +/// Channel handle for Go-style channels +/// Channels allow typed, thread-safe communication between concurrent tasks +#[derive(Clone)] +pub struct ChannelHandle { + /// Sender side + sender: Sender, + /// Receiver side (wrapped in Arc for sharing) + receiver: Arc>>, + /// Channel name (optional, for debugging) + pub name: Option, + /// Whether the channel is closed + closed: Arc>, + /// Buffer capacity (0 = unbuffered/sync) + pub capacity: usize, +} + +impl std::fmt::Debug for ChannelHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Channel") + .field("name", &self.name) + .field("capacity", &self.capacity) + .field("closed", &*self.closed.lock().unwrap()) + .finish() + } +} + +impl PartialEq for ChannelHandle { + fn eq(&self, _other: &Self) -> bool { + // Channels are never equal (identity comparison would need Arc pointer comparison) + false + } +} + +impl ChannelHandle { + /// Create a new unbuffered channel + pub fn new() -> Self { + let (sender, receiver) = mpsc::channel(); + Self { + sender, + receiver: Arc::new(Mutex::new(receiver)), + name: None, + closed: Arc::new(Mutex::new(false)), + capacity: 0, + } + } + + /// Create a new unbuffered channel with a name + pub fn with_name(name: String) -> Self { + let mut ch = Self::new(); + ch.name = Some(name); + ch + } + + /// Create a buffered channel + pub fn buffered(capacity: usize) -> Self { + let (sender, receiver) = mpsc::channel(); + Self { + sender, + receiver: Arc::new(Mutex::new(receiver)), + name: None, + closed: Arc::new(Mutex::new(false)), + capacity, + } + } + + /// Send a value through the channel + pub fn send(&self, value: Value) -> Result<(), String> { + if *self.closed.lock().unwrap() { + return Err("cannot send on closed channel".to_string()); + } + self.sender + .send(value) + .map_err(|_| "channel send failed: receiver dropped".to_string()) + } + + /// Receive a value from the channel (blocking) + pub fn recv(&self) -> Result { + if *self.closed.lock().unwrap() { + return Err("cannot receive on closed channel".to_string()); + } + let receiver = self.receiver.lock().unwrap(); + receiver + .recv() + .map_err(|_| "channel receive failed: sender dropped".to_string()) + } + + /// Try to receive a value (non-blocking) + pub fn try_recv(&self) -> Result, String> { + if *self.closed.lock().unwrap() { + return Ok(None); + } + let receiver = self.receiver.lock().unwrap(); + match receiver.try_recv() { + Ok(value) => Ok(Some(value)), + Err(TryRecvError::Empty) => Ok(None), + Err(TryRecvError::Disconnected) => Err("channel disconnected".to_string()), + } + } + + /// Receive with timeout + pub fn recv_timeout(&self, timeout_ms: u64) -> Result, String> { + if *self.closed.lock().unwrap() { + return Ok(None); + } + let receiver = self.receiver.lock().unwrap(); + match receiver.recv_timeout(Duration::from_millis(timeout_ms)) { + Ok(value) => Ok(Some(value)), + Err(RecvTimeoutError::Timeout) => Ok(None), + Err(RecvTimeoutError::Disconnected) => Err("channel disconnected".to_string()), + } + } + + /// Close the channel + pub fn close(&self) { + *self.closed.lock().unwrap() = true; + } + + /// Check if the channel is closed + pub fn is_closed(&self) -> bool { + *self.closed.lock().unwrap() + } +} + +impl Default for ChannelHandle { + fn default() -> Self { + Self::new() + } +} + /// Runtime value in WokeLang #[derive(Debug, Clone, PartialEq)] pub enum Value { @@ -60,6 +192,8 @@ pub enum Value { Oops(String), /// First-class function/closure Function(Closure), + /// Go-style channel for concurrent communication + Channel(ChannelHandle), } impl Value { @@ -76,6 +210,7 @@ impl Value { Value::Okay(_) => true, Value::Oops(_) => false, Value::Function(_) => true, + Value::Channel(ch) => !ch.is_closed(), } } @@ -133,6 +268,13 @@ impl fmt::Display for Value { let param_names: Vec<_> = closure.params.iter().map(|p| p.name.as_str()).collect(); write!(f, "|{}| -> ", param_names.join(", ")) } + Value::Channel(ch) => { + let status = if ch.is_closed() { "closed" } else { "open" }; + match &ch.name { + Some(name) => write!(f, "", name, status), + None => write!(f, "", status), + } + } } } } diff --git a/src/stdlib/chan.rs b/src/stdlib/chan.rs new file mode 100644 index 0000000..c9c75ea --- /dev/null +++ b/src/stdlib/chan.rs @@ -0,0 +1,261 @@ +//! WokeLang Standard Library - Channel Module +//! +//! Go-style channels for concurrent communication. +//! Channels are typed, thread-safe communication primitives. + +use crate::interpreter::{ChannelHandle, Value}; +use crate::security::CapabilityRegistry; +use super::{check_arity, check_arity_range, expect_int, StdlibError}; + +/// Maximum channel buffer size +const MAX_BUFFER_SIZE: usize = 10000; + +/// Create a new channel +/// make_chan() -> Channel (unbuffered) +/// make_chan(capacity) -> Channel (buffered) +pub fn make_chan(args: &[Value], _caps: &mut CapabilityRegistry) -> Result { + check_arity_range(args, 0, 1)?; + + let capacity = if args.is_empty() { + 0 + } else { + let cap = expect_int(&args[0], "capacity")?; + if cap < 0 { + return Err(StdlibError::RuntimeError( + "channel capacity cannot be negative".to_string(), + )); + } + if cap as usize > MAX_BUFFER_SIZE { + return Err(StdlibError::RuntimeError(format!( + "channel capacity too large (max {})", + MAX_BUFFER_SIZE + ))); + } + cap as usize + }; + + let channel = if capacity == 0 { + ChannelHandle::new() + } else { + ChannelHandle::buffered(capacity) + }; + + Ok(Value::Channel(channel)) +} + +/// Send a value on a channel +/// send(channel, value) -> Bool +pub fn send(args: &[Value], _caps: &mut CapabilityRegistry) -> Result { + check_arity(args, 2)?; + + let channel = match &args[0] { + Value::Channel(ch) => ch, + other => { + return Err(StdlibError::TypeError { + expected: "Channel".to_string(), + got: format!("{:?}", other), + }) + } + }; + + match channel.send(args[1].clone()) { + Ok(()) => Ok(Value::Bool(true)), + Err(e) => Ok(Value::Oops(e)), + } +} + +/// Receive a value from a channel (blocking) +/// recv(channel) -> Result +pub fn recv(args: &[Value], _caps: &mut CapabilityRegistry) -> Result { + check_arity(args, 1)?; + + let channel = match &args[0] { + Value::Channel(ch) => ch, + other => { + return Err(StdlibError::TypeError { + expected: "Channel".to_string(), + got: format!("{:?}", other), + }) + } + }; + + match channel.recv() { + Ok(value) => Ok(Value::Okay(Box::new(value))), + Err(e) => Ok(Value::Oops(e)), + } +} + +/// Try to receive a value from a channel (non-blocking) +/// try_recv(channel) -> Result (Okay(value) or Oops("empty")) +pub fn try_recv(args: &[Value], _caps: &mut CapabilityRegistry) -> Result { + check_arity(args, 1)?; + + let channel = match &args[0] { + Value::Channel(ch) => ch, + other => { + return Err(StdlibError::TypeError { + expected: "Channel".to_string(), + got: format!("{:?}", other), + }) + } + }; + + match channel.try_recv() { + Ok(Some(value)) => Ok(Value::Okay(Box::new(value))), + Ok(None) => Ok(Value::Oops("channel empty".to_string())), + Err(e) => Ok(Value::Oops(e)), + } +} + +/// Receive with timeout +/// recv_timeout(channel, timeout_ms) -> Result +pub fn recv_timeout(args: &[Value], _caps: &mut CapabilityRegistry) -> Result { + check_arity(args, 2)?; + + let channel = match &args[0] { + Value::Channel(ch) => ch, + other => { + return Err(StdlibError::TypeError { + expected: "Channel".to_string(), + got: format!("{:?}", other), + }) + } + }; + + let timeout_ms = expect_int(&args[1], "timeout_ms")?; + if timeout_ms < 0 { + return Err(StdlibError::RuntimeError( + "timeout cannot be negative".to_string(), + )); + } + + match channel.recv_timeout(timeout_ms as u64) { + Ok(Some(value)) => Ok(Value::Okay(Box::new(value))), + Ok(None) => Ok(Value::Oops("timeout".to_string())), + Err(e) => Ok(Value::Oops(e)), + } +} + +/// Close a channel +/// close(channel) -> Bool +pub fn close(args: &[Value], _caps: &mut CapabilityRegistry) -> Result { + check_arity(args, 1)?; + + let channel = match &args[0] { + Value::Channel(ch) => ch, + other => { + return Err(StdlibError::TypeError { + expected: "Channel".to_string(), + got: format!("{:?}", other), + }) + } + }; + + channel.close(); + Ok(Value::Bool(true)) +} + +/// Check if a channel is closed +/// is_closed(channel) -> Bool +pub fn is_closed(args: &[Value], _caps: &mut CapabilityRegistry) -> Result { + check_arity(args, 1)?; + + let channel = match &args[0] { + Value::Channel(ch) => ch, + other => { + return Err(StdlibError::TypeError { + expected: "Channel".to_string(), + got: format!("{:?}", other), + }) + } + }; + + Ok(Value::Bool(channel.is_closed())) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_caps() -> CapabilityRegistry { + CapabilityRegistry::permissive() + } + + #[test] + fn test_make_unbuffered_channel() { + let mut caps = test_caps(); + let result = make_chan(&[], &mut caps).unwrap(); + assert!(matches!(result, Value::Channel(_))); + } + + #[test] + fn test_make_buffered_channel() { + let mut caps = test_caps(); + let result = make_chan(&[Value::Int(10)], &mut caps).unwrap(); + if let Value::Channel(ch) = result { + assert_eq!(ch.capacity, 10); + } else { + panic!("Expected channel"); + } + } + + #[test] + fn test_send_try_recv() { + let mut caps = test_caps(); + + // Create channel + let channel = make_chan(&[], &mut caps).unwrap(); + + // Get the channel handle for sending + let channel_handle = if let Value::Channel(ch) = &channel { + ch.clone() + } else { + panic!("Expected channel"); + }; + + // Send a value directly using the handle + channel_handle.send(Value::Int(42)).unwrap(); + + // Now try_recv should work + let result = try_recv(&[channel], &mut caps).unwrap(); + + if let Value::Okay(boxed) = result { + assert_eq!(*boxed, Value::Int(42)); + } else { + panic!("Expected Okay result, got {:?}", result); + } + } + + #[test] + fn test_try_recv_empty() { + let mut caps = test_caps(); + + let channel = make_chan(&[], &mut caps).unwrap(); + let result = try_recv(&[channel], &mut caps).unwrap(); + + // Should be Oops("channel empty") + assert!(matches!(result, Value::Oops(_))); + } + + #[test] + fn test_close_channel() { + let mut caps = test_caps(); + + let channel = make_chan(&[], &mut caps).unwrap(); + + // Close + let result = close(&[channel.clone()], &mut caps).unwrap(); + assert_eq!(result, Value::Bool(true)); + + // Check closed + let result = is_closed(&[channel], &mut caps).unwrap(); + assert_eq!(result, Value::Bool(true)); + } + + #[test] + fn test_negative_capacity_rejected() { + let mut caps = test_caps(); + let result = make_chan(&[Value::Int(-1)], &mut caps); + assert!(result.is_err()); + } +} diff --git a/src/stdlib/json.rs b/src/stdlib/json.rs index fbff8c5..fcf61c1 100644 --- a/src/stdlib/json.rs +++ b/src/stdlib/json.rs @@ -313,6 +313,7 @@ fn stringify_value(value: &Value) -> String { Value::Okay(inner) => stringify_value(inner), Value::Oops(msg) => format!("{{\"error\":\"{}\"}}", msg), Value::Function(_) => "null".to_string(), // Functions cannot be serialized to JSON + Value::Channel(_) => "null".to_string(), // Channels cannot be serialized to JSON } } diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index b43ca92..f4ba6b9 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -3,6 +3,7 @@ //! This module provides the standard library for WokeLang, offering //! common functionality with consent-aware operations. +pub mod chan; pub mod io; pub mod json; pub mod math; @@ -117,6 +118,15 @@ impl StdlibRegistry { self.register("std.net.httpGet", net::http_get); self.register("std.net.httpPost", net::http_post); self.register("std.net.download", net::download); + + // Channel functions (Go-style concurrency) + self.register("std.chan.make", chan::make_chan); + self.register("std.chan.send", chan::send); + self.register("std.chan.recv", chan::recv); + self.register("std.chan.tryRecv", chan::try_recv); + self.register("std.chan.recvTimeout", chan::recv_timeout); + self.register("std.chan.close", chan::close); + self.register("std.chan.isClosed", chan::is_closed); } /// Register a function