ExpandTerm,
FetchGlobalVar,
FetchGlobalVarWithOffset,
- FileToChars,
FirstStream,
FlushOutput,
GetByte,
&SystemClauseType::FetchGlobalVarWithOffset => {
clause_name!("$fetch_global_var_with_offset")
}
- &SystemClauseType::FileToChars => clause_name!("$file_to_chars"),
&SystemClauseType::FirstStream => clause_name!("$first_stream"),
&SystemClauseType::FlushOutput => clause_name!("$flush_output"),
&SystemClauseType::GetByte => clause_name!("$get_byte"),
("$expand_goal", 2) => Some(SystemClauseType::ExpandGoal),
("$fetch_global_var", 2) => Some(SystemClauseType::FetchGlobalVar),
("$fetch_global_var_with_offset", 3) => Some(SystemClauseType::FetchGlobalVarWithOffset),
- ("$file_to_chars", 3) => Some(SystemClauseType::FileToChars),
("$get_byte", 2) => Some(SystemClauseType::GetByte),
("$get_char", 2) => Some(SystemClauseType::GetChar),
("$get_code", 2) => Some(SystemClauseType::GetCode),
:- use_module(library(dcgs)).
:- use_module(library(error)).
+:- use_module(library(freeze)).
+:- use_module(library(iso_ext), [setup_call_cleanup/3, partial_string/3]).
:- use_module(library(lists), [member/2]).
phrase_from_file(NT, File) :-
member(Type, [text,binary])
; Type = text
),
- '$file_to_chars'(File, Chars, Type),
- phrase(NT, Chars)
- ).
+ setup_call_cleanup(open(File, read, Stream, [reposition(true)|Options]),
+ ( stream_to_lazy_list(pio:Type, Stream, Xs),
+ phrase(NT, Xs) ),
+ close(Stream))
+ ).
+
+
+stream_to_lazy_list(Type_3, Stream, Xs) :-
+ stream_property(Stream, position(Pos)),
+ freeze(Xs, reader_step(Type_3, Stream, Pos, Xs)).
+
+reader_step(Type_3, Stream, Pos, Xs0) :-
+ set_stream_position(Stream, Pos),
+ ( at_end_of_stream(Stream)
+ -> Xs0 = []
+ ; % phrase(call(call(Type_3,Stream)), Xs0,Xs), % conforming call
+ call(Type_3, Stream, Cs,[]), % effective call
+ partial_string(Cs, Xs0, Xs),
+ stream_to_lazy_list(Type_3, Stream, Xs)
+ ).
+
+binary(Stream, Xs0, Xs) :- get_pending_bytes(Stream, Xs0, Xs).
+text(Stream, Xs0, Xs) :- get_pending_chars(Stream, Xs0, Xs).
+
+
+get_pending_chars(Stream, Chs0,Chs) :-
+ n_get_chars(4096, Stream, Chs, Chs0,Chs).
+
+% EOF means: If EOF == [], then EOF has definitely been reached, otherwise
+% it is unknown and the argument remains uninstantiated.
+
+% To improve performance, the following predicates should be replaced
+% by a fast Rust implementation that reads a number of characters (or
+% bytes) at once.
+
+% Files that do not contain 0-bytes can even be mmapped to memory.
+
+n_get_chars(N0, Stream, EOF, Chs0,Chs) :-
+ N0 > 0,
+ N1 is N0-1,
+ get_char(Stream, Ch),
+ ( Ch == end_of_file
+ -> Chs0 = Chs,
+ EOF = []
+ ; Chs0 = [Ch|Chs1],
+ n_get_chars(N1, Stream, EOF, Chs1,Chs)
+ ).
+n_get_chars(0, _, _, Chs,Chs).
+
+
+get_pending_bytes(Stream, Chs0,Chs) :-
+ n_get_bytes(4096, Stream, Chs, Chs0,Chs).
+
+n_get_bytes(N0, Stream, EOF, Chs0,Chs) :-
+ N0 > 0,
+ N1 is N0-1,
+ get_byte(Stream, Byte),
+ ( Byte == -1
+ -> Chs0 = Chs,
+ EOF = []
+ ; char_code(Ch, Byte),
+ Chs0 = [Ch|Chs1],
+ n_get_bytes(N1, Stream, EOF, Chs1,Chs)
+ ).
+n_get_bytes(0, _, _, Chs,Chs).
use std::convert::TryFrom;
use std::io::{ErrorKind, Read, Write};
use std::iter::{once, FromIterator};
-use std::fs::{File, OpenOptions};
+use std::fs::{OpenOptions};
use std::net::{TcpListener, TcpStream};
use std::ops::Sub;
use std::rc::Rc;
}
};
}
- &SystemClauseType::FileToChars => {
- // TODO: Replace this with stream.
- let a1 = self.store(self.deref(self[temp_v!(1)]));
- let a2 = self.store(self.deref(self[temp_v!(2)]));
-
- let file_name = match a1 {
- Addr::Con(h) if self.heap.atom_at(h) => {
- if let HeapCellValue::Atom(name, _) = &self.heap[h] {
- name.clone()
- }
- else {
- unreachable!()
- }
- }
- Addr::Char(c) => {
- clause_name!(c.to_string(), indices.atom_tbl.clone())
- }
- _ => {
- unreachable!()
- }
- };
-
- let name = clause_name!("$file_to_chars");
- let mut file = match File::open(file_name.as_str()) {
- Ok(f) => f,
- Err(e) => {
- let arity = 2;
- let stub = MachineError::functor_stub(name.clone(), arity);
- let h = self.heap.h();
-
- let err = match e.kind() {
- ErrorKind::NotFound => {
- MachineError::existence_error(
- h,
- ExistenceError::ModuleSource(
- ModuleSource::File(file_name)
- ),
- )
- }
- ErrorKind::PermissionDenied => {
- let source_sink = self.store(self.deref(a1));
-
- MachineError::permission_error(
- h,
- Permission::Access,
- "source_sink",
- source_sink
- )
- }
- _ => unreachable!() // Not nice.
- };
-
- return Err(self.error_form(err, stub));
- }
- };
-
-
- let type_str = match self.store(self.deref(self[temp_v!(3)])) {
- Addr::Con(h) if self.heap.atom_at(h) => {
- if let HeapCellValue::Atom(ref atom, _) = &self.heap[h] {
- atom.as_str()
- } else {
- unreachable!()
- }
- }
- _ => {
- unreachable!()
- }
- };
-
- let complete_string = {
- let mut buffer = String::new();
- match type_str {
- "text" => { match file.read_to_string(&mut buffer) {
- Ok(_size) => {
- self.heap.put_complete_string(&buffer)
- }
- Err(_e) => {
- // the data isn't valid UTF-8, so we fail.
- self.fail = true;
- return Ok(());
-
- }
- }
- }
- "binary" => { let mut buffer = Vec::new();
- let _ = match file.read_to_end(&mut buffer) {
- Ok(size) => size,
- Err(_e) => unreachable!()
- };
-
- let buffer = String::from_iter(
- buffer.into_iter().map(|b| b as char)
- );
-
- self.heap.put_complete_string(&buffer)
- }
- _ => { unreachable!() }
- }
- };
-
- self.unify(complete_string, a2);
- }
&SystemClauseType::PutCode => {
let mut stream =
self.get_stream_or_alias(self[temp_v!(1)], indices, "put_code", 2)?;