use crate::arena::*; use crate::atom_table::*; use crate::parser::ast::*; use crate::parser::char_reader::*; use crate::read::*; #[cfg(feature = "http")] use crate::http::HttpResponse; use crate::machine::heap::*; use crate::machine::machine_errors::*; use crate::machine::machine_indices::*; use crate::machine::machine_state::*; use crate::types::*; pub use scryer_modular_bitfield::prelude::*; #[cfg(feature = "http")] use bytes::{buf::Reader as BufReader, Buf, Bytes}; use std::cmp::Ordering; use std::error::Error; use std::fmt; use std::fmt::Debug; use std::fs::{File, OpenOptions}; use std::hash::Hash; use std::io; use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write}; use std::mem::ManuallyDrop; use std::net::{Shutdown, TcpStream}; use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use std::ptr; use std::sync::mpsc::Receiver; use std::sync::mpsc::TryRecvError; #[cfg(feature = "tls")] use native_tls::TlsStream; #[cfg(feature = "http")] use warp::hyper; #[derive(Debug, BitfieldSpecifier, Clone, Copy, PartialEq, Eq, Hash)] #[bits = 1] pub enum StreamType { Binary, Text, } impl StreamType { #[inline] pub(crate) fn as_atom(&self) -> Atom { match self { StreamType::Binary => atom!("binary_stream"), StreamType::Text => atom!("text_stream"), } } #[inline] pub(crate) fn as_property_atom(&self) -> Atom { match self { StreamType::Binary => atom!("binary"), StreamType::Text => atom!("text"), } } #[inline] pub(crate) fn other(self) -> StreamType { match self { StreamType::Binary => StreamType::Text, StreamType::Text => StreamType::Binary, } } } #[derive(Debug, BitfieldSpecifier, Clone, Copy, PartialEq, Eq, Hash)] #[bits = 2] pub enum EOFAction { EOFCode, Error, Reset, } #[derive(Debug, BitfieldSpecifier, Copy, Clone, PartialEq)] #[bits = 2] pub(crate) enum AtEndOfStream { Not, At, Past, } impl AtEndOfStream { #[inline] pub(crate) fn as_atom(&self) -> Atom { match self { AtEndOfStream::Not => atom!("not"), AtEndOfStream::Past => atom!("past"), AtEndOfStream::At => atom!("at"), } } } impl EOFAction { #[inline] pub(crate) fn as_atom(&self) -> Atom { match self { EOFAction::EOFCode => atom!("eof_code"), EOFAction::Error => atom!("error"), EOFAction::Reset => atom!("reset"), } } } #[derive(Debug)] pub struct ByteStream(Cursor>); impl ByteStream { #[inline(always)] pub fn from_string(string: String) -> Self { ByteStream(Cursor::new(string.into())) } } impl Read for ByteStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.0.read(buf) } } impl Write for ByteStream { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { let pos = self.0.position(); self.0.seek(SeekFrom::End(0))?; let result = self.0.write(buf); self.0.seek(SeekFrom::Start(pos))?; result } #[inline] fn flush(&mut self) -> std::io::Result<()> { self.0.flush() } } #[derive(Debug)] pub struct InputFileStream { file_name: Atom, file: File, } impl Read for InputFileStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.file.read(buf) } } impl StreamLayout> { #[inline] fn position(&mut self) -> Option { // stream is the internal CharReader. subtract // its pending buffer length from position. self.get_mut() .file .stream_position() .map(|pos| pos - self.stream.rem_buf_len() as u64) .ok() } } #[derive(Debug)] pub struct OutputFileStream { file_name: Atom, file: File, is_append: bool, } impl Write for OutputFileStream { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { self.file.write(buf) } #[inline] fn flush(&mut self) -> std::io::Result<()> { self.file.flush() } } #[derive(Debug)] pub struct StaticStringStream { stream: Cursor<&'static str>, } impl Read for StaticStringStream { #[inline(always)] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.stream.read(buf) } } impl CharRead for StaticStringStream { #[inline(always)] fn peek_char(&mut self) -> Option> { let pos = self.stream.position() as usize; self.stream.get_ref()[pos..].chars().next().map(Ok) } #[inline(always)] fn consume(&mut self, nread: usize) { self.stream.seek(SeekFrom::Current(nread as i64)).unwrap(); } #[inline(always)] fn put_back_char(&mut self, c: char) { self.stream .seek(SeekFrom::Current(-(c.len_utf8() as i64))) .unwrap(); } } #[derive(Debug)] pub struct NamedTcpStream { address: Atom, tcp_stream: TcpStream, } impl Read for NamedTcpStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.tcp_stream.read(buf) } } impl Write for NamedTcpStream { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { self.tcp_stream.write(buf) } #[inline] fn flush(&mut self) -> std::io::Result<()> { self.tcp_stream.flush() } } #[cfg(feature = "tls")] #[derive(Debug)] pub struct NamedTlsStream { address: Atom, tls_stream: TlsStream, } #[cfg(feature = "tls")] impl Read for NamedTlsStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.tls_stream.read(buf) } } #[cfg(feature = "tls")] impl Write for NamedTlsStream { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { self.tls_stream.write(buf) } #[inline] fn flush(&mut self) -> std::io::Result<()> { self.tls_stream.flush() } } #[cfg(feature = "http")] pub struct HttpReadStream { url: Atom, body_reader: BufReader, } #[cfg(feature = "http")] impl Debug for HttpReadStream { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Http Read Stream [{}]", self.url.as_str()) } } #[cfg(feature = "http")] impl Read for HttpReadStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.body_reader.read(buf) } } #[cfg(feature = "http")] pub struct HttpWriteStream { status_code: u16, headers: std::mem::ManuallyDrop, response: TypedArenaPtr, buffer: std::mem::ManuallyDrop>, } #[cfg(feature = "http")] impl Debug for HttpWriteStream { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Http Write Stream") } } #[cfg(feature = "http")] impl Write for HttpWriteStream { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { self.buffer.extend_from_slice(buf); Ok(buf.len()) } #[inline] fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } #[cfg(feature = "http")] impl HttpWriteStream { // TODO why is this suddenly dead code and should it be used somewhere? // Should this be impl Drop for HttpWriteStream? #[allow(dead_code)] fn drop(&mut self) { let headers = unsafe { std::mem::ManuallyDrop::take(&mut self.headers) }; let buffer = unsafe { std::mem::ManuallyDrop::take(&mut self.buffer) }; let (ready, response, cvar) = &**self.response; let mut ready = ready.lock().unwrap(); { let mut response = response.lock().unwrap(); let mut response_ = warp::http::Response::builder().status(self.status_code); *response_.headers_mut().unwrap() = headers; *response = Some(response_.body(warp::hyper::Body::from(buffer)).unwrap()); } *ready = true; cvar.notify_one(); } } #[derive(Debug)] pub struct StandardOutputStream {} impl Write for StandardOutputStream { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { io::stdout().write(buf) } #[inline] fn flush(&mut self) -> std::io::Result<()> { io::stdout().flush() } } #[derive(Debug)] pub struct StandardErrorStream {} impl Write for StandardErrorStream { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { io::stderr().write(buf) } #[inline] fn flush(&mut self) -> std::io::Result<()> { io::stderr().flush() } } pub type Callback = Box>)>; pub struct CallbackStream { pub(crate) inner: Cursor>, callback: Callback, } impl Debug for CallbackStream { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CallbackStream") .field("inner", &self.inner) .finish() } } impl Write for CallbackStream { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { let pos = self.inner.position(); self.inner.seek(SeekFrom::End(0))?; let result = self.inner.write(buf); self.inner.seek(SeekFrom::Start(pos))?; result } #[inline] fn flush(&mut self) -> std::io::Result<()> { (self.callback)(&mut self.inner); self.inner.flush() } } #[derive(Debug)] pub struct InputChannelStream { pub(crate) inner: Cursor>, pub eof: bool, channel: Receiver>, } impl Read for InputChannelStream { #[inline] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { if self.eof { return Ok(0); } let to_read = buf.len(); let mut total_read = 0; loop { total_read += self.inner.read(&mut buf[total_read..])?; if total_read < to_read { // We need to get more data to read match self.channel.try_recv() { Ok(data) => { // Append into self.inner let pos = self.inner.position(); assert_eq!(pos as usize, self.inner.get_ref().len()); self.inner.write_all(&data)?; self.inner.seek(SeekFrom::Start(pos))?; } Err(TryRecvError::Empty) => { // Data is pending break; } Err(TryRecvError::Disconnected) => { // The other end of the channel was closed so we are EOF self.eof = true; break; } } } else { assert_eq!(total_read, to_read); break; } } Ok(total_read) } } #[bitfield] #[repr(u64)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct StreamOptions { pub stream_type: StreamType, pub reposition: bool, pub eof_action: EOFAction, pub has_alias: bool, pub alias: B59, } impl StreamOptions { #[inline] pub fn get_alias(self) -> Option { if self.has_alias() { Some(Atom::from(self.alias() << 3)) } else { None } } #[inline] pub fn set_alias_to_atom_opt(&mut self, alias: Option) { self.set_has_alias(alias.is_some()); if let Some(alias) = alias { self.set_alias(alias.flat_index()); } } } impl Default for StreamOptions { #[inline] fn default() -> Self { StreamOptions::new() .with_stream_type(StreamType::Text) .with_reposition(false) .with_eof_action(EOFAction::EOFCode) .with_has_alias(false) .with_alias(0) } } #[derive(Debug, Copy, Clone)] pub struct StreamLayout { pub options: StreamOptions, pub lines_read: usize, past_end_of_stream: bool, stream: T, } impl StreamLayout { #[inline] pub fn new(stream: T) -> Self { Self { options: StreamOptions::default(), lines_read: 0, past_end_of_stream: false, stream, } } } impl Deref for StreamLayout { type Target = T; fn deref(&self) -> &Self::Target { &self.stream } } impl DerefMut for StreamLayout { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stream } } macro_rules! arena_allocated_impl_for_stream { ($stream_type:ty, $stream_tag:ident) => { impl $crate::arena::AllocateInArena<$stream_tag> for StreamLayout<$stream_type> { fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<$stream_tag> { $stream_tag::alloc(arena, core::mem::ManuallyDrop::new(self)) } } impl ArenaAllocated for $stream_tag { type Payload = core::mem::ManuallyDrop>; #[inline] fn tag() -> ArenaHeaderTag { ArenaHeaderTag::$stream_tag } unsafe fn dealloc(ptr: std::ptr::NonNull>) { let mut slab = unsafe { Box::from_raw(ptr.as_ptr()) }; match slab.tag() { ArenaHeaderTag::$stream_tag => { unsafe { std::mem::ManuallyDrop::drop(slab.payload()) }; } ArenaHeaderTag::Dropped => {} _ => { unreachable!() } } drop(slab); } } }; } arena_allocated_impl_for_stream!(CharReader, ByteStream); arena_allocated_impl_for_stream!(CharReader, InputFileStream); arena_allocated_impl_for_stream!(OutputFileStream, OutputFileStream); arena_allocated_impl_for_stream!(CharReader, NamedTcpStream); #[cfg(feature = "tls")] arena_allocated_impl_for_stream!(CharReader, NamedTlsStream); #[cfg(feature = "http")] arena_allocated_impl_for_stream!(CharReader, HttpReadStream); #[cfg(feature = "http")] arena_allocated_impl_for_stream!(CharReader, HttpWriteStream); arena_allocated_impl_for_stream!(ReadlineStream, ReadlineStream); arena_allocated_impl_for_stream!(StaticStringStream, StaticStringStream); arena_allocated_impl_for_stream!(StandardOutputStream, StandardOutputStream); arena_allocated_impl_for_stream!(StandardErrorStream, StandardErrorStream); arena_allocated_impl_for_stream!(CharReader, CallbackStream); arena_allocated_impl_for_stream!(CharReader, InputChannelStream); #[derive(Debug, Copy, Clone)] pub enum Stream { Byte(TypedArenaPtr), InputFile(TypedArenaPtr), OutputFile(TypedArenaPtr), StaticString(TypedArenaPtr), NamedTcp(TypedArenaPtr), #[cfg(feature = "tls")] NamedTls(TypedArenaPtr), #[cfg(feature = "http")] HttpRead(TypedArenaPtr), #[cfg(feature = "http")] HttpWrite(TypedArenaPtr), Null(StreamOptions), Readline(TypedArenaPtr), StandardOutput(TypedArenaPtr), StandardError(TypedArenaPtr), Callback(TypedArenaPtr), InputChannel(TypedArenaPtr), } impl From> for Stream { #[inline] fn from(stream: TypedArenaPtr) -> Stream { Stream::Readline(stream) } } impl Stream { #[inline] pub fn from_owned_string(string: String, arena: &mut Arena) -> Stream { Stream::Byte(arena_alloc!( StreamLayout::new(CharReader::new(ByteStream(Cursor::new( string.into_bytes() )))), arena )) } #[inline] pub fn from_static_string(src: &'static str, arena: &mut Arena) -> Stream { Stream::StaticString(arena_alloc!( StreamLayout::new(StaticStringStream { stream: Cursor::new(src) }), arena )) } #[inline] pub fn input_channel(channel: Receiver>, arena: &mut Arena) -> Stream { let inner = Cursor::new(Vec::new()); Stream::InputChannel(arena_alloc!( StreamLayout::new(CharReader::new(InputChannelStream { inner, eof: false, channel })), arena )) } #[inline] pub fn stdin(arena: &mut Arena, add_history: bool) -> Stream { Stream::Readline(arena_alloc!( StreamLayout::new(ReadlineStream::new("", add_history)), arena )) } pub fn from_tag(tag: ArenaHeaderTag, ptr: UntypedArenaPtr) -> Self { match tag { ArenaHeaderTag::ByteStream => Stream::Byte(unsafe { ptr.as_typed_ptr() }), ArenaHeaderTag::InputFileStream => Stream::InputFile(unsafe { ptr.as_typed_ptr() }), ArenaHeaderTag::OutputFileStream => Stream::OutputFile(unsafe { ptr.as_typed_ptr() }), ArenaHeaderTag::NamedTcpStream => Stream::NamedTcp(unsafe { ptr.as_typed_ptr() }), #[cfg(feature = "tls")] ArenaHeaderTag::NamedTlsStream => Stream::NamedTls(unsafe { ptr.as_typed_ptr() }), #[cfg(feature = "http")] ArenaHeaderTag::HttpReadStream => Stream::HttpRead(unsafe { ptr.as_typed_ptr() }), #[cfg(feature = "http")] ArenaHeaderTag::HttpWriteStream => Stream::HttpWrite(unsafe { ptr.as_typed_ptr() }), ArenaHeaderTag::ReadlineStream => Stream::Readline(unsafe { ptr.as_typed_ptr() }), ArenaHeaderTag::StaticStringStream => { Stream::StaticString(unsafe { ptr.as_typed_ptr() }) } ArenaHeaderTag::StandardOutputStream => { Stream::StandardOutput(unsafe { ptr.as_typed_ptr() }) } ArenaHeaderTag::StandardErrorStream => { Stream::StandardError(unsafe { ptr.as_typed_ptr() }) } ArenaHeaderTag::Dropped | ArenaHeaderTag::NullStream => { Stream::Null(StreamOptions::default()) } ArenaHeaderTag::CallbackStream => Stream::Callback(unsafe { ptr.as_typed_ptr() }), ArenaHeaderTag::InputChannelStream => { Stream::InputChannel(unsafe { ptr.as_typed_ptr() }) } _ => unreachable!(), } } #[inline] pub fn is_stderr(&self) -> bool { matches!(self, Stream::StandardError(_)) } #[inline] pub fn is_stdout(&self) -> bool { matches!(self, Stream::StandardOutput(_)) } #[inline] pub fn is_stdin(&self) -> bool { matches!(self, Stream::Readline(_)) } pub fn as_ptr(&self) -> *const ArenaHeader { match self { Stream::Byte(ptr) => ptr.header_ptr(), Stream::InputFile(ptr) => ptr.header_ptr(), Stream::OutputFile(ptr) => ptr.header_ptr(), Stream::StaticString(ptr) => ptr.header_ptr(), Stream::NamedTcp(ptr) => ptr.header_ptr(), #[cfg(feature = "tls")] Stream::NamedTls(ptr) => ptr.header_ptr(), #[cfg(feature = "http")] Stream::HttpRead(ptr) => ptr.header_ptr(), #[cfg(feature = "http")] Stream::HttpWrite(ptr) => ptr.header_ptr(), Stream::Null(_) => ptr::null(), Stream::Readline(ptr) => ptr.header_ptr(), Stream::StandardOutput(ptr) => ptr.header_ptr(), Stream::StandardError(ptr) => ptr.header_ptr(), Stream::Callback(ptr) => ptr.header_ptr(), Stream::InputChannel(ptr) => ptr.header_ptr(), } } pub fn options(&self) -> &StreamOptions { match self { Stream::Byte(ref ptr) => &ptr.options, Stream::InputFile(ref ptr) => &ptr.options, Stream::OutputFile(ref ptr) => &ptr.options, Stream::StaticString(ref ptr) => &ptr.options, Stream::NamedTcp(ref ptr) => &ptr.options, #[cfg(feature = "tls")] Stream::NamedTls(ref ptr) => &ptr.options, #[cfg(feature = "http")] Stream::HttpRead(ref ptr) => &ptr.options, #[cfg(feature = "http")] Stream::HttpWrite(ref ptr) => &ptr.options, Stream::Null(ref options) => options, Stream::Readline(ref ptr) => &ptr.options, Stream::StandardOutput(ref ptr) => &ptr.options, Stream::StandardError(ref ptr) => &ptr.options, Stream::Callback(ref ptr) => &ptr.options, Stream::InputChannel(ref ptr) => &ptr.options, } } pub(super) fn options_mut(&mut self) -> &mut StreamOptions { match self { Stream::Byte(ref mut ptr) => &mut ptr.options, Stream::InputFile(ref mut ptr) => &mut ptr.options, Stream::OutputFile(ref mut ptr) => &mut ptr.options, Stream::StaticString(ref mut ptr) => &mut ptr.options, Stream::NamedTcp(ref mut ptr) => &mut ptr.options, #[cfg(feature = "tls")] Stream::NamedTls(ref mut ptr) => &mut ptr.options, #[cfg(feature = "http")] Stream::HttpRead(ref mut ptr) => &mut ptr.options, #[cfg(feature = "http")] Stream::HttpWrite(ref mut ptr) => &mut ptr.options, Stream::Null(ref mut options) => options, Stream::Readline(ref mut ptr) => &mut ptr.options, Stream::StandardOutput(ref mut ptr) => &mut ptr.options, Stream::StandardError(ref mut ptr) => &mut ptr.options, Stream::Callback(ref mut ptr) => &mut ptr.options, Stream::InputChannel(ref mut ptr) => &mut ptr.options, } } #[inline] pub(crate) fn add_lines_read(&mut self, incr_num_lines_read: usize) { match self { Stream::Byte(ptr) => ptr.lines_read += incr_num_lines_read, Stream::InputFile(ptr) => ptr.lines_read += incr_num_lines_read, Stream::OutputFile(ptr) => ptr.lines_read += incr_num_lines_read, Stream::StaticString(ptr) => ptr.lines_read += incr_num_lines_read, Stream::NamedTcp(ptr) => ptr.lines_read += incr_num_lines_read, #[cfg(feature = "tls")] Stream::NamedTls(ptr) => ptr.lines_read += incr_num_lines_read, #[cfg(feature = "http")] Stream::HttpRead(ptr) => ptr.lines_read += incr_num_lines_read, #[cfg(feature = "http")] Stream::HttpWrite(_) => {} Stream::Null(_) => {} Stream::Readline(ptr) => ptr.lines_read += incr_num_lines_read, Stream::StandardOutput(ptr) => ptr.lines_read += incr_num_lines_read, Stream::StandardError(ptr) => ptr.lines_read += incr_num_lines_read, Stream::Callback(ptr) => ptr.lines_read += incr_num_lines_read, Stream::InputChannel(ptr) => ptr.lines_read += incr_num_lines_read, } } #[inline] pub(crate) fn set_lines_read(&mut self, value: usize) { match self { Stream::Byte(ptr) => ptr.lines_read = value, Stream::InputFile(ptr) => ptr.lines_read = value, Stream::OutputFile(ptr) => ptr.lines_read = value, Stream::StaticString(ptr) => ptr.lines_read = value, Stream::NamedTcp(ptr) => ptr.lines_read = value, #[cfg(feature = "tls")] Stream::NamedTls(ptr) => ptr.lines_read = value, #[cfg(feature = "http")] Stream::HttpRead(ptr) => ptr.lines_read = value, #[cfg(feature = "http")] Stream::HttpWrite(_) => {} Stream::Null(_) => {} Stream::Readline(ptr) => ptr.lines_read = value, Stream::StandardOutput(ptr) => ptr.lines_read = value, Stream::StandardError(ptr) => ptr.lines_read = value, Stream::Callback(ptr) => ptr.lines_read = value, Stream::InputChannel(ptr) => ptr.lines_read = value, } } #[inline] pub(crate) fn lines_read(&self) -> usize { match self { Stream::Byte(ptr) => ptr.lines_read, Stream::InputFile(ptr) => ptr.lines_read, Stream::OutputFile(ptr) => ptr.lines_read, Stream::StaticString(ptr) => ptr.lines_read, Stream::NamedTcp(ptr) => ptr.lines_read, #[cfg(feature = "tls")] Stream::NamedTls(ptr) => ptr.lines_read, #[cfg(feature = "http")] Stream::HttpRead(ptr) => ptr.lines_read, #[cfg(feature = "http")] Stream::HttpWrite(_) => 0, Stream::Null(_) => 0, Stream::Readline(ptr) => ptr.lines_read, Stream::StandardOutput(ptr) => ptr.lines_read, Stream::StandardError(ptr) => ptr.lines_read, Stream::Callback(ptr) => ptr.lines_read, Stream::InputChannel(ptr) => ptr.lines_read, } } } impl CharRead for Stream { fn peek_char(&mut self) -> Option> { match self { Stream::InputFile(file) => (*file).peek_char(), Stream::NamedTcp(tcp_stream) => (*tcp_stream).peek_char(), #[cfg(feature = "tls")] Stream::NamedTls(tls_stream) => (*tls_stream).peek_char(), #[cfg(feature = "http")] Stream::HttpRead(http_stream) => (*http_stream).peek_char(), Stream::Readline(rl_stream) => (*rl_stream).peek_char(), Stream::StaticString(src) => (*src).peek_char(), Stream::Byte(cursor) => (*cursor).peek_char(), Stream::InputChannel(cursor) => (*cursor).peek_char(), #[cfg(feature = "http")] Stream::HttpWrite(_) => Some(Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::ReadFromOutputStream, ))), Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::Null(_) | Stream::Callback(_) => Some(Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::ReadFromOutputStream, ))), } } fn read_char(&mut self) -> Option> { match self { Stream::InputFile(file) => (*file).read_char(), Stream::NamedTcp(tcp_stream) => (*tcp_stream).read_char(), #[cfg(feature = "tls")] Stream::NamedTls(tls_stream) => (*tls_stream).read_char(), #[cfg(feature = "http")] Stream::HttpRead(http_stream) => (*http_stream).read_char(), Stream::Readline(rl_stream) => (*rl_stream).read_char(), Stream::StaticString(src) => (*src).read_char(), Stream::Byte(cursor) => (*cursor).read_char(), Stream::InputChannel(cursor) => (*cursor).read_char(), #[cfg(feature = "http")] Stream::HttpWrite(_) => Some(Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::ReadFromOutputStream, ))), Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::Null(_) | Stream::Callback(_) => Some(Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::ReadFromOutputStream, ))), } } fn put_back_char(&mut self, c: char) { match self { Stream::InputFile(file) => file.put_back_char(c), Stream::NamedTcp(tcp_stream) => tcp_stream.put_back_char(c), #[cfg(feature = "tls")] Stream::NamedTls(tls_stream) => tls_stream.put_back_char(c), #[cfg(feature = "http")] Stream::HttpRead(http_stream) => http_stream.put_back_char(c), Stream::Readline(rl_stream) => rl_stream.put_back_char(c), Stream::StaticString(src) => src.put_back_char(c), Stream::Byte(cursor) => cursor.put_back_char(c), #[cfg(feature = "http")] Stream::HttpWrite(_) => {} Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::Null(_) | Stream::Callback(_) => {} Stream::InputChannel(_) => {} } } fn consume(&mut self, nread: usize) { match self { Stream::InputFile(ref mut file) => file.consume(nread), Stream::NamedTcp(ref mut tcp_stream) => tcp_stream.consume(nread), #[cfg(feature = "tls")] Stream::NamedTls(ref mut tls_stream) => tls_stream.consume(nread), #[cfg(feature = "http")] Stream::HttpRead(ref mut http_stream) => http_stream.consume(nread), Stream::Readline(ref mut rl_stream) => rl_stream.consume(nread), Stream::StaticString(ref mut src) => src.consume(nread), Stream::Byte(ref mut cursor) => cursor.consume(nread), Stream::InputChannel(ref mut cursor) => cursor.consume(nread), #[cfg(feature = "http")] Stream::HttpWrite(_) => {} Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::Null(_) | Stream::Callback(_) => {} } } } impl Read for Stream { #[inline] fn read(&mut self, buf: &mut [u8]) -> std::io::Result { match self { Stream::InputFile(file) => (*file).read(buf), Stream::NamedTcp(tcp_stream) => (*tcp_stream).read(buf), #[cfg(feature = "tls")] Stream::NamedTls(tls_stream) => (*tls_stream).read(buf), #[cfg(feature = "http")] Stream::HttpRead(http_stream) => (*http_stream).read(buf), Stream::Readline(rl_stream) => (*rl_stream).read(buf), Stream::StaticString(src) => (*src).read(buf), Stream::Byte(cursor) => (*cursor).read(buf), Stream::InputChannel(cursor) => (*cursor).read(buf), #[cfg(feature = "http")] Stream::HttpWrite(_) => Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::ReadFromOutputStream, )), Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::Null(_) | Stream::Callback(_) => Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::ReadFromOutputStream, )), } } } impl Write for Stream { fn write(&mut self, buf: &[u8]) -> std::io::Result { match self { Stream::OutputFile(ref mut file) => file.write(buf), Stream::NamedTcp(ref mut tcp_stream) => tcp_stream.get_mut().write(buf), #[cfg(feature = "tls")] Stream::NamedTls(ref mut tls_stream) => tls_stream.get_mut().write(buf), Stream::Byte(ref mut cursor) => cursor.get_mut().write(buf), Stream::Callback(ref mut callback_stream) => callback_stream.get_mut().write(buf), Stream::StandardOutput(stream) => stream.write(buf), Stream::StandardError(stream) => stream.write(buf), #[cfg(feature = "http")] Stream::HttpWrite(ref mut stream) => stream.get_mut().write(buf), #[cfg(feature = "http")] Stream::HttpRead(_) => Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::WriteToInputStream, )), Stream::StaticString(_) | Stream::InputChannel(_) | Stream::Readline(_) | Stream::InputFile(..) | Stream::Null(_) => Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::WriteToInputStream, )), } } fn flush(&mut self) -> std::io::Result<()> { match self { Stream::OutputFile(ref mut file) => file.stream.flush(), Stream::NamedTcp(ref mut tcp_stream) => tcp_stream.stream.get_mut().flush(), #[cfg(feature = "tls")] Stream::NamedTls(ref mut tls_stream) => tls_stream.stream.get_mut().flush(), Stream::Byte(ref mut cursor) => cursor.stream.get_mut().flush(), Stream::Callback(ref mut callback_stream) => callback_stream.stream.get_mut().flush(), Stream::StandardError(stream) => stream.stream.flush(), Stream::StandardOutput(stream) => stream.stream.flush(), #[cfg(feature = "http")] Stream::HttpWrite(ref mut stream) => stream.stream.get_mut().flush(), #[cfg(feature = "http")] Stream::HttpRead(_) => Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::FlushToInputStream, )), Stream::StaticString(_) | Stream::InputChannel(_) | Stream::Readline(_) | Stream::InputFile(_) | Stream::Null(_) => Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::FlushToInputStream, )), } } } #[derive(Debug)] enum StreamError { PeekByteFailed, PeekByteFromNonPeekableStream, #[allow(unused)] PeekCharFailed, #[allow(unused)] PeekCharFromNonPeekableStream, ReadFromOutputStream, WriteToInputStream, FlushToInputStream, } impl fmt::Display for StreamError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { StreamError::PeekByteFailed => { write!(f, "peek byte failed!") } StreamError::PeekByteFromNonPeekableStream => { write!(f, "attempted to peek byte from a non-peekable input stream") } StreamError::PeekCharFailed => { write!(f, "peek char failed!") } StreamError::PeekCharFromNonPeekableStream => { write!(f, "attempted to peek char from a non-peekable input stream") } StreamError::ReadFromOutputStream => { write!(f, "attempted to read from a write-only stream") } StreamError::WriteToInputStream => { write!(f, "attempted to write to a read-only stream") } StreamError::FlushToInputStream => { write!(f, "attempted to flush a read-only stream") } } } } impl Error for StreamError {} impl PartialOrd for Stream { #[inline] fn partial_cmp(&self, other: &Stream) -> Option { Some(self.cmp(other)) } } impl Ord for Stream { #[inline] fn cmp(&self, other: &Stream) -> Ordering { self.as_ptr().cmp(&other.as_ptr()) } } impl PartialEq for Stream { #[inline] fn eq(&self, other: &Self) -> bool { self.as_ptr() == other.as_ptr() } } impl Eq for Stream {} fn cursor_position( past_end_of_stream: &mut bool, cursor: &Cursor, cursor_len: u64, ) -> AtEndOfStream { match cursor.position().cmp(&cursor_len) { Ordering::Equal => AtEndOfStream::At, Ordering::Greater => { *past_end_of_stream = true; AtEndOfStream::Past } Ordering::Less => AtEndOfStream::Not, } } impl Stream { #[inline] pub(crate) fn position(&mut self) -> Option<(u64, usize)> { // returns lines_read, position. let result = match self { Stream::Byte(byte_stream_layout) => { Some(byte_stream_layout.stream.get_ref().0.position()) } Stream::StaticString(string_stream_layout) => { Some(string_stream_layout.stream.stream.position()) } Stream::InputFile(file_stream) => file_stream.position(), #[cfg(feature = "tls")] Stream::NamedTls(..) => Some(0), Stream::NamedTcp(..) | Stream::Readline(..) => Some(0), _ => None, }; result.map(|position| (position, self.lines_read())) } #[inline] pub(crate) fn set_position(&mut self, position: u64) { if let Stream::InputFile(stream_layout) = self { let StreamLayout { past_end_of_stream, stream, .. } = &mut ***stream_layout; stream .get_mut() .file .seek(SeekFrom::Start(position)) .unwrap(); stream.reset_buffer(); // flush the internal buffer. if let Ok(metadata) = stream.get_ref().file.metadata() { *past_end_of_stream = position > metadata.len(); } } } #[inline] pub(crate) fn past_end_of_stream(&self) -> bool { match self { Stream::Byte(stream) => stream.past_end_of_stream, Stream::InputFile(stream) => stream.past_end_of_stream, Stream::OutputFile(stream) => stream.past_end_of_stream, Stream::StaticString(stream) => stream.past_end_of_stream, Stream::NamedTcp(stream) => stream.past_end_of_stream, #[cfg(feature = "tls")] Stream::NamedTls(stream) => stream.past_end_of_stream, #[cfg(feature = "http")] Stream::HttpRead(stream) => stream.past_end_of_stream, #[cfg(feature = "http")] Stream::HttpWrite(stream) => stream.past_end_of_stream, Stream::Null(_) => false, Stream::Readline(stream) => stream.past_end_of_stream, Stream::StandardOutput(stream) => stream.past_end_of_stream, Stream::StandardError(stream) => stream.past_end_of_stream, Stream::Callback(stream) => stream.past_end_of_stream, Stream::InputChannel(stream) => stream.past_end_of_stream, } } #[inline] pub(crate) fn at_end_of_stream(&mut self) -> bool { self.position_relative_to_end() == AtEndOfStream::At } #[inline] pub(crate) fn set_past_end_of_stream(&mut self, value: bool) { match self { Stream::Byte(stream) => stream.past_end_of_stream = value, Stream::InputFile(stream) => stream.past_end_of_stream = value, Stream::OutputFile(stream) => stream.past_end_of_stream = value, Stream::StaticString(stream) => stream.past_end_of_stream = value, Stream::NamedTcp(stream) => stream.past_end_of_stream = value, #[cfg(feature = "tls")] Stream::NamedTls(stream) => stream.past_end_of_stream = value, #[cfg(feature = "http")] Stream::HttpRead(stream) => stream.past_end_of_stream = value, #[cfg(feature = "http")] Stream::HttpWrite(stream) => stream.past_end_of_stream = value, Stream::Null(_) => {} Stream::Readline(stream) => stream.past_end_of_stream = value, Stream::StandardOutput(stream) => stream.past_end_of_stream = value, Stream::StandardError(stream) => stream.past_end_of_stream = value, Stream::Callback(stream) => stream.past_end_of_stream = value, Stream::InputChannel(stream) => stream.past_end_of_stream = value, } } #[inline] pub(crate) fn position_relative_to_end(&mut self) -> AtEndOfStream { if self.past_end_of_stream() { return AtEndOfStream::Past; } match self { Stream::Byte(stream_layout) => { let StreamLayout { past_end_of_stream, stream, .. } = &mut ***stream_layout; let cursor_len = stream.get_ref().0.get_ref().len() as u64; cursor_position(past_end_of_stream, &stream.get_ref().0, cursor_len) } Stream::StaticString(stream_layout) => { let StreamLayout { past_end_of_stream, stream, .. } = &mut ***stream_layout; let cursor_len = stream.stream.get_ref().len() as u64; cursor_position(past_end_of_stream, &stream.stream, cursor_len) } Stream::InputFile(stream_layout) => { let position = stream_layout.position(); let StreamLayout { past_end_of_stream, stream, .. } = &mut ***stream_layout; match stream.get_ref().file.metadata() { Ok(metadata) => { if let Some(position) = position { match position.cmp(&metadata.len()) { Ordering::Equal => AtEndOfStream::At, Ordering::Less => AtEndOfStream::Not, Ordering::Greater => { *past_end_of_stream = true; AtEndOfStream::Past } } } else { *past_end_of_stream = true; AtEndOfStream::Past } } _ => { *past_end_of_stream = true; AtEndOfStream::Past } } } #[cfg(feature = "http")] Stream::HttpRead(stream_layout) => { if stream_layout .stream .get_ref() .body_reader .get_ref() .has_remaining() { AtEndOfStream::Not } else { AtEndOfStream::Past } } Stream::InputChannel(stream_layout) => { if stream_layout.stream.get_ref().eof { AtEndOfStream::At } else { AtEndOfStream::Not } } _ => AtEndOfStream::Not, } } #[inline] pub(crate) fn file_name(&self) -> Option { match self { Stream::InputFile(file) => Some(file.stream.get_ref().file_name), Stream::OutputFile(file) => Some(file.stream.file_name), Stream::NamedTcp(tcp) => Some(tcp.stream.get_ref().address), #[cfg(feature = "tls")] Stream::NamedTls(tls) => Some(tls.stream.get_ref().address), _ => None, } } #[inline] pub(crate) fn mode(&self) -> Atom { match self { #[cfg(feature = "http")] Stream::HttpRead(_) => atom!("read"), #[cfg(feature = "tls")] Stream::NamedTls(..) => atom!("read_append"), Stream::Byte(_) | Stream::InputChannel(_) | Stream::Readline(_) | Stream::StaticString(_) | Stream::InputFile(..) => atom!("read"), Stream::NamedTcp(..) => atom!("read_append"), Stream::OutputFile(file) if file.is_append => atom!("append"), #[cfg(feature = "http")] Stream::HttpWrite(_) => atom!("write"), Stream::OutputFile(_) | Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::Callback(_) => { atom!("write") } Stream::Null(_) => atom!(""), } } #[inline] pub fn stdout(arena: &mut Arena) -> Self { Stream::StandardOutput(arena_alloc!( StreamLayout::new(StandardOutputStream {}), arena )) } #[inline] pub fn stderr(arena: &mut Arena) -> Self { Stream::StandardError(arena_alloc!( StreamLayout::new(StandardErrorStream {}), arena )) } #[inline] pub fn from_callback(callback: Callback, arena: &mut Arena) -> Self { Stream::Callback(arena_alloc!( ManuallyDrop::new(StreamLayout::new(CharReader::new(CallbackStream { inner: Cursor::new(Vec::new()), callback, }))), arena )) } #[inline] pub(crate) fn from_tcp_stream(address: Atom, tcp_stream: TcpStream, arena: &mut Arena) -> Self { tcp_stream.set_read_timeout(None).unwrap(); tcp_stream.set_write_timeout(None).unwrap(); Stream::NamedTcp(arena_alloc!( StreamLayout::new(CharReader::new(NamedTcpStream { address, tcp_stream })), arena )) } #[cfg(feature = "tls")] #[inline] pub(crate) fn from_tls_stream( address: Atom, tls_stream: TlsStream, arena: &mut Arena, ) -> Self { Stream::NamedTls(arena_alloc!( StreamLayout::new(CharReader::new(NamedTlsStream { address, tls_stream })), arena )) } #[cfg(feature = "http")] #[inline] pub(crate) fn from_http_stream( url: Atom, http_stream: BufReader, arena: &mut Arena, ) -> Self { Stream::HttpRead(arena_alloc!( StreamLayout::new(CharReader::new(HttpReadStream { url, body_reader: http_stream })), arena )) } #[cfg(feature = "http")] #[inline] pub(crate) fn from_http_sender( response: TypedArenaPtr, status_code: u16, headers: hyper::HeaderMap, arena: &mut Arena, ) -> Self { Stream::HttpWrite(arena_alloc!( StreamLayout::new(CharReader::new(HttpWriteStream { response, status_code, headers: std::mem::ManuallyDrop::new(headers), buffer: std::mem::ManuallyDrop::new(Vec::new()), })), arena )) } #[inline] pub(crate) fn from_file_as_output( file_name: Atom, file: File, is_append: bool, arena: &mut Arena, ) -> Self { Stream::OutputFile(arena_alloc!( StreamLayout::new(OutputFileStream { file_name, file, is_append }), arena )) } #[inline] pub(crate) fn from_file_as_input(file_name: Atom, file: File, arena: &mut Arena) -> Self { Stream::InputFile(arena_alloc!( StreamLayout::new(CharReader::new(InputFileStream { file_name, file })), arena )) } /// Drops the stream handle and marks the arena pointer as [`ArenaHeaderTag::Dropped`]. #[inline] pub(crate) fn close(&mut self) -> Result<(), std::io::Error> { match self { Stream::NamedTcp(ref mut tcp_stream) => { tcp_stream.inner_mut().tcp_stream.shutdown(Shutdown::Both) } #[cfg(feature = "tls")] Stream::NamedTls(ref mut tls_stream) => tls_stream.inner_mut().tls_stream.shutdown(), #[cfg(feature = "http")] Stream::HttpRead(ref mut http_stream) => { http_stream.drop_payload(); Ok(()) } #[cfg(feature = "http")] Stream::HttpWrite(mut http_stream) => { http_stream.drop_payload(); Ok(()) } Stream::InputFile(mut file_stream) => { // close the stream by dropping the inner File. file_stream.drop_payload(); Ok(()) } Stream::OutputFile(mut file_stream) => { // close the stream by dropping the inner File. file_stream.drop_payload(); Ok(()) } Stream::Byte(mut stream) => { stream.drop_payload(); Ok(()) } Stream::Callback(mut stream) => { stream.drop_payload(); Ok(()) } Stream::InputChannel(mut stream) => { stream.drop_payload(); Ok(()) } Stream::StaticString(mut stream) => { stream.drop_payload(); Ok(()) } Stream::Null(_) => Ok(()), Stream::Readline(_) | Stream::StandardOutput(_) | Stream::StandardError(_) => { unreachable!(); } } } #[inline] pub(crate) fn is_null_stream(&self) -> bool { matches!(self, Stream::Null(_)) } #[inline] pub(crate) fn is_input_stream(&self) -> bool { match self { #[cfg(feature = "tls")] Stream::NamedTls(..) => true, #[cfg(feature = "http")] Stream::HttpRead(..) => true, Stream::NamedTcp(..) | Stream::Byte(_) | Stream::InputChannel(_) | Stream::Readline(_) | Stream::StaticString(_) | Stream::InputFile(..) => true, _ => false, } } #[inline] pub(crate) fn is_output_stream(&self) -> bool { match self { #[cfg(feature = "tls")] Stream::NamedTls(..) => true, #[cfg(feature = "http")] Stream::HttpWrite(..) => true, Stream::StandardError(_) | Stream::StandardOutput(_) | Stream::NamedTcp(..) | Stream::Byte(_) | Stream::Callback(_) | Stream::OutputFile(..) => true, _ => false, } } // returns true on success. #[inline] pub(super) fn reset(&mut self) -> bool { self.set_lines_read(0); self.set_past_end_of_stream(false); match self { Stream::Byte(ref mut cursor) => { cursor.stream.get_mut().0.set_position(0); true } Stream::InputFile(ref mut file_stream) => { file_stream .stream .get_mut() .file .seek(SeekFrom::Start(0)) .unwrap(); true } Stream::Readline(ref mut readline_stream) => { readline_stream.reset(); true } Stream::InputChannel(ref mut input_channel_stream) => { input_channel_stream.stream.get_mut().inner.set_position(0); true } _ => false, } } #[inline] pub(crate) fn peek_byte(&mut self) -> std::io::Result { match self { Stream::Byte(ref mut cursor) => { let mut b = [0u8; 1]; let pos = cursor.stream.get_mut().0.position(); match cursor.read(&mut b)? { 1 => { cursor.stream.get_mut().0.set_position(pos); Ok(b[0]) } _ => Err(std::io::Error::new(ErrorKind::UnexpectedEof, "end of file")), } } Stream::InputFile(ref mut file) => match file.peek_byte() { Some(result) => Ok(result?), _ => Err(std::io::Error::new( ErrorKind::UnexpectedEof, StreamError::PeekByteFailed, )), }, Stream::Readline(ref mut stream) => stream.stream.peek_byte(), Stream::NamedTcp(ref mut stream) => { let mut b = [0u8; 1]; stream.stream.get_mut().tcp_stream.peek(&mut b)?; Ok(b[0]) } _ => Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::PeekByteFromNonPeekableStream, )), } } } impl MachineState { #[inline] pub(crate) fn eof_action( &mut self, result: HeapCellValue, mut stream: Stream, caller: Atom, arity: usize, ) -> CallResult { let eof_action = stream.options().eof_action(); match eof_action { EOFAction::Error => { stream.set_past_end_of_stream(true); Err(self.open_past_eos_error(stream, caller, arity)) } EOFAction::EOFCode => { let end_of_stream = if stream.options().stream_type() == StreamType::Binary { fixnum_as_cell!(Fixnum::build_with(-1)) } else { atom_as_cell!(atom!("end_of_file")) }; stream.set_past_end_of_stream(true); Ok(unify!(self, result, end_of_stream)) } EOFAction::Reset => { if !stream.reset() { stream.set_past_end_of_stream(true); } self.fail = stream.past_end_of_stream(); Ok(()) } } } /// ## Warning /// /// The options of streams stored in `Machine::indices` should only /// be modified through [`IndexStore::update_stream_options`]. pub(crate) fn get_stream_options( &mut self, alias: HeapCellValue, eof_action: HeapCellValue, reposition: HeapCellValue, stream_type: HeapCellValue, ) -> StreamOptions { let alias = read_heap_cell!(self.store(MachineState::deref(self, alias)), (HeapCellValueTag::Atom, (name, arity)) => { debug_assert_eq!(arity, 0); if name != atom!("[]") { Some(name) } else { None } } (HeapCellValueTag::Str, s) => { let (name, arity) = cell_as_atom_cell!(self.heap[s]) .get_name_and_arity(); debug_assert_eq!(arity, 0); if name != atom!("[]") { Some(name) } else { None } } _ => { None } ); let eof_action = read_heap_cell!(self.store(MachineState::deref(self, eof_action)), (HeapCellValueTag::Atom, (name, arity)) => { debug_assert_eq!(arity, 0); match name { atom!("eof_code") => EOFAction::EOFCode, atom!("error") => EOFAction::Error, atom!("reset") => EOFAction::Reset, _ => unreachable!(), } } (HeapCellValueTag::Str, s) => { let (name, arity) = cell_as_atom_cell!(self.heap[s]) .get_name_and_arity(); debug_assert_eq!(arity, 0); match name { atom!("eof_code") => EOFAction::EOFCode, atom!("error") => EOFAction::Error, atom!("reset") => EOFAction::Reset, _ => unreachable!(), } } _ => { unreachable!() } ); let reposition = read_heap_cell!(self.store(MachineState::deref(self, reposition)), (HeapCellValueTag::Atom, (name, arity)) => { debug_assert_eq!(arity, 0); name == atom!("true") } (HeapCellValueTag::Str, s) => { let (name, arity) = cell_as_atom_cell!(self.heap[s]) .get_name_and_arity(); debug_assert_eq!(arity, 0); name == atom!("true") } _ => { unreachable!() } ); let stream_type = read_heap_cell!(self.store(MachineState::deref(self, stream_type)), (HeapCellValueTag::Atom, (name, arity)) => { debug_assert_eq!(arity, 0); match name { atom!("text") => StreamType::Text, atom!("binary") => StreamType::Binary, _ => unreachable!(), } } (HeapCellValueTag::Str, s) => { let (name, arity) = cell_as_atom_cell!(self.heap[s]) .get_name_and_arity(); debug_assert_eq!(arity, 0); match name { atom!("text") => StreamType::Text, atom!("binary") => StreamType::Binary, _ => unreachable!(), } } _ => { unreachable!() } ); let mut options = StreamOptions::default(); options.set_stream_type(stream_type); options.set_reposition(reposition); options.set_alias_to_atom_opt(alias); options.set_eof_action(eof_action); options } /// If `addr` is a [`Cons`](HeapCellValueTag::Cons) to a stream, then returns it. /// /// If it is an atom or a string, then this searches for the corresponding stream /// inside of [`self.indices`], returning it. /// /// ## Warning /// /// **Do not directly modify [`stream.options_mut()`](Stream::options_mut) /// on the returned stream.** /// /// Other functions rely on the invariants of [`IndexStore`], which may /// become invalidated by the direct modification of a stream's option (namely, /// its alias name). Instead, use [`IndexStore::update_stream_options`]. pub(crate) fn get_stream_or_alias( &mut self, addr: HeapCellValue, indices: &IndexStore, caller: Atom, arity: usize, ) -> Result { let addr = self.store(MachineState::deref(self, addr)); read_heap_cell!(addr, (HeapCellValueTag::Atom, (name, arity)) => { debug_assert_eq!(arity, 0); return match indices.get_stream(name) { Some(stream) if !stream.is_null_stream() => Ok(stream), _ => { let stub = functor_stub(caller, arity); let addr = atom_as_cell!(name); let existence_error = self.existence_error(ExistenceError::Stream(addr)); Err(self.error_form(existence_error, stub)) } }; } (HeapCellValueTag::Str, s) => { let (name, arity) = cell_as_atom_cell!(self.heap[s]) .get_name_and_arity(); debug_assert_eq!(arity, 0); return match indices.get_stream(name) { Some(stream) if !stream.is_null_stream() => Ok(stream), _ => { let stub = functor_stub(caller, arity); let addr = atom_as_cell!(name); let existence_error = self.existence_error(ExistenceError::Stream(addr)); Err(self.error_form(existence_error, stub)) } }; } (HeapCellValueTag::Cons, ptr) => { match_untyped_arena_ptr!(ptr, (ArenaHeaderTag::Stream, stream) => { return if stream.is_null_stream() { Err(self.open_permission_error(stream_as_cell!(stream), caller, arity)) } else { Ok(stream) }; } (ArenaHeaderTag::Dropped, _value) => { let stub = functor_stub(caller, arity); let err = self.existence_error(ExistenceError::Stream(addr)); return Err(self.error_form(err, stub)); } _ => { } ); } _ => { } ); let stub = functor_stub(caller, arity); if addr.is_var() { let instantiation_error = self.instantiation_error(); Err(self.error_form(instantiation_error, stub)) } else { let domain_error = self.domain_error(DomainErrorType::StreamOrAlias, addr); Err(self.error_form(domain_error, stub)) } } pub(crate) fn open_parsing_stream( &mut self, mut stream: Stream, ) -> Result { match stream.peek_char() { None => Ok(stream), // empty stream is handled gracefully by Lexer::eof Some(Err(e)) => Err(ParserError::IO(e)), Some(Ok(c)) => { if c == '\u{feff}' { // skip UTF-8 BOM stream.consume(c.len_utf8()); } Ok(stream) } } } pub(crate) fn stream_permission_error( &mut self, perm: Permission, err_atom: Atom, stream: Stream, caller: Atom, arity: usize, ) -> MachineStub { let stub = functor_stub(caller, arity); let err = self.permission_error( perm, err_atom, if let Some(alias) = stream.options().get_alias() { atom_as_cell!(alias) } else { stream_as_cell!(stream) }, ); self.error_form(err, stub) } #[inline] pub(crate) fn open_past_eos_error( &mut self, stream: Stream, caller: Atom, arity: usize, ) -> MachineStub { self.stream_permission_error( Permission::InputStream, atom!("past_end_of_stream"), stream, caller, arity, ) } pub(crate) fn open_permission_error( &mut self, culprit: T, stub_name: Atom, stub_arity: usize, ) -> MachineStub { let stub = functor_stub(stub_name, stub_arity); let err = self.permission_error(Permission::Open, atom!("source_sink"), culprit); self.error_form(err, stub) } pub(crate) fn occupied_alias_permission_error( &mut self, alias: Atom, stub_name: Atom, stub_arity: usize, ) -> MachineStub { let stub = functor_stub(stub_name, stub_arity); let err = self.permission_error( Permission::Open, atom!("source_sink"), functor!(atom!("alias"), [atom(alias)]), ); self.error_form(err, stub) } pub(crate) fn reposition_error(&mut self, stub_name: Atom, stub_arity: usize) -> MachineStub { let stub = functor_stub(stub_name, stub_arity); let rep_stub = functor!(atom!("reposition"), [atom(atom!("true"))]); let err = self.permission_error(Permission::Open, atom!("source_sink"), rep_stub); self.error_form(err, stub) } pub(crate) fn check_stream_properties( &mut self, stream: Stream, expected_type: StreamType, input: Option, caller: Atom, arity: usize, ) -> CallResult { let opt_err = if input.is_some() && !stream.is_input_stream() || input.is_none() && !stream.is_output_stream() { Some(atom!("stream")) // 8.14.2.3 g) } else if stream.options().stream_type() != expected_type { Some(expected_type.other().as_atom()) // 8.14.2.3 h) } else { None }; let permission = if input.is_some() { Permission::InputStream } else { Permission::OutputStream }; if let Some(err_atom) = opt_err { return Err(self.stream_permission_error(permission, err_atom, stream, caller, arity)); } if let Some(input) = input { if stream.past_end_of_stream() { self.eof_action(input, stream, caller, arity)?; } } Ok(()) } pub(crate) fn stream_from_file_spec( &mut self, file_spec: Atom, indices: &mut IndexStore, options: &StreamOptions, ) -> Result { if file_spec == atom!("") { let stub = functor_stub(atom!("open"), 4); let err = self.domain_error(DomainErrorType::SourceSink, self.registers[1]); return Err(self.error_form(err, stub)); } // 8.11.5.3l) if let Some(alias) = options.get_alias() { if indices.has_stream(alias) { return Err(self.occupied_alias_permission_error(alias, atom!("open"), 4)); } } let mode = cell_as_atom!(self.store(MachineState::deref(self, self.registers[2]))); let mut open_options = OpenOptions::new(); let (is_input_file, in_append_mode) = match mode { atom!("read") => { open_options.read(true).write(false).create(false); (true, false) } atom!("write") => { open_options .read(false) .write(true) .truncate(true) .create(true); (false, false) } atom!("append") => { open_options .read(false) .write(true) .create(true) .append(true); (false, true) } _ => { let stub = functor_stub(atom!("open"), 4); let err = self.domain_error(DomainErrorType::IOMode, self[temp_v!(2)]); // 8.11.5.3h) return Err(self.error_form(err, stub)); } }; let mut path = PathBuf::from(&*file_spec.as_str()); loop { let file = match open_options.open(&path) { Ok(file) => file, Err(err) => { match err.kind() { ErrorKind::NotFound => { // 8.11.5.3j) let stub = functor_stub(atom!("open"), 4); let err = self.existence_error(ExistenceError::SourceSink(self[temp_v!(1)])); return Err(self.error_form(err, stub)); } ErrorKind::PermissionDenied => { // 8.11.5.3k) return Err(self.open_permission_error( self.registers[1], atom!("open"), 4, )); } _ => { // assume the OS is out of file descriptors. let stub = functor_stub(atom!("open"), 4); let err = self.resource_error(ResourceError::OutOfFiles); return Err(self.error_form(err, stub)); } } } }; if path.extension().is_none() { if let Ok(metadata) = file.metadata() { if metadata.is_dir() { path.set_extension("pl"); continue; } } } return Ok(if is_input_file { Stream::from_file_as_input(file_spec, file, &mut self.arena) } else { Stream::from_file_as_output(file_spec, file, in_append_mode, &mut self.arena) }); } } } #[cfg(test)] mod test { use super::*; use crate::machine::config::*; #[test] #[cfg_attr(miri, ignore)] fn close_memory_user_output_stream() { let mut machine = MachineBuilder::new() .with_streams(StreamConfig::in_memory()) .build(); let results = machine .run_query( "\\+ \\+ (current_output(Stream), close(Stream)), write(user_output, hello).", ) .collect::>(); assert_eq!(results.len(), 1); assert!(results[0].is_ok()); let mut actual = String::new(); machine.user_output.read_to_string(&mut actual).unwrap(); assert_eq!(actual, "hello"); } #[test] #[cfg_attr(miri, ignore)] fn close_memory_user_output_stream_twice() { let mut machine = MachineBuilder::new() .with_streams(StreamConfig::in_memory()) .build(); let results = machine .run_query("\\+ \\+ (current_output(Stream), close(Stream), close(Stream)).") .collect::>(); assert_eq!(results.len(), 1); assert!(results[0].is_ok()); } #[test] #[cfg_attr(miri, ignore)] fn close_realiased_stream() { let mut machine = MachineBuilder::new().build(); let results = machine .run_query( r#" \+ \+ ( open("README.md", read, S, [alias(readme)]), open(stream(S), read, _, [alias(another_alias)]), close(S) ), open("README.md", read, _, [alias(readme)]). "#, ) .collect::>(); assert_eq!(results.len(), 1); assert!(results[0].is_ok()); } #[test] #[cfg_attr(miri, ignore)] fn close_realiased_user_output() { let mut machine = MachineBuilder::new() .with_streams(StreamConfig::in_memory()) .build(); let results = machine .run_query( r#" \+ \+ ( open("README.md", read, S), open(stream(S), read, _, [alias(user_output)]), close(S) ), write(user_output, hello). "#, ) .collect::>(); assert_eq!(results.len(), 1); assert!(results[0].is_ok()); } }