ADDED: library(files), for reasoning about files and directories.
This commit is contained in:
@@ -418,6 +418,9 @@ The modules that ship with Scryer Prolog are also called
|
|||||||
`current_time/1` to obtain the current system time, the nonterminal
|
`current_time/1` to obtain the current system time, the nonterminal
|
||||||
`format_time//2` to describe strings with dates and times, and
|
`format_time//2` to describe strings with dates and times, and
|
||||||
`sleep/1` to slow down a computation.
|
`sleep/1` to slow down a computation.
|
||||||
|
* [`files`](src/lib/files.pl)
|
||||||
|
Predicates for reasoning about files and directories, such as
|
||||||
|
`directory_files/2`, `file_exists/1` and `file_size/2`.
|
||||||
* [`cont`](src/lib/cont.pl)
|
* [`cont`](src/lib/cont.pl)
|
||||||
Provides *delimited continuations* via `reset/3` and `shift/1`.
|
Provides *delimited continuations* via `reset/3` and `shift/1`.
|
||||||
* [`random`](src/lib/random.pl)
|
* [`random`](src/lib/random.pl)
|
||||||
|
|||||||
@@ -171,6 +171,11 @@ pub enum SystemClauseType {
|
|||||||
CurrentHostname,
|
CurrentHostname,
|
||||||
CurrentInput,
|
CurrentInput,
|
||||||
CurrentOutput,
|
CurrentOutput,
|
||||||
|
DirectoryFiles,
|
||||||
|
FileSize,
|
||||||
|
FileExists,
|
||||||
|
DirectoryExists,
|
||||||
|
MakeDirectory,
|
||||||
DeleteAttribute,
|
DeleteAttribute,
|
||||||
DeleteHeadAttribute,
|
DeleteHeadAttribute,
|
||||||
DynamicModuleResolution(usize),
|
DynamicModuleResolution(usize),
|
||||||
@@ -328,6 +333,11 @@ impl SystemClauseType {
|
|||||||
&SystemClauseType::CurrentInput => clause_name!("$current_input"),
|
&SystemClauseType::CurrentInput => clause_name!("$current_input"),
|
||||||
&SystemClauseType::CurrentHostname => clause_name!("$current_hostname"),
|
&SystemClauseType::CurrentHostname => clause_name!("$current_hostname"),
|
||||||
&SystemClauseType::CurrentOutput => clause_name!("$current_output"),
|
&SystemClauseType::CurrentOutput => clause_name!("$current_output"),
|
||||||
|
&SystemClauseType::DirectoryFiles => clause_name!("$directory_files"),
|
||||||
|
&SystemClauseType::FileSize => clause_name!("$file_size"),
|
||||||
|
&SystemClauseType::FileExists => clause_name!("$file_exists"),
|
||||||
|
&SystemClauseType::DirectoryExists => clause_name!("$directory_exists"),
|
||||||
|
&SystemClauseType::MakeDirectory => clause_name!("$make_directory"),
|
||||||
&SystemClauseType::REPL(REPLCodePtr::CompileBatch) => clause_name!("$compile_batch"),
|
&SystemClauseType::REPL(REPLCodePtr::CompileBatch) => clause_name!("$compile_batch"),
|
||||||
&SystemClauseType::REPL(REPLCodePtr::UseModule) => clause_name!("$use_module"),
|
&SystemClauseType::REPL(REPLCodePtr::UseModule) => clause_name!("$use_module"),
|
||||||
&SystemClauseType::REPL(REPLCodePtr::UseQualifiedModule) => {
|
&SystemClauseType::REPL(REPLCodePtr::UseQualifiedModule) => {
|
||||||
@@ -654,6 +664,11 @@ impl SystemClauseType {
|
|||||||
("$unwind_environments", 0) => Some(SystemClauseType::UnwindEnvironments),
|
("$unwind_environments", 0) => Some(SystemClauseType::UnwindEnvironments),
|
||||||
("$unwind_stack", 0) => Some(SystemClauseType::UnwindStack),
|
("$unwind_stack", 0) => Some(SystemClauseType::UnwindStack),
|
||||||
("$unify_with_occurs_check", 2) => Some(SystemClauseType::UnifyWithOccursCheck),
|
("$unify_with_occurs_check", 2) => Some(SystemClauseType::UnifyWithOccursCheck),
|
||||||
|
("$directory_files", 2) => Some(SystemClauseType::DirectoryFiles),
|
||||||
|
("$file_size", 2) => Some(SystemClauseType::FileSize),
|
||||||
|
("$file_exists", 1) => Some(SystemClauseType::FileExists),
|
||||||
|
("$directory_exists", 1) => Some(SystemClauseType::DirectoryExists),
|
||||||
|
("$make_directory", 1) => Some(SystemClauseType::MakeDirectory),
|
||||||
("$use_module", 1) => Some(SystemClauseType::REPL(REPLCodePtr::UseModule)),
|
("$use_module", 1) => Some(SystemClauseType::REPL(REPLCodePtr::UseModule)),
|
||||||
("$use_module_from_file", 1) =>
|
("$use_module_from_file", 1) =>
|
||||||
Some(SystemClauseType::REPL(REPLCodePtr::UseModuleFromFile)),
|
Some(SystemClauseType::REPL(REPLCodePtr::UseModuleFromFile)),
|
||||||
|
|||||||
88
src/lib/files.pl
Normal file
88
src/lib/files.pl
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written June 2020 by Markus Triska (triska@metalevel.at)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
|
||||||
|
Predicates for reasoning about files and directories.
|
||||||
|
|
||||||
|
In this library, directories and files are represented as
|
||||||
|
*lists of characters*. This is an ideal representation:
|
||||||
|
|
||||||
|
-) Lists of characters can be conveniently reasoned about with DCGs
|
||||||
|
and built-in Prolog predicates from library(lists). This alone
|
||||||
|
is already a very compelling argument to use them.
|
||||||
|
-) Other Scryer libraries such as library(http/http_open) also already
|
||||||
|
use lists of characters to represent paths.
|
||||||
|
-) File names are mostly ephemeral, so it is good for efficiency
|
||||||
|
that they can quickly allocated transiently on the heap, leaving the
|
||||||
|
atom table mostly unaffected. Indexing is almost never needed
|
||||||
|
for file names.
|
||||||
|
-) The previous point is also good for security, since the system
|
||||||
|
leaves little trace of which files were even accessed.
|
||||||
|
-) Scryer Prolog represents lists of characters extremely compactly.
|
||||||
|
|
||||||
|
Some Prolog programmers will likely find this representation quite
|
||||||
|
unusual, because files are represented as *atoms* in many systems.
|
||||||
|
|
||||||
|
However, the ISO standard only demands that sources and sinks be
|
||||||
|
*ground* terms, so lists of characters are completely admissible:
|
||||||
|
|
||||||
|
A source/sink is specified as an implementation defined
|
||||||
|
ground term in a call of open/4 (8.11.5). All subsequent
|
||||||
|
references to the source/sink are made by referring to a
|
||||||
|
stream-term (7.10.2) or alias (7.10.2.2).
|
||||||
|
|
||||||
|
I believe that with the advent of Scryer Prolog and its efficient
|
||||||
|
representation of strings as lists of characters, we should take
|
||||||
|
this opportunity for improvement, and in fact extend the use of
|
||||||
|
lists of characters also to other predicates like open/3.
|
||||||
|
|
||||||
|
Please note that we *cannot* simply accept *both* representations,
|
||||||
|
because that would invalidate the type errors raised by this library,
|
||||||
|
and make future extensions for type checking impossible.
|
||||||
|
|
||||||
|
So I ask you: Please try out this representation for a few months.
|
||||||
|
It simplifies working with files considerably. Once we have collected
|
||||||
|
more experience with this representations, please let us consider
|
||||||
|
how to proceed in such a way that we can call it an improvement.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
:- module(files, [directory_files/2,
|
||||||
|
file_size/2,
|
||||||
|
file_exists/1,
|
||||||
|
directory_exists/1,
|
||||||
|
make_directory/1]).
|
||||||
|
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
|
list_of_chars(Cs) :-
|
||||||
|
must_be(list, Cs),
|
||||||
|
( ground(Cs) ->
|
||||||
|
( member(C, Cs), \+ atom_length(C, 1) ->
|
||||||
|
type_error(char, C, files_and_directories)
|
||||||
|
; true
|
||||||
|
)
|
||||||
|
; instantiation_error(s)
|
||||||
|
).
|
||||||
|
|
||||||
|
directory_files(Directory, Files) :-
|
||||||
|
list_of_chars(Directory),
|
||||||
|
can_be(list, Files),
|
||||||
|
'$directory_files'(Directory, Files).
|
||||||
|
|
||||||
|
file_size(File, Size) :-
|
||||||
|
list_of_chars(File),
|
||||||
|
can_be(integer, Size),
|
||||||
|
'$file_size'(File, Size).
|
||||||
|
|
||||||
|
file_exists(File) :-
|
||||||
|
list_of_chars(File),
|
||||||
|
'$file_exists'(File).
|
||||||
|
|
||||||
|
directory_exists(Directory) :-
|
||||||
|
list_of_chars(Directory),
|
||||||
|
'$directory_exists'(Directory).
|
||||||
|
|
||||||
|
make_directory(Directory) :-
|
||||||
|
list_of_chars(Directory),
|
||||||
|
'$make_directory'(Directory).
|
||||||
@@ -23,11 +23,11 @@ use crate::indexmap::IndexSet;
|
|||||||
use crate::ref_thread_local::RefThreadLocal;
|
use crate::ref_thread_local::RefThreadLocal;
|
||||||
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use std::fs;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::io::{ErrorKind, Read, Write};
|
use std::io::{ErrorKind, Read, Write};
|
||||||
use std::iter::{once, FromIterator};
|
use std::iter::{once, FromIterator};
|
||||||
use std::fs::{OpenOptions};
|
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::ops::Sub;
|
use std::ops::Sub;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -822,6 +822,55 @@ impl MachineState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&SystemClauseType::DirectoryFiles => {
|
||||||
|
let dir = self.heap_pstr_iter(self[temp_v!(1)]).to_string();
|
||||||
|
let path = std::path::Path::new(&dir);
|
||||||
|
let mut files = Vec::new();
|
||||||
|
|
||||||
|
if let Ok(entries) = fs::read_dir(path) {
|
||||||
|
for entry in entries {
|
||||||
|
if let Ok(entry) = entry {
|
||||||
|
let name = entry.file_name().into_string().unwrap();
|
||||||
|
files.push(self.heap.put_complete_string(&name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let files_list = Addr::HeapCell(self.heap.to_list(files.into_iter()));
|
||||||
|
self.unify(self[temp_v!(2)], files_list);
|
||||||
|
}
|
||||||
|
&SystemClauseType::FileSize => {
|
||||||
|
let file = self.heap_pstr_iter(self[temp_v!(1)]).to_string();
|
||||||
|
let len = Integer::from(fs::metadata(&file).unwrap().len());
|
||||||
|
|
||||||
|
let len = self.heap.to_unifiable(HeapCellValue::Integer(Rc::new(len)));
|
||||||
|
|
||||||
|
self.unify(self[temp_v!(2)], len);
|
||||||
|
}
|
||||||
|
&SystemClauseType::FileExists => {
|
||||||
|
let file = self.heap_pstr_iter(self[temp_v!(1)]).to_string();
|
||||||
|
if !std::path::Path::new(&file).exists() || !fs::metadata(&file).unwrap().is_file() {
|
||||||
|
self.fail = true;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&SystemClauseType::DirectoryExists => {
|
||||||
|
let directory = self.heap_pstr_iter(self[temp_v!(1)]).to_string();
|
||||||
|
if !std::path::Path::new(&directory).exists() || !fs::metadata(&directory).unwrap().is_dir() {
|
||||||
|
self.fail = true;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&SystemClauseType::MakeDirectory => {
|
||||||
|
let directory = self.heap_pstr_iter(self[temp_v!(1)]).to_string();
|
||||||
|
|
||||||
|
match fs::create_dir(directory) {
|
||||||
|
Ok(_) => { }
|
||||||
|
_ => { self.fail = true;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
&SystemClauseType::AtEndOfExpansion => {
|
&SystemClauseType::AtEndOfExpansion => {
|
||||||
if self.cp == LocalCodePtr::TopLevel(0, 0) {
|
if self.cp == LocalCodePtr::TopLevel(0, 0) {
|
||||||
self.at_end_of_expansion = true;
|
self.at_end_of_expansion = true;
|
||||||
@@ -3256,7 +3305,7 @@ impl MachineState {
|
|||||||
let mode =
|
let mode =
|
||||||
atom_from!(self, indices, self.store(self.deref(self[temp_v!(2)])));
|
atom_from!(self, indices, self.store(self.deref(self[temp_v!(2)])));
|
||||||
|
|
||||||
let mut open_options = OpenOptions::new();
|
let mut open_options = fs::OpenOptions::new();
|
||||||
|
|
||||||
let (is_input_file, in_append_mode) =
|
let (is_input_file, in_append_mode) =
|
||||||
match mode.as_str() {
|
match mode.as_str() {
|
||||||
|
|||||||
Reference in New Issue
Block a user