Skip to content

Latest commit

 

History

History
807 lines (579 loc) · 39.1 KB

File metadata and controls

807 lines (579 loc) · 39.1 KB

Relevant predicates for generating output in Prolog (by order of importance)

TL;DR

TL;DR Diagram.

Work in progress

Input sources and output sinks

Also work in progress

SWI-Prolog

From Predefined stream aliases:

Each thread has five stream aliases identified by atoms:

  • user_input: Initially denotes whatever is "stdin" on this system (POSIX file handles 0)
  • user_output: Initially denotes whatever is "stdout" on this system (POSIX file handles 1)
  • user_error: Initially denotes whatever is "stderr" on this system (POSIX file handles 2)
  • current_input: Implicit source for predicates that do not take a stream argument for input, initially user_input
    • Rebind with set_input and also see/1 if you insist on Edinburgh tradition.
  • current_output: Implicit sink for predicates that do not take a stream argument for output, initially user_error

The predicate with_output_to/2 (in section Write onto atoms, code-lists, etc.) provides temporary context for a goal to redirect temporarily output to another sink: stream, an atom, a string etc. It's a beginning! The various forms of sink are listed in the page of with_output_to/2.

There is also with_output_to_chars/2, which captures the output in a list of codes (unicode code points, not chars). Nice!

Note that there is a special format/3 where you can redirect the sink directly just for format/3.

  • Can with_output_to/2 be nested?
  • Why is there no with_input_from/2?
  • The goal given to with_output_to/2 is run as with once/1, so backtracking is lost.

(Can this be done better?)

Edinburgh Tradition

From the venerable DEC-10 Edinburgh Prolog (didn't have any "stderr" equivalent, apparently?). See Edinburgh I/O.

There is:

  • The (implicitly handled) "current input stream" handled by
    • see/1
    • seeing/1
    • seen/0 - Close the current input stream. The new input stream becomes user_input.
  • The (implicitly handled) "current output stream" -

The reserved stream name user refers to the terminal (not sure what that implies)

ISO standard

  • There may be two version of output/input predicates:
    • One taking a (mode +) output stream as sink, or a (mode +) input stream as source
    • One using an implicit source or sink (i.e. using the globally defined stream current_output (see above)

More

  • "I/O Commands" i.e. instructions that do not perform I/O when they are encountered but accumulate a sequence of operations in an accumulator, where the accumulator can be printed by some predicate later are extremely useful. This is often described using a monad structure (colloquially, "a monad") from Category Theory. One can consider this as a goal accumulator. Would it make sense?

Formatted write: "format family"

See chapter "4.32.2 Format" of the SWI-Prolog documentation.

Corresponds to the C-library's printf, fprintf, etc.

The "format family" is not standardized nor mentioned in the Prolog ISO Standard!

We read:

The format family of predicates is the most versatile and portable way to produce textual output (unfortunately not covered by any standard.)

The "format family" was defined by Quintus Prolog and currently available in many Prolog systems, although the details vary.

Predicates

Predicate Description
format/1 Takes no arguments, just the format string, which must not contain any placeholders.
Actually calls format/2 with the empty argument list.
format/2 Takes a list of arguments, or a single argument that is not a list and will be wrapped into a list.
format/x is not "lenient"!, that is:
If the number of arguments and the number of placeholders differ, an error is thrown.
(Until recently, more arguments than placeholders was accepted).
Additionally, an error is thrown if the type of the argument is such that the argument cannot be meaningfully used for a given placeholder.
format/3 Takes a list of arguments and an output target (which can be anything accepted by with_output_to/2, in particular atoms or code-lists).

Earlier versions defined sformat/3 instead of format/3. These predicates have been moved to the library library(backcomp).

Placeholders

This family supports in-text "format placeholders" aka "format specifiers". The SWI-Prolog documentation calls them "special sequences".

They are introduced with the tilde character: ~n, ~w etc.

(For C-library printf and friends and Prolog's "writef family", placeholders are introduced with the % character. Why and where did that particular change in convention from using % to using ~ arise?)

Placeholders of the C-library printf are more extensive and feature-rich than those of format/x, in particular for formatting of numeric data. See these:

The placeholders of the "format family" look as follows ~[numeric][:]flag

For a complete list of placeholders, see the description of format/2.

A cheatsheet for format/2 placeholders:

format/2 placeholder cheatsheet

Grab it from here:

SWI-Prolog: A string/atom of length 1 is interpreted as a float by the float placeholders by obtaining the code point. Prolog has too much history.

?- format("|~e|",["1"]).
|4.900000e+01| 

The above stems from this way of obtaining character codes:

?- A is "1".
A = 49.

Multi-character strings are not acceptable:

?- format("|~e|",["11"]).
ERROR: Type error: `[]' expected, found `"11"' (a string) ("x" must hold one character)

I don't know what this is about, apparently SWI-Prolog tries to call the predicate to obtain a constant:

?- format("|~e|",['1']).
ERROR: Arithmetic: `'1'/0' is not a function

as in

?- format("|~e|",[pi]).
|3.141593e+00|
true.

Escape sequences

C-style "backslash escapes" (commonly called "escape sequences") are supported. These are not the escapes sequences of the "writef family" but the ones understood by the C-library's printf procedure. format/x likely calls the C library printf more or less directly, so one would expect this to be the case. However...

What exactly performs the escaping?

I'm not sure what the Prolog lexer does with the in-String escape sequences. Does \uhhhh in a String mean that:

  • the codepoint for that character appears the resulting String in the compiled code (as is done in Java) or that
  • the character sequence \uhhhh appears "as is" in the resulting String in the compiled code, to be interpreted by format/2?

There is ambiguity here, and for some (all? most?) it seems to be the former, so these "escape sequences" are a feature of the Prolog lexer, not of format/x.

Let's test what happens with a String with escape sequences is transformed into an atom:

% Test unicode

?- S=">\u2200<", string(S), atom_string(A,S), atom(A), string_codes(S,C).
S = ">∀<",
A = >∀<,
C = [62, 8704, 60].

% Test hexadecimal

?- S=">\x5E<", string(S), atom_string(A,S), atom(A), string_codes(S,C).
S = ">^<",
A = >^<,
C = [62, 94, 60].

% Test apostrophe

?- S=">\'<", string(S), atom_string(A,S), atom(A), string_codes(S,C).
S = ">'<",
A = '>\'<',
C = [62, 39, 60].

% Test double quote

?- S=">\"<", string(S), atom_string(A,S), atom(A), string_codes(S,C).
S = ">\"<",
A = '>"<',
C = [62, 34, 60].

% Test newline

?- S=">\n<", string(S), atom_string(A,S), atom(A), string_codes(S,C).
S = ">\n<",
A = '>\n<',
C = [62, 10, 60].

So we should divide the "escape sequences" into two blocks:

  • Those handled by Prolog lexer.
  • Those handled by format/x.

Those handled by Prolog lexer

Here interpretation happens when you try it in code unrelated to format/x, as shown above.

Seq. Outputs Description
\' 0x27 Apostrophe or single quotation mark '
\" 0x22 Double quotation mark " (but the target for this would be the Prolog source lexer, not format/2)
\\ 0x5C Backslash itself \
\a 0x07 BEL aka "beep"
\b 0x08 Backspace; erase previous character
\e 0x1B Escape; should probably be followed by a special sequence that can be interpreted by the terminal emulation to set color for example
\f 0x0C Formfeed page break
\n 0x0A Newline (see also nl/x)
\r 0x0D Carriage return
\t 0x09 Horizontal Tab
\v 0x09 Vertical Tab
\n where n is a sequence of digits between 0 and 7; the character with octal code n is output (\0 yields the NUL character). This escape sequence reads octal digits until it encounters a non-octal-digit. Weird.
\xh where h is a sequence of uppercase/lowercase hexdigits; the character with hexadecimal code h is output. This escape sequence reads hexdigits until it encounters a non-hexdigit. Weird.
\uh where h is a sequence of exactly 4 hexdigits, a 16-bit UCS-2/UTF-16 code point; \u2200 outputs the "forall sign", , for example.
\Uh where h is a sequence of exactly 8 hexdigits, a 32-bit UCS-4/UTF-32 code point.
\? NO! 0x3F Prints the ? for C-library printf (it is used to avoid trigraphs), but SWI Prolog's lexer throws an error when it sees it.

Those handled by format/x:

It turns out there are none. Let's try to give to format/1 the sequence \n "as is", by drilling through the lexer level:

?- S=">\\n<", string(S), atom_string(A,S), atom(A), string_codes(S,C).
S = ">\\n<",
A = '>\\n<',
C = [62, 92, 110, 60].

So, here format/1 actually sees \n ... but outputs it unmodified:

?- format(">\\n<").
>\n<
true.

"Escape sequences in atoms"

There is an additional page which explains the above in the context of atoms, as opposed to strings, but apparently is valid for both cases (which wouldn't be surprising):

2.17.1.3 Character Escape Syntax

Within quoted atoms (using single quotes: '<atom>'), special characters are represented using escape sequences. An escape sequence is led in by the backslash \ character. The list of escape sequences is compatible with the ISO standard but contains some extensions, and the interpretation of numerically specified characters is slightly more flexible to improve compatibility.

Undefined escape characters raise a syntax_error exception.

Character escaping is only available if current_prolog_flag(character_escapes, true) is active (default). See current_prolog_flag/2.

And this paragraph alludes to the difference between what the lexer does and what writef/2 would expect as escape sequences. Confusingly, read/x is introduced.

Character escapes conflict with writef/2 in two ways:

  • \40 (for example) is interpreted as "decimal 40" by writef/2, but as "octal 40" (decimal 32) by read/x.
  • Also, the writef/2 sequence \l is illegal. It is advised to use the more widely supported format/[2,3] predicate instead. If you insist upon using writef/2, either switch character_escapes to false, or use double \\, as in writef('\\l').

Formatting hook triggered by "~"

See 4.32.3 Programming Format.

If a sequence ~c (tilde, followed by some character) is found, the format/3 and friends first check whether the user has defined a predicate to handle the format.

If not, the built-in formatting rules described by format/x, writef/x are used.

Predicate Description
format_predicate/2
current_format_predicate/2

Formatted write: "writef family"

N.B.: writef/x, not write/x, which is the predicate for serializing out terms (see further below).

See chapter "4.32.1 Writef" of the SWI-Prolog documentation.

The "writef family" is not standardized nor mentioned in the Prolog ISO Standard!

We read:

The "writef family" is compatible with Edinburgh C-Prolog and should be considered deprecated.

writef family predicates

Predicate Description
writef/1
writef/2 (page contains details)
swritef/2
swritef/3

writef family escape sequences

As above, escape sequences are interpreted by the Prolog lexer first. Any escape sequences appearing in the string of writef/2 have already been interpreted when writef/2 sees them, and they are those used by the C-library (except for \?). The escape sequences used in practice are thus exactly the same as for format/x.

In particular, at that level, \% is an unknown escape sequence and leads to an error:

?- writef("\%").
ERROR: Syntax error: Unknown character escape in quoted atom or string: `\%'

To drill through the Prolog lexer, one has to escape the backslash with a backslash:

?- writef(">\\%<").
>%<
true.

Does writef/1 actually care about a sequence like \n?

?- writef(">\\n<").
>
<
true.

Contrary to format/x, it does!

So the escape sequences listed on the page of writef/2 may well be meaningful. In practice, it doesn't look they will be used much (but suppose the string comes in over the network, not the terminal. Then they will be used.)

writef family placeholders

Placeholders are introduced with %, as is the C tradition.

For a list, see writef/2.

Printing messages (logging)

See 4.11.4 Printing messages.

Anne Ogborn has a tutorial on printing messages.

Corresponds to a logging library like SLF4J + logback or Apache log4j or java.util.logging.

(Note sure whether there is a "logging hierarchy" (a a tree of loggers that can be set to log or filter messages or not by entire subtrees), "loggers" (configurable sinks attached to the nodes of the logging hierarchy) and "formatters" (specific formatting rules applicable to logger subtrees)).

The predicate print_message/2 is used to print a message term in a human-readable format.

The other predicates from this section allow the user to refine and extend the message system. A common usage of print_message/2 is to print error messages regarding exceptions.

In particular:

Printing from libraries

Libraries should not use format/3 or other output predicates directly. Libraries that print informational output directly to the console are hard to use from code that depend on your textual output, such as a CGI script. The predicates in "Printing messages" define the API for dealing with messages. The idea behind this is that a library that wants to provide information about its status, progress, events or problems calls print_message/2. The first argument is the level. The supported levels are described with print_message/2. Libraries typically use informational and warning, while libraries should use exceptions for errors (see throw/1, type_error/2, etc.).

Package "log4p"

Package log4p implements

a simple logging library for Prolog, inspired by log4j.

Term reading and writing (serialization)

...see 4.20 Term reading and writing

Corresponds to serialization/Deserialization of objects (see also Comparison of data-serialization formats).

The following predicates are listed in the ISO standard for Prolog, chapter 8.14: read_term/(2,3), read/(1,2), write_term/(2,3), write/(1,2), writeq/(1,2), write_canonical/(1,2).

Note that reading is sensitive to the Prolog flag character_escapes, which controls the interpretation of the \ character in quoted atoms and strings.

Example:

?- with_output_to(
      string(Buf),
      write_term(a(b,c,[1,2,3],var(ZZ)),[])), 
   read_term_from_atom(Buf, T, []).
   
Buf = "a(b,c,[1,2,3],var(_7256))",
T = a(b, c, [1, 2, 3], var(_7848)).

For fast binary I/O, there is this.

Hmmm....

  1. ... How do I check terms received from the network against a specification? (As in "XML Schema"). Langsec is important! I suppose DCGs can be deployed to advantage here. (TODO: Write example code).
  2. ... Is the underlying reader (i.e. the implementation underneath read/n) robust? (as in, it's probably not been implemented using Hammer. Time for fuzzing tests? Such fun!

Notable predicates:

Predicate Description
write_term/2 The predicate write_term/2 is the generic form of all Prolog term-write predicates.
write_canonical/1 Write Term on the current output stream using standard parenthesised prefix notation (i.e., ignoring operator declarations).
write/1 Write a term to the current output, using brackets and operators where appropriate.
writeq/1 ("with quoted atoms"). Write term to the current output, using brackets and operators where appropriate. Atoms that need quotes are quoted.
writeln/1 Equivalent to write(Term), nl.. The output stream is locked, which implies no output from other threads can appear between the term and newline.
print/1 Print a term for debugging purposes. The print/1 predicate is used primarily through the ~p escape sequence of format/2, which is commonly used in the recipies used by print_message/2 to emit messages.
portray/1 A dynamic predicate, which can be defined by the user to change the behaviour of print/1 on (sub)terms.
read/1 Read the next Prolog term from the current input stream and unify it with Term. On reaching end-of-file Term is unified with the atom end_of_file. This is the same as read_term/2 using an empty option list.
read_clause/3 Equivalent to read_term/3, but sets options according to the current compilation context and optionally processes comments.
read_term/2 Read a term from the current input stream and unify the term with the first argument. The reading is controlled by options from the list of the second argument.
read_term_from_atom/3 Use read_term/3 to read the next term from Atom. Atom is either an atom or a string object (see section 5.2)
read_history/6 Similar to read_term/2 using the option variable_names, but allows for history substitutions.
prompt/2 Set prompt associated with read/1 and its derivatives.
prompt1/1 Sets the prompt for the next line to be read.

Miscellaneous

Primitive character I/O

...see 4.19 Primitive character I/O.

Notable predicates:

  • nl/0 - Write a newline character to the current output stream. On Unix systems nl/0 is equivalent to put(10).
  • put_char/1 - Write a character to the current output, obeying the encoding defined for the current output stream. Note that this may raise an exception if the encoding of the output stream cannot represent Char.

Less primitive character reading: library(readutil)

This exists in SWI Prolog: library(readutil)

Not sure yet about other implementations.

This is adapted to getting input from users. For example, using read_line_to_string/2:

?- format("Answer me!\n"), 
   read_line_to_string(user_input,S1), 
   string_lower(S1,S2), 
   (member(S2,["yes","1","ok","y","ja","oui"]) -> format("OK!") ; (format("NOK"), fail)).
   
Answer me!
|: YES
OK!

Analyzing and constructing atoms

...see 4.22 Analysing and Constructing Atoms

These predicates convert between Prolog constants and lists of character codes (which are not Strings in SWI Prolog).

Libraries to read stuff

library(readutil), library(pure_input) or libraries from the extension packages to read XML, JSON, YAML, etc.

Character representation and Wide Character support

...see 4.2 Character representation and 2.20 Wide character support.

In SWI-Prolog, character codes are always the Unicode equivalent of the encoding.

That is, if get_code/1 reads from a stream encoded as (for example) KOI8-R (used for the Cyrillic alphabet), it returns the corresponding Unicode code points. Similarly, assembling or disassembling atoms using atom_codes/2 interprets the codes as Unicode points. See "wide character encoding on streams" for details.

The "lynx" library

The lynx library provides some good stuff.

TODO: Try it!

In swipl/lib/swipl/library/lynx, we find:

  • File format.pl exporting predicate text_format:format_paragraph/2.
  • File html_style.pl exporting predicates format_style:element_css/3, format_style:css_block_options/5, format_style:css_inline_options/3, format_style:attrs_classes/2, format_style:style_css_attrs/2.
  • File html_text.pl exporting predicates html_text:html_text/1, html_text::html_text/2.
  • File pldoc_style.pl with module pldoc_style, exporting nothing but extending html_text:style.

Loading the "lynx" library

The "lynx" subdirectory of the SWI Prolog library directory is probably not in your library search path. The files won't be found automatically, even though the library directory itself is on the search path:

?- bagof(X,file_search_path(library,X),L).
L = ['/usr/local/logic/swipl/lib/swipl/library',
     '/usr/local/logic/swipl/lib/swipl/library/clp',
     '/home/paquette/.local/share/swi-prolog/pack/sldnfdraw/prolog',
     pce('prolog/lib')].

So add it, then load whatever is in file format.pl (not module format)

assertz(file_search_path(library,'/usr/local/logic/swipl/lib/swipl/library/lynx')).
use_module(library(format)).

Alternatively, one could use full paths, as in:

use_module('/usr/local/logic/swipl/lib/swipl/library/lynx/format.pl').

or even

use_module(library(lynx/format)).

After that:

?- text_format:format_paragraph("hello, world",[width(50),text_align(center)]).
                   hello, world

Updates

Update 1

Jan Wielemaker says in https://swi-prolog.discourse.group/t/too-many-arguments-to-format-2/1715/14

(SWI-)Prolog’s I/O indeed has some old flavor to it. It isn’t too bad though:

For quick and dirty output for debugging purposes, use debug/3. That is by default silent, so your application won’t crash. If you enable a message channel you probably want to see the output. Still, debug/3 ultimately uses print_message/2, which is lenient (see below). In general though, use almost invariably use ~p as this prints anything for debug purposes and thus only the number of arguments must match the format.

For printing messages to the user, use print_message/2. The print_message/2 predicate is lenient (in recent versions) in the sense that it it prints the exception that occurred without propagating the exception.

Proper printing of text is a bit rare these days, but format/3 can be used for that. This indeed suffers from positional arguments. We can use format_types/2 to realise a lint-like checker:

?- format_types('~w: ~d', Types).
Types = [any, integer].

In most cases it is probably nicer to output HTML, for which we do have a quite clean infrastructure. There is even a quite reasonable html-to-text renderer that is used by help/1 and available in the lynx directory of the library.

If you want text templating, quasi quotations would be the way to go in SWI-Prolog. One day we should add a nice quasi quotation rule for plain text. As is, only a few lines of code should suffice to connect the HTML quasi quoter to the above lynx library and enjoy nice paragraphs, underline, bold, color, lists and simple tables being rendered on your console.

More on quasi-quotation: quasi_quotations.pl -- Define Quasi Quotation syntax

None of this provides good support for logging. The HTTP server provides logging infra structure. The idea is to use library(broadcast) which implements a publish/subscribe interface. Now the application broadcasts events as Prolog terms and a subscriber can send these events somewhere (write to a file, send over the network, add to a database, …).

This would be something like a Log4J JMS Appender.

Inline function call

SWI-Prolog has dicts. Predicates can be associated to those, and they behave like function calls. This gives us function calls beyond the arithmetic evaluation of is/2. In particular, for strings you can do things like this:

Define a module called string to associate functions to dicts with tag string (there is a problem in that modules are not hierarchical so you may end up with a name clash in the unique global module space, but, let's disregard this for now):

:- module(string,[]).

% This is a predicate format/3 written as a function format/2 (maybe there
% needs to be a new descriptor syntax: format///2 ?) taking a dict tagged as
% "string" and two arguments which evaluates to the value of variable Text.

S.format(Msg,Args) := Text :- with_output_to(string(Text),format(Msg,Args)).

Once the above module has been loaded, you can do things like these, which behave like a Java "static" method call (you can move args into the dict too of course):

?- X=string{}.format("Hello, ~q\n",["World"]).
X = "Hello, \"World\"\n".
?- debug(foo),debug(foo,string{}.format("Hello, ~q\n",["World"]),[]).
% Hello, "World"

Defensive programming around format calls

format/2 is precise in the arguments it expects. If you want to make sure a call to format/2 won't generate further exceptions in situations where you are already handling an error for example:

textize(Msg,Params,Text) :-
   (is_list(Params)                           % make sure it's a for sure
    -> ListyParams = Params
    ;  ListyParams = [Params]),
   catch(
      format(string(Text),Msg,ListyParams),   % if all goes well, we are done after that
      Catcher1,                               % catch & ignore exception term in _Catcher1
      (format(user_error,"~q",Catcher1),                 % Exception handler
       catch(                                  % make doubly sure with another catch
         % We end up here if format/2 doesn't like what it sees in ListyParams.
         finagle_some_text(Msg,ListyParams,Text),
         _Catcher2,
         "Exception inside exception handling."))).
         
finagle_some_text(Msg,ListyParams,Text) :-
   maplist(
      [X,Text]>>format(string(Text),"~q",[X]),
      [Msg|ListyParams],
      Texts),
   atomic_list_concat(["Exception while printing with the following:"|Texts],",",Text).

Then

% Happy path

?- textize("Everything is going extremely ~s. We have ~s complaints",["well","no"],Text).
Text = "Everything is going extremely well. We have no complaints".

% Saves your bacon, you forgot a placeholder in the template:

?- textize("Open the %s, %s.",["pod bay doors","HAL"],Text).
error(format('too many arguments'),context(system:format/3,_3440))
Text = 'Exception while printing with the following:,"Open the %s, %s.","pod bay doors","HAL"'.

Multi-line strings (or atoms) in SWI-Prolog

Multiline strings can be written in SWI-Prolog by putting the \c escape sequence at the end of a line. This concatenates the current line with the next one, minus any indenting whitespace.

\c is not in the ISO Standard but stems from Quintus Prolog, see the Quintus Prolog Manual under "The Prolog Language > Syntax > Compound Terms > Character Escaping".

For example:

:- begin_tests(long_atoms).

test("long atom without newlines",true(TXT == 'this is a long atom that is written over several lines of source code')) :-
    TXT = 'this is a long atom \c
                    that is written over \c
               several lines of \c
                    source code',
    format("Written with writeq/1: ~q\n",TXT),
    format("Written with write/1:  ~w\n",TXT).

test("long atom with newlines",true(TXT == 'this is a long atom\nthat actually involves newlines and\nthe famous longcat')) :-
    TXT = 'this is a long atom\n\c
           that actually involves \c
           newlines and\n\c
           the famous longcat',
    format("Written with writeq/1: ~q\n",TXT),
    format("Written with write/1:  ~w\n",TXT).

test("data atom is 9*32 characters exactly") :-
   TXT = '5765206172652070726f756420746f20\c
          616e6e6f756e63652074686174206120\c
          68756d616e206578706c6f726174696f\c
          6e20636f6d70616e792066726f6d2053\c
          747261756d6c69205265616c6d206861',
   Length is 5*32,
   format("Written with writeq/1: ~q\n",TXT),
   atom_length(TXT,Length).

% Another way of getting multiline strings.
% atomic_list_concat/2 is not ISO though, but neither is \c

test("data atom is 9*32 characters exactly, with atomic_list_concat/2") :-
   atomic_list_concat(
      ['5765206172652070726f756420746f20',
       '616e6e6f756e63652074686174206120',
       '68756d616e206578706c6f726174696f',
       '6e20636f6d70616e792066726f6d2053',
       '747261756d6c69205265616c6d206861'],TXT),
   Length is 5*32,
   format("Written with writeq/1: ~q\n",TXT),
   atom_length(TXT,Length).

:- end_tests(long_atoms).

(A note concerning this has been added to the SWI-Prolog manual page for Syntax Changes).

"here documents" using library(strings) in SWI-Prolog

library(strings), provides full functionality for here documents (and some additional helper predicates).

It is based on library(quasi_quotations) which (as the name says) provides quasi-quotation syntax, as used in

See also

  • This Wikipedia entry for the origin: Quasi-quotation.
  • This entry in the Stanford Encyclopedia of Philosophy: Quotation

Here is a test case:

:- use_module(library(strings)).
  
:- begin_tests(here_document).

test("generate greeting",true(TXT == 'Dear Perl Developer,\n\nI\'m happy to announce a \nstring interpolation quasi quoter\nfor Prolog!\n')) :-
    [To,Lang] = ["Perl Developer","Prolog"],
    with_output_to(atom(TXT),
       write({|string(To,Lang)||
              | Dear {To},
              |
              | I'm happy to announce a 
              | string interpolation quasi quoter
              | for {Lang}!
              |})),
    format("~w\n",[TXT]).
    
:- end_tests(here_document).