From 893fb0e3ccd34830abaf5ce49870109a9afa217d Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Sun, 7 Nov 2021 17:02:41 +0100 Subject: [PATCH 1/8] ADDED: phrase_to_stream/2, writing a list of characters to a stream. --- src/lib/pio.pl | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/lib/pio.pl b/src/lib/pio.pl index 7755918f..67e4ea34 100644 --- a/src/lib/pio.pl +++ b/src/lib/pio.pl @@ -1,17 +1,19 @@ :- module(pio, [phrase_from_file/2, phrase_from_file/3, - phrase_to_file/2]). + phrase_to_file/2, + phrase_to_stream/2 + ]). :- use_module(library(dcgs)). :- use_module(library(error)). :- use_module(library(freeze)). -:- use_module(library(iso_ext), [setup_call_cleanup/3, partial_string/3]). -:- use_module(library(lists), [member/2]). -:- use_module(library(format), [format/3]). +:- use_module(library(iso_ext), [setup_call_cleanup/3, partial_string/1, partial_string/3]). +:- use_module(library(lists), [member/2, maplist/2]). :- meta_predicate(phrase_from_file(2, ?)). :- meta_predicate(phrase_from_file(2, ?, ?)). :- meta_predicate(phrase_to_file(2, ?)). +:- meta_predicate(phrase_to_stream(2, ?)). phrase_from_file(NT, File) :- phrase_from_file(NT, File, []). @@ -46,13 +48,12 @@ reader_step(Stream, Pos, Xs0) :- ). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - phrase_to_file(+GRBody, +File) + phrase_to_stream(+GRBody, +Stream) Emit the list of characters described by the grammar rule body - GRBody to File. File is a string, which is also the representation - used by library(files) to test for existence etc. of files. + GRBody to Stream. - An ideal implementation of phrase_to_file/2 writes each character + An ideal implementation of phrase_to_stream/2 writes each character as soon as it becomes known and no choice-points remain, and thus avoids the manifestation of the entire string in memory. See #691 for more information. @@ -64,9 +65,31 @@ reader_step(Stream, Pos, Xs0) :- represented in memory, and thus covers a large number of use cases. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +phrase_to_stream(GRBody, Stream) :- + phrase(GRBody, Cs), + ( partial_string(Cs) -> % very efficiently check for a string + true % (success is the expected case here) + ; must_be(list, Cs), + maplist(must_be(character), Cs) + ), + ( stream_property(Stream, type(binary)) -> + ( '$first_non_octet'(Cs, N) -> + domain_error(byte_char, N, phrase_to_stream/2) + ; true + ) + ; true + ), + % we use a specialised internal predicate that uses only a + % single "write" operation for efficiency. It is equivalent to + % maplist(put_char(Stream), Cs). It also works for binary streams. + '$put_chars'(Stream, Cs). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + phrase_to_file(+GRBody, +File), writing the string described + by GRBody to File. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + phrase_to_file(GRBody, File) :- - atom_chars(Atom, File), - phrase(GRBody, Chars), - setup_call_cleanup(open(Atom, write, Stream), - format(Stream, "~s", [Chars]), + setup_call_cleanup(open(File, write, Stream), + phrase_to_stream(GRBody, Stream), close(Stream)). From e32215e19adc87d8a6b43c357273a1d00ee78988 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Sun, 7 Nov 2021 17:06:56 +0100 Subject: [PATCH 2/8] express format/3 and portray_clause/2 in terms of phrase_to_stream/2 --- src/lib/format.pl | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/lib/format.pl b/src/lib/format.pl index db5eb48f..6962470e 100644 --- a/src/lib/format.pl +++ b/src/lib/format.pl @@ -83,6 +83,7 @@ :- use_module(library(error)). :- use_module(library(charsio)). :- use_module(library(between)). +:- use_module(library(pio)). format_(Fs, Args) --> { must_be(list, Fs), @@ -389,18 +390,7 @@ format(Fs, Args) :- format(Stream, Fs, Args). format(Stream, Fs, Args) :- - phrase(format_(Fs, Args), Cs), - ( stream_property(Stream, type(binary)) -> - ( '$first_non_octet'(Cs, N) -> - domain_error(byte_char, N, format/3) - ; true - ) - ; true - ), - % we use a specialised internal predicate that uses only a - % single "write" operation for efficiency. It is equivalent to - % maplist(put_char(Stream), Cs). It also works for binary streams. - '$put_chars'(Stream, Cs), + phrase_to_stream(format_(Fs, Args), Stream), flush_output(Stream). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -477,8 +467,8 @@ portray_clause(Term) :- portray_clause(Out, Term). portray_clause(Stream, Term) :- - phrase(portray_clause_(Term), Ls), - format(Stream, "~s", [Ls]). + phrase_to_stream(portray_clause_(Term), Stream), + flush_output(Stream). portray_clause_(Term) --> { unique_variable_names(Term, VNs) }, From a0a01b8ba7c9791c95a753616b9d78591c504563 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Sun, 7 Nov 2021 17:11:00 +0100 Subject: [PATCH 3/8] ADDED: library(format): DCG nonterminal portray_clause_//1, in analogy to format_//2. --- src/lib/format.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/format.pl b/src/lib/format.pl index 6962470e..47150a97 100644 --- a/src/lib/format.pl +++ b/src/lib/format.pl @@ -73,6 +73,7 @@ :- module(format, [format_//2, format/2, format/3, + portray_clause_//1, portray_clause/1, portray_clause/2, listing/1 From c44d27fb7fc8dae23df68065ab75c11c222f040d Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 8 Nov 2021 20:29:39 +0100 Subject: [PATCH 4/8] mention pure output predicates in the README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8da7f4d3..37405b45 100644 --- a/README.md +++ b/README.md @@ -464,6 +464,8 @@ The modules that ship with Scryer Prolog are also called file, reading lazily only as much as is needed. Due to the compact internal string representation, also extremely large files can be efficiently processed with Scryer Prolog in this way. + `phrase_to_file/2` and `phrase_to_stream/2` write lists of + characters described by DCGs to files and streams, respectively. * [`lambda`](src/lib/lambda.pl) Lambda expressions to simplify higher order programming. * [`charsio`](src/lib/charsio.pl) Various predicates that are useful From a9bfeb0e96ad664c0c6c03743394e70b07813518 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 8 Nov 2021 21:43:45 +0100 Subject: [PATCH 5/8] use newly available library predicate read_n_chars/3 --- src/lib/pio.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/pio.pl b/src/lib/pio.pl index 67e4ea34..25a2cba8 100644 --- a/src/lib/pio.pl +++ b/src/lib/pio.pl @@ -9,6 +9,7 @@ :- use_module(library(freeze)). :- use_module(library(iso_ext), [setup_call_cleanup/3, partial_string/1, partial_string/3]). :- use_module(library(lists), [member/2, maplist/2]). +:- use_module(library(charsio), [read_n_chars/3]). :- meta_predicate(phrase_from_file(2, ?)). :- meta_predicate(phrase_from_file(2, ?, ?)). @@ -42,7 +43,7 @@ reader_step(Stream, Pos, Xs0) :- set_stream_position(Stream, Pos), ( at_end_of_stream(Stream) -> Xs0 = [] - ; '$get_n_chars'(Stream, 4096, Cs), + ; read_n_chars(Stream, 4096, Cs), partial_string(Cs, Xs0, Xs), stream_to_lazy_list(Stream, Xs) ). From 0677f517173409d1cd09235f6f92b24789b77d11 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 8 Nov 2021 21:48:50 +0100 Subject: [PATCH 6/8] add motivation and comments about library(pio) predicates --- src/lib/pio.pl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib/pio.pl b/src/lib/pio.pl index 25a2cba8..18ceaab5 100644 --- a/src/lib/pio.pl +++ b/src/lib/pio.pl @@ -1,3 +1,14 @@ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Pure I/O + ======== + + Our goal is to encourage the use of definite clause grammars (DCGs) + for describing strings. The predicates phrase_from_file/[2,3], + phrase_to_file/2 and phrase_to_stream/2 let us apply DCGs transparently + to files and streams, and therefore decouple side-effects from + declarative descriptions. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + :- module(pio, [phrase_from_file/2, phrase_from_file/3, phrase_to_file/2, @@ -16,6 +27,13 @@ :- meta_predicate(phrase_to_file(2, ?)). :- meta_predicate(phrase_to_stream(2, ?)). +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + phrase_from_file(GRBody, File) + + True if grammar rule body GRBody covers the contents of File, + represented as a list of characters. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + phrase_from_file(NT, File) :- phrase_from_file(NT, File, []). From 2d1f182c496ba09dbfa8604f7b072f983fef14eb Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 8 Nov 2021 22:10:14 +0100 Subject: [PATCH 7/8] FIXED: instantiation errors for type(Var) in open/4 options This addresses #1030. --- src/lib/builtins.pl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/builtins.pl b/src/lib/builtins.pl index 3c9c27a1..1a92acf9 100644 --- a/src/lib/builtins.pl +++ b/src/lib/builtins.pl @@ -1429,7 +1429,10 @@ parse_stream_options(Options, OptionValues, Stub) :- parse_stream_options_(type(Type), type-Type) :- - ( nonvar(Type), lists:member(Type, [text, binary]), !, true + ( var(Type) -> + throw(error(instantiation_error, open/4)) % 8.1.3 7) + ; + lists:member(Type, [text, binary]) -> true ; throw(error(domain_error(stream_option, type(Type)), _)) ). From 3045f327d51a095b5668cb2820a523a58ee88f17 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 8 Nov 2021 22:41:04 +0100 Subject: [PATCH 8/8] FIXED: instantiation errors for alias(Var) in open/4 options This addresses #1030. --- src/lib/builtins.pl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/builtins.pl b/src/lib/builtins.pl index 1a92acf9..f8808ebb 100644 --- a/src/lib/builtins.pl +++ b/src/lib/builtins.pl @@ -1442,7 +1442,10 @@ parse_stream_options_(reposition(Bool), reposition-Bool) :- throw(error(domain_error(stream_option, reposition(Bool)), _)) ). parse_stream_options_(alias(A), alias-A) :- - ( atom(A), A \== [], !, true + ( var(A) -> + throw(error(instantiation_error, open/4)) % 8.1.3 7) + ; + atom(A), A \== [] -> true ; throw(error(domain_error(stream_option, alias(A)), _)) ).