ADDED: library(files), for reasoning about files and directories.

This commit is contained in:
Markus Triska
2020-06-19 18:20:31 +02:00
parent 376c5263f0
commit 30d1585468
4 changed files with 157 additions and 2 deletions

View File

@@ -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)

View File

@@ -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
View 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).

View File

@@ -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() {