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 diff --git a/src/lib/builtins.pl b/src/lib/builtins.pl index 3c9c27a1..f8808ebb 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)), _)) ). @@ -1439,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)), _)) ). diff --git a/src/lib/format.pl b/src/lib/format.pl index db5eb48f..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 @@ -83,6 +84,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 +391,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 +468,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) }, diff --git a/src/lib/pio.pl b/src/lib/pio.pl index 7755918f..18ceaab5 100644 --- a/src/lib/pio.pl +++ b/src/lib/pio.pl @@ -1,17 +1,38 @@ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 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]). + 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]). +:- use_module(library(charsio), [read_n_chars/3]). :- 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(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, []). @@ -40,19 +61,18 @@ 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) ). /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 +84,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)).