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
|
||||
`format_time//2` to describe strings with dates and times, and
|
||||
`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)
|
||||
Provides *delimited continuations* via `reset/3` and `shift/1`.
|
||||
* [`random`](src/lib/random.pl)
|
||||
|
||||
@@ -171,6 +171,11 @@ pub enum SystemClauseType {
|
||||
CurrentHostname,
|
||||
CurrentInput,
|
||||
CurrentOutput,
|
||||
DirectoryFiles,
|
||||
FileSize,
|
||||
FileExists,
|
||||
DirectoryExists,
|
||||
MakeDirectory,
|
||||
DeleteAttribute,
|
||||
DeleteHeadAttribute,
|
||||
DynamicModuleResolution(usize),
|
||||
@@ -328,6 +333,11 @@ impl SystemClauseType {
|
||||
&SystemClauseType::CurrentInput => clause_name!("$current_input"),
|
||||
&SystemClauseType::CurrentHostname => clause_name!("$current_hostname"),
|
||||
&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::UseModule) => clause_name!("$use_module"),
|
||||
&SystemClauseType::REPL(REPLCodePtr::UseQualifiedModule) => {
|
||||
@@ -654,6 +664,11 @@ impl SystemClauseType {
|
||||
("$unwind_environments", 0) => Some(SystemClauseType::UnwindEnvironments),
|
||||
("$unwind_stack", 0) => Some(SystemClauseType::UnwindStack),
|
||||
("$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_from_file", 1) =>
|
||||
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 std::cmp;
|
||||
use std::fs;
|
||||
use std::collections::BTreeSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{ErrorKind, Read, Write};
|
||||
use std::iter::{once, FromIterator};
|
||||
use std::fs::{OpenOptions};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::ops::Sub;
|
||||
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 => {
|
||||
if self.cp == LocalCodePtr::TopLevel(0, 0) {
|
||||
self.at_end_of_expansion = true;
|
||||
@@ -3256,7 +3305,7 @@ impl MachineState {
|
||||
let mode =
|
||||
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) =
|
||||
match mode.as_str() {
|
||||
|
||||
Reference in New Issue
Block a user