2553 lines
84 KiB
Rust
2553 lines
84 KiB
Rust
#![allow(unused_parens)] // see mthom/scryer-prolog#3092 and rust-lang/rust#147126
|
|
|
|
use crate::arena::*;
|
|
use crate::atom_table::*;
|
|
use crate::functor_macro::*;
|
|
use crate::parser::ast::*;
|
|
use crate::parser::char_reader::*;
|
|
use crate::read::*;
|
|
|
|
#[cfg(feature = "http")]
|
|
use crate::http::HttpResponse;
|
|
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;
|
|
|
|
mod compat;
|
|
pub use compat::*;
|
|
|
|
#[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<Vec<u8>>);
|
|
|
|
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<usize> {
|
|
self.0.read(buf)
|
|
}
|
|
}
|
|
|
|
impl Write for ByteStream {
|
|
#[inline]
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
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<usize> {
|
|
self.file.read(buf)
|
|
}
|
|
}
|
|
|
|
impl StreamLayout<CharReader<InputFileStream>> {
|
|
#[inline]
|
|
fn position(&mut self) -> Option<u64> {
|
|
// 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<usize> {
|
|
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<usize> {
|
|
self.stream.read(buf)
|
|
}
|
|
}
|
|
|
|
impl CharRead for StaticStringStream {
|
|
#[inline(always)]
|
|
fn peek_char(&mut self) -> Option<std::io::Result<char>> {
|
|
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<usize> {
|
|
self.tcp_stream.read(buf)
|
|
}
|
|
}
|
|
|
|
impl Write for NamedTcpStream {
|
|
#[inline]
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
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<Stream>,
|
|
}
|
|
|
|
#[cfg(feature = "tls")]
|
|
impl Read for NamedTlsStream {
|
|
#[inline]
|
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
self.tls_stream.read(buf)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "tls")]
|
|
impl Write for NamedTlsStream {
|
|
#[inline]
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
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<Bytes>,
|
|
}
|
|
|
|
#[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<usize> {
|
|
self.body_reader.read(buf)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "http")]
|
|
pub struct HttpWriteStream {
|
|
status_code: u16,
|
|
headers: std::mem::ManuallyDrop<hyper::HeaderMap>,
|
|
response: TypedArenaPtr<HttpResponse>,
|
|
buffer: std::mem::ManuallyDrop<Vec<u8>>,
|
|
}
|
|
|
|
#[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<usize> {
|
|
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<usize> {
|
|
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<usize> {
|
|
io::stderr().write(buf)
|
|
}
|
|
|
|
#[inline]
|
|
fn flush(&mut self) -> std::io::Result<()> {
|
|
io::stderr().flush()
|
|
}
|
|
}
|
|
|
|
pub type Callback = Box<dyn FnMut(&mut Cursor<Vec<u8>>)>;
|
|
|
|
pub struct CallbackStream {
|
|
pub(crate) inner: Cursor<Vec<u8>>,
|
|
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<usize> {
|
|
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<Vec<u8>>,
|
|
pub eof: bool,
|
|
channel: Receiver<Vec<u8>>,
|
|
}
|
|
|
|
impl Read for InputChannelStream {
|
|
#[inline]
|
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
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<Atom> {
|
|
if self.has_alias() {
|
|
Some(Atom::from(self.alias()))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn set_alias_to_atom_opt(&mut self, alias: Option<Atom>) {
|
|
self.set_has_alias(alias.is_some());
|
|
|
|
if let Some(alias) = alias {
|
|
self.set_alias(alias.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<T> {
|
|
pub options: StreamOptions,
|
|
pub lines_read: usize,
|
|
past_end_of_stream: bool,
|
|
stream: T,
|
|
}
|
|
|
|
impl<T> StreamLayout<T> {
|
|
#[inline]
|
|
pub fn new(stream: T) -> Self {
|
|
Self {
|
|
options: StreamOptions::default(),
|
|
lines_read: 0,
|
|
past_end_of_stream: false,
|
|
stream,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Deref for StreamLayout<T> {
|
|
type Target = T;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.stream
|
|
}
|
|
}
|
|
|
|
impl<T> DerefMut for StreamLayout<T> {
|
|
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<StreamLayout<$stream_type>>;
|
|
|
|
#[inline]
|
|
fn tag() -> ArenaHeaderTag {
|
|
ArenaHeaderTag::$stream_tag
|
|
}
|
|
|
|
unsafe fn dealloc(ptr: std::ptr::NonNull<TypedAllocSlab<Self>>) {
|
|
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>, ByteStream);
|
|
arena_allocated_impl_for_stream!(CharReader<InputFileStream>, InputFileStream);
|
|
arena_allocated_impl_for_stream!(OutputFileStream, OutputFileStream);
|
|
arena_allocated_impl_for_stream!(CharReader<NamedTcpStream>, NamedTcpStream);
|
|
#[cfg(feature = "tls")]
|
|
arena_allocated_impl_for_stream!(CharReader<NamedTlsStream>, NamedTlsStream);
|
|
#[cfg(feature = "http")]
|
|
arena_allocated_impl_for_stream!(CharReader<HttpReadStream>, HttpReadStream);
|
|
#[cfg(feature = "http")]
|
|
arena_allocated_impl_for_stream!(CharReader<HttpWriteStream>, 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>, CallbackStream);
|
|
arena_allocated_impl_for_stream!(CharReader<InputChannelStream>, InputChannelStream);
|
|
arena_allocated_impl_for_stream!(CharReader<PipeReader>, PipeReader);
|
|
arena_allocated_impl_for_stream!(CharReader<PipeWriter>, PipeWriter);
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum Stream {
|
|
Byte(TypedArenaPtr<ByteStream>),
|
|
InputFile(TypedArenaPtr<InputFileStream>),
|
|
OutputFile(TypedArenaPtr<OutputFileStream>),
|
|
StaticString(TypedArenaPtr<StaticStringStream>),
|
|
NamedTcp(TypedArenaPtr<NamedTcpStream>),
|
|
#[cfg(feature = "tls")]
|
|
NamedTls(TypedArenaPtr<NamedTlsStream>),
|
|
#[cfg(feature = "http")]
|
|
HttpRead(TypedArenaPtr<HttpReadStream>),
|
|
#[cfg(feature = "http")]
|
|
HttpWrite(TypedArenaPtr<HttpWriteStream>),
|
|
Null(StreamOptions),
|
|
Readline(TypedArenaPtr<ReadlineStream>),
|
|
StandardOutput(TypedArenaPtr<StandardOutputStream>),
|
|
StandardError(TypedArenaPtr<StandardErrorStream>),
|
|
Callback(TypedArenaPtr<CallbackStream>),
|
|
InputChannel(TypedArenaPtr<InputChannelStream>),
|
|
PipeReader(TypedArenaPtr<PipeReader>),
|
|
PipeWriter(TypedArenaPtr<PipeWriter>),
|
|
}
|
|
|
|
impl From<TypedArenaPtr<ReadlineStream>> for Stream {
|
|
#[inline]
|
|
fn from(stream: TypedArenaPtr<ReadlineStream>) -> 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<Vec<u8>>, 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() })
|
|
}
|
|
ArenaHeaderTag::PipeReader => Stream::PipeReader(unsafe { ptr.as_typed_ptr() }),
|
|
ArenaHeaderTag::PipeWriter => Stream::PipeWriter(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(),
|
|
Stream::PipeReader(ptr) => ptr.header_ptr(),
|
|
Stream::PipeWriter(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,
|
|
Stream::PipeReader(ref ptr) => &ptr.options,
|
|
Stream::PipeWriter(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,
|
|
Stream::PipeReader(ref mut ptr) => &mut ptr.options,
|
|
Stream::PipeWriter(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,
|
|
Stream::PipeReader(ptr) => ptr.lines_read += incr_num_lines_read,
|
|
Stream::PipeWriter(_) => {}
|
|
}
|
|
}
|
|
|
|
#[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,
|
|
Stream::PipeReader(ptr) => ptr.lines_read = value,
|
|
Stream::PipeWriter(_) => {}
|
|
}
|
|
}
|
|
|
|
#[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,
|
|
Stream::PipeReader(ptr) => ptr.lines_read,
|
|
Stream::PipeWriter(_) => 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CharRead for Stream {
|
|
fn peek_char(&mut self) -> Option<std::io::Result<char>> {
|
|
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(),
|
|
Stream::PipeReader(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(_)
|
|
| Stream::PipeWriter(_) => Some(Err(std::io::Error::new(
|
|
ErrorKind::PermissionDenied,
|
|
StreamError::ReadFromOutputStream,
|
|
))),
|
|
}
|
|
}
|
|
|
|
fn read_char(&mut self) -> Option<std::io::Result<char>> {
|
|
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(),
|
|
Stream::PipeReader(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(_)
|
|
| Stream::PipeWriter(_) => 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),
|
|
Stream::PipeReader(cursor) => cursor.put_back_char(c),
|
|
#[cfg(feature = "http")]
|
|
Stream::HttpWrite(_) => {}
|
|
Stream::OutputFile(_)
|
|
| Stream::StandardError(_)
|
|
| Stream::StandardOutput(_)
|
|
| Stream::Null(_)
|
|
| Stream::Callback(_)
|
|
| Stream::PipeWriter(_) => {}
|
|
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),
|
|
Stream::PipeReader(ref mut cursor) => cursor.consume(nread),
|
|
#[cfg(feature = "http")]
|
|
Stream::HttpWrite(_) => {}
|
|
Stream::OutputFile(_)
|
|
| Stream::StandardError(_)
|
|
| Stream::StandardOutput(_)
|
|
| Stream::Null(_)
|
|
| Stream::Callback(_)
|
|
| Stream::PipeWriter(_) => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Read for Stream {
|
|
#[inline]
|
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
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),
|
|
Stream::PipeReader(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::Callback(_)
|
|
| Stream::PipeWriter(_) => Err(std::io::Error::new(
|
|
ErrorKind::PermissionDenied,
|
|
StreamError::ReadFromOutputStream,
|
|
)),
|
|
Stream::Null(_) => Ok(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Write for Stream {
|
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
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),
|
|
Stream::PipeWriter(ref mut stream) => stream.get_mut().write(buf),
|
|
#[cfg(feature = "http")]
|
|
Stream::HttpRead(_) => Err(std::io::Error::new(
|
|
ErrorKind::PermissionDenied,
|
|
StreamError::WriteToInputStream,
|
|
)),
|
|
Stream::Null(_) => Ok(buf.len()),
|
|
Stream::StaticString(_)
|
|
| Stream::InputChannel(_)
|
|
| Stream::Readline(_)
|
|
| Stream::InputFile(..)
|
|
| Stream::PipeReader(_) => 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(),
|
|
Stream::PipeWriter(ref mut stream) => stream.stream.get_mut().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::Null(_) => Ok(()),
|
|
Stream::StaticString(_)
|
|
| Stream::InputChannel(_)
|
|
| Stream::Readline(_)
|
|
| Stream::InputFile(_)
|
|
| Stream::PipeReader(_) => 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<Ordering> {
|
|
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<T>(
|
|
past_end_of_stream: &mut bool,
|
|
cursor: &Cursor<T>,
|
|
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 From<Stream> for HeapCellValue {
|
|
#[inline(always)]
|
|
fn from(stream: Stream) -> Self {
|
|
if stream.is_null_stream() {
|
|
let res = atom!("null_stream");
|
|
atom_as_cell!(res)
|
|
} else {
|
|
let res = stream.as_ptr();
|
|
debug_assert!(!res.is_null());
|
|
raw_ptr_as_cell!(res)
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
Stream::PipeReader(stream) => stream.past_end_of_stream,
|
|
Stream::PipeWriter(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,
|
|
Stream::PipeReader(stream) => stream.past_end_of_stream = value,
|
|
Stream::PipeWriter(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
|
|
}
|
|
}
|
|
}
|
|
Stream::Null(_) => AtEndOfStream::At,
|
|
#[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<Atom> {
|
|
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(..)
|
|
| Stream::PipeReader(_) => 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(_)
|
|
| Stream::PipeWriter(_) => {
|
|
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
|
|
))
|
|
}
|
|
|
|
pub(crate) fn from_pipe_writer(writer: PipeWriter, arena: &mut Arena) -> Stream {
|
|
Stream::PipeWriter(arena_alloc!(
|
|
ManuallyDrop::new(StreamLayout::new(CharReader::new(writer))),
|
|
arena
|
|
))
|
|
}
|
|
|
|
pub(crate) fn from_pipe_reader(reader: PipeReader, arena: &mut Arena) -> Stream {
|
|
Stream::PipeReader(arena_alloc!(
|
|
ManuallyDrop::new(StreamLayout::new(CharReader::new(reader))),
|
|
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<Stream>,
|
|
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<Bytes>,
|
|
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<HttpResponse>,
|
|
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::PipeReader(mut stream) => {
|
|
stream.drop_payload();
|
|
Ok(())
|
|
}
|
|
|
|
Stream::PipeWriter(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(..)
|
|
| Stream::PipeReader(_)
|
|
| Stream::Null(_) => 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::OutputFile(..)
|
|
| Stream::Callback(_)
|
|
| Stream::PipeWriter(_)
|
|
| Stream::Null(_) => 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<u8> {
|
|
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);
|
|
unify!(self, result, end_of_stream);
|
|
Ok(())
|
|
}
|
|
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<Stream, MachineStub> {
|
|
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) => 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) => 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<Stream, ParserError> {
|
|
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.into()
|
|
},
|
|
);
|
|
|
|
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<T: PermissionError>(
|
|
&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_as_cell(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_as_cell((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<HeapCellValue>,
|
|
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<Stream, MachineStub> {
|
|
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 crate::*;
|
|
use std::{cell::RefCell, io::Read, io::Write, rc::Rc};
|
|
|
|
use crate::machine::config::*;
|
|
use crate::LeafAnswer;
|
|
|
|
use super::{Stream, StreamOptions};
|
|
|
|
fn succeeded<T>(answer: Vec<Result<LeafAnswer, T>>) -> bool {
|
|
// Ideally this should be a method in QueryState or LeafAnswer.
|
|
matches!(
|
|
answer[0].as_ref(),
|
|
Ok(LeafAnswer::True) | Ok(LeafAnswer::LeafAnswer { .. })
|
|
)
|
|
}
|
|
|
|
fn is_successful<T>(answer: &Result<LeafAnswer, T>) -> bool {
|
|
matches!(
|
|
answer,
|
|
Ok(LeafAnswer::True) | Ok(LeafAnswer::LeafAnswer { .. })
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn user_input_string_stream() {
|
|
let streams =
|
|
StreamConfig::default().with_user_input(InputStreamConfig::string("a(1,2,3)."));
|
|
|
|
let mut machine = MachineBuilder::default().with_streams(streams).build();
|
|
|
|
let complete_answer: Vec<_> = machine
|
|
.run_query(r#"current_input(_), \+ at_end_of_stream."#)
|
|
.collect();
|
|
|
|
assert!(succeeded(complete_answer));
|
|
|
|
let complete_answer: Vec<_> = machine.run_query("read(A).").collect();
|
|
|
|
assert_eq!(
|
|
complete_answer,
|
|
[Ok(LeafAnswer::from_bindings([(
|
|
"A",
|
|
Term::compound("a", [Term::integer(1), Term::integer(2), Term::integer(3),])
|
|
)]))]
|
|
);
|
|
|
|
let complete_answer: Vec<_> = machine.run_query(r#"at_end_of_stream."#).collect();
|
|
|
|
assert!(succeeded(complete_answer));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn user_input_channel_stream() {
|
|
let (mut user_input, channel_stream) = InputStreamConfig::channel();
|
|
let streams = StreamConfig::default().with_user_input(channel_stream);
|
|
let mut machine = MachineBuilder::default().with_streams(streams).build();
|
|
|
|
let complete_answer: Vec<_> = machine
|
|
.run_query(r#"current_input(_), \+ at_end_of_stream."#)
|
|
.collect();
|
|
|
|
assert!(succeeded(complete_answer));
|
|
|
|
write!(user_input, "a(1,2,3).").unwrap();
|
|
|
|
let complete_answer: Vec<_> = machine
|
|
.run_query(r#"\+ at_end_of_stream, read(A)."#)
|
|
.collect();
|
|
|
|
assert_eq!(
|
|
complete_answer,
|
|
[Ok(LeafAnswer::from_bindings([(
|
|
"A",
|
|
Term::compound("a", [Term::integer(1), Term::integer(2), Term::integer(3),])
|
|
)]))]
|
|
);
|
|
|
|
// End-of-data but not end-of-stream;
|
|
let complete_answer: Vec<_> = machine
|
|
.run_query(
|
|
r#"
|
|
use_module(library(charsio)),
|
|
current_input(In), get_n_chars(In, N, C),
|
|
N == 0, \+ at_end_of_stream.
|
|
"#,
|
|
)
|
|
.collect();
|
|
|
|
assert!(succeeded(complete_answer));
|
|
|
|
// Dropping the sender closes the input
|
|
drop(user_input);
|
|
|
|
let complete_answer: Vec<_> = machine
|
|
.run_query(
|
|
r#"
|
|
current_input(In), get_n_chars(In, N, _),
|
|
N == 0, at_end_of_stream.
|
|
"#,
|
|
)
|
|
.collect();
|
|
|
|
assert!(succeeded(complete_answer));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn user_output_callback_stream() {
|
|
let test_string = Rc::new(RefCell::new(String::new()));
|
|
|
|
let streams =
|
|
StreamConfig::default().with_user_output(OutputStreamConfig::callback(Box::new({
|
|
let test_string = test_string.clone();
|
|
move |x| {
|
|
x.read_to_string(&mut test_string.borrow_mut()).unwrap();
|
|
}
|
|
})));
|
|
|
|
let mut machine = MachineBuilder::default().with_streams(streams).build();
|
|
|
|
let complete_answer: Vec<_> = machine
|
|
.run_query(r#"current_output(Out), \+ at_end_of_stream(Out)."#)
|
|
.collect();
|
|
|
|
assert!(succeeded(complete_answer));
|
|
|
|
let complete_answer: Vec<_> = machine
|
|
.run_query(r#"write(asdf), nl, flush_output."#)
|
|
.collect();
|
|
|
|
assert!(succeeded(complete_answer));
|
|
assert_eq!(test_string.borrow().as_str(), "asdf\n");
|
|
|
|
let complete_answer: Vec<_> = machine.run_query(r#"write(abcd), flush_output."#).collect();
|
|
|
|
assert!(succeeded(complete_answer));
|
|
assert_eq!(test_string.borrow().as_str(), "asdf\nabcd");
|
|
}
|
|
|
|
#[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::<Vec<_>>();
|
|
|
|
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 current_input_null_stream() {
|
|
let mut machine = MachineBuilder::new()
|
|
.with_streams(StreamConfig::in_memory())
|
|
.build();
|
|
|
|
let results = machine.run_query("current_input(S).").collect::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(is_successful(&results[0]));
|
|
}
|
|
|
|
#[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::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(results[0].is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn read_null_stream() {
|
|
let mut machine = MachineBuilder::new()
|
|
.with_streams(StreamConfig::in_memory())
|
|
.build();
|
|
|
|
let results = machine.run_query("get_code(C).").collect::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(
|
|
is_successful(&results[0]),
|
|
"Expected read to succeed, got {:?}",
|
|
results[0]
|
|
);
|
|
}
|
|
|
|
#[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::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(results[0].is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn current_output_null_stream() {
|
|
// TODO: switch to a proper solution for configuring the machine with null streams
|
|
// once `StreamConfig` supports it.
|
|
let mut machine = MachineBuilder::new().build();
|
|
machine.user_output = Stream::Null(StreamOptions::default());
|
|
machine.configure_streams();
|
|
|
|
let results = machine.run_query("current_output(S).").collect::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(is_successful(&results[0]));
|
|
}
|
|
|
|
#[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::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(results[0].is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn write_null_stream() {
|
|
// TODO: switch to a proper solution for configuring the machine with null streams
|
|
// once `StreamConfig` supports it.
|
|
let mut machine = MachineBuilder::new().build();
|
|
machine.user_output = Stream::Null(StreamOptions::default());
|
|
machine.configure_streams();
|
|
|
|
let results = machine.run_query("write(hello).").collect::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(
|
|
is_successful(&results[0]),
|
|
"Expected write to succeed, got {:?}",
|
|
results[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn put_code_null_stream() {
|
|
// TODO: switch to a proper solution for configuring the machine with null streams
|
|
// once `StreamConfig` supports it.
|
|
let mut machine = MachineBuilder::new().build();
|
|
machine.user_output = Stream::Null(StreamOptions::default());
|
|
machine.configure_streams();
|
|
|
|
let results = machine
|
|
.run_query("put_code(user_output, 65).")
|
|
.collect::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(
|
|
is_successful(&results[0]),
|
|
"Expected write to succeed, got {:?}",
|
|
results[0]
|
|
);
|
|
}
|
|
|
|
/// A variant of the [`write_null_stream`] that tries to write to a (null) input stream.
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn write_null_input_stream() {
|
|
let mut machine = MachineBuilder::new()
|
|
.with_streams(StreamConfig::in_memory())
|
|
.build();
|
|
|
|
let results = machine
|
|
.run_query("current_input(Stream), write(Stream, hello).")
|
|
.collect::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(
|
|
is_successful(&results[0]),
|
|
"Expected write to succeed, got {:?}",
|
|
results[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn at_end_of_stream_0_null_stream() {
|
|
let mut machine = MachineBuilder::new()
|
|
.with_streams(StreamConfig::in_memory())
|
|
.build();
|
|
|
|
let results = machine.run_query("at_end_of_stream.").collect::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(
|
|
is_successful(&results[0]),
|
|
"Expected at_end_of_stream to succeed, got {:?}",
|
|
results[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)]
|
|
fn at_end_of_stream_1_null_stream() {
|
|
let mut machine = MachineBuilder::new()
|
|
.with_streams(StreamConfig::in_memory())
|
|
.build();
|
|
|
|
let results = machine
|
|
.run_query("current_input(Stream), at_end_of_stream(Stream).")
|
|
.collect::<Vec<_>>();
|
|
|
|
assert_eq!(results.len(), 1);
|
|
assert!(
|
|
is_successful(&results[0]),
|
|
"Expected at_end_of_stream to succeed, got {:?}",
|
|
results[0]
|
|
);
|
|
}
|
|
}
|