From 30d158546820becdf43140b1c601cb28c51c30ec Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Fri, 19 Jun 2020 18:20:31 +0200 Subject: [PATCH] ADDED: library(files), for reasoning about files and directories. --- README.md | 3 ++ src/clause_types.rs | 15 +++++++ src/lib/files.pl | 88 +++++++++++++++++++++++++++++++++++++ src/machine/system_calls.rs | 53 +++++++++++++++++++++- 4 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/lib/files.pl diff --git a/README.md b/README.md index d8c4acd8..a04aecee 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/src/clause_types.rs b/src/clause_types.rs index 7b7923c7..66222a6d 100644 --- a/src/clause_types.rs +++ b/src/clause_types.rs @@ -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)), diff --git a/src/lib/files.pl b/src/lib/files.pl new file mode 100644 index 00000000..4667fadd --- /dev/null +++ b/src/lib/files.pl @@ -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). diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 97c16867..8e25941e 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -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() { -- 2.54.0