]> Repositorios git - scryer-prolog.git/commitdiff
ADDED: library(files), for reasoning about files and directories.
authorMarkus Triska <[email protected]>
Fri, 19 Jun 2020 16:20:31 +0000 (18:20 +0200)
committerMarkus Triska <[email protected]>
Wed, 24 Jun 2020 20:29:34 +0000 (22:29 +0200)
README.md
src/clause_types.rs
src/lib/files.pl [new file with mode: 0644]
src/machine/system_calls.rs

index d8c4acd84caa506b29ceb93daccde5a22a859c2a..a04aeceeb9ae82e4f521d38f0dcc87536d3d4320 100644 (file)
--- a/README.md
+++ b/README.md
@@ -418,6 +418,9 @@ The modules that ship with Scryer&nbsp;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)
index 7b7923c75a059f9ad313b71f207465fa7ea6942c..66222a6dd7e577bb7b636f9db26b331d745a2409 100644 (file)
@@ -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 (file)
index 0000000..4667fad
--- /dev/null
@@ -0,0 +1,88 @@
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+   Written June 2020 by Markus Triska ([email protected])
+   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).
index 97c1686749a3afcae0bb8c03c668306a71037a44..8e25941eff187eced903ab3e4e646efa00933d73 100644 (file)
@@ -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() {