639 lines
21 KiB
Prolog
639 lines
21 KiB
Prolog
:- module(pwp,
|
|
[ pwp_files/2, % +FileIn, +FileOut
|
|
pwp_stream/3, % +StreamIn, +StreamOut, +Context
|
|
pwp_xml/3 % +DomIn, -DOMOut, +Context
|
|
]).
|
|
|
|
/** <module> Prolog Well-formed Pages
|
|
|
|
PWP is an approach to server-side scripting using Prolog
|
|
which is based on a simple key principle:
|
|
|
|
- The source form of a PWP should be WELL-FORMED XML
|
|
|
|
Especially when generating XML rather than HTML, this is such an
|
|
obvious thing to do. We have many kinds of XML checking tools.
|
|
|
|
- We can tell whether an XML document is WELL FORMED (all the
|
|
punctuation is right, all tags balance) using practically
|
|
any decent parser, including SWI Prolog's 'sgml'.
|
|
|
|
- If we can write a Document Type Description then we can check
|
|
that a document is VALID using tools like Open SP (formerly
|
|
James Clark's SP) or SWI Prolog's 'sgml'. This does not
|
|
guarantee that the output will be valid, but it does catch
|
|
a lot of errors very early.
|
|
|
|
- If we can write an XML Schema then we can check that a
|
|
document is schema-valid. (SWI Prolog's 'sgml' does not
|
|
yet come with a schema validator, but who knows what the
|
|
future holds?).
|
|
|
|
- Since an XML document is just a data structure, we can use
|
|
any checking tool that we can write in Prolog, IF the input
|
|
is well-formed so that we can load a template as a Prolog
|
|
data structure.
|
|
|
|
Having decided that the input should be well formed, that means
|
|
*|NO NEW SYNTAX|*
|
|
|
|
None of the weird and horrible <% ... %> or whatever not-quite-XML
|
|
stuff you see in other template systems, making checking so very hard
|
|
(and therefore, making errors so distressingly common).
|
|
|
|
That in turns means that PWP "markup" must be based on special
|
|
elements or special attributes. The fact that an XML parser must
|
|
allow undeclared attributes on any element even when validating,
|
|
but must not allow undeclared elements, suggests doing this through
|
|
attributes. In particular, one should be able to take an existing
|
|
DTD, such as an XHTML DTD, and just use that without modification.
|
|
So the design reduces to
|
|
|
|
- Allow dynamic data selection, insertion, and transformation
|
|
just using a small number of extra attributes.
|
|
|
|
This description uses the following name space:
|
|
|
|
==
|
|
xmlns:pwp='http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl'
|
|
==
|
|
|
|
The attributes are
|
|
|
|
- pwp:ask = Query
|
|
- pwp:use = Term
|
|
- pwp:how = text | xml
|
|
- pwp:tag = QName or '-'
|
|
- pwp:att = '' | 'one non-alphanumeric character'
|
|
|
|
Here's what they mean. Each element is expanded in the context
|
|
of a set of variable bindings. After expansion, if the tag is
|
|
not mapped to '-', all attributes in the pwp: namespace are removed
|
|
and the children elements are recursively expanded.
|
|
|
|
* pwp:ask = Query
|
|
|
|
* Query is a Prolog goal. For each solution of Query, the element
|
|
is further processed with the new variables of Query added to
|
|
the context.
|
|
|
|
* If Query is not a well formed Prolog goal, or if execution of
|
|
Query throws an exception, page transformation comes to a complete
|
|
halt and no page is generated.
|
|
|
|
* pwp:use = Term
|
|
* pwp:how = text | xml | text-file | xml-file
|
|
|
|
Term is a Prolog term; variables in Term are bound by the context.
|
|
An empty Term is regarded as a missing value for this attribute.
|
|
The Prolog variable CONTEXT refers to the entire context, a list
|
|
of Name = Value, where Name is a Prolog atom holding the name of the context variable and Value is an arbitrary Prolog term.
|
|
|
|
- If pwp:how is text,
|
|
The value of Term is used to define a sequence of characters.
|
|
|
|
- A number produces the same characters that write/1 would.
|
|
- An atom produces the same characters that write/1 would.
|
|
- A string produces the same characters that write/1 would.
|
|
- A list of character codes produces those characters.
|
|
|
|
- The following terms produce the same sequence of characters
|
|
that the corresponding goal would have sent to the current
|
|
output stream:
|
|
|
|
- write(Datum)
|
|
- writeq(Datum)
|
|
- write_canonical(Datum)
|
|
- print(Datum)
|
|
- print(Datum)
|
|
- format(Format)
|
|
- format(Format, Arguments)
|
|
|
|
- A singleton list [X] defines the characters that X defines.
|
|
- Any other term F(T1,...,Tn) defines the characters that T1
|
|
defines, followed by the characters that T2 defines, ...,
|
|
followed by the characters that Tn defines.
|
|
|
|
- If pwp:how is xml,
|
|
The value of Term must be an XML term as defined in the
|
|
SGML2PL documentation or a list of such terms. A single
|
|
term is taken as if it had been [Term]. The resulting
|
|
list of terms replaces the children of the current element
|
|
and will not be further processed.
|
|
|
|
- If pwp:how is text-file,
|
|
The value of Term is used to define a sequence of characters.
|
|
That sequence of characters is used as a file name.
|
|
The file is read as a sequence of characters, and that
|
|
sequence used as character data.
|
|
|
|
- If pwp:how is xml-file,
|
|
The value of Term is used to define a sequence of characters.
|
|
That sequence of characters is used as a file name.
|
|
The file is loaded as XML, and the sequence of XML items thus
|
|
obtained used. This means that PWP provides XML inclusion
|
|
without depending on the parser to support XInclude.
|
|
|
|
The default value for pwp:how is text.
|
|
|
|
* pwp:tag = QName or '-'
|
|
|
|
If pwp:tag is missing or the value is empty, the current element
|
|
appears in the output (after further processing) with its present
|
|
tag. If pwp:tag is a QName, the current element appears (...)
|
|
with that as its tag. That option is most useful in DTDs, where
|
|
an "authoring" DTD may use one tag and have it automatically mapped
|
|
to another tag in the output, e.g., <item> -> <li>. Finally, if
|
|
pwp:tag is '-', the children of the current element (either the
|
|
result of pwp:use or the transformed original children, whichever
|
|
applies) appear in the output but there is no element around them.
|
|
|
|
A missing or empty pwp:ask is just like pwp:ask = 'true'.
|
|
|
|
* pwp:att = '' | 'one non-alphanumeric character'.
|
|
|
|
Attributes in the pwp namespace are not affected by this attribute.
|
|
Such attributes are always stripped out and never substituted into.
|
|
|
|
If pwp:att is missing or empty, attributes of the current
|
|
element are copied over to the output unchanged.
|
|
|
|
If pwp:att = 'c' for some non-alphanumeric character c,
|
|
each attribute is examined for occurrences of c(...)c which
|
|
are as short as possible.
|
|
There is no one character which could be used every time, so you
|
|
have to explicitly choose a substitution marker which is safe
|
|
for the data you do not want to be altered. None of the pwp
|
|
attributes are inherited, least of all this one.
|
|
|
|
Text outside c(...)c groups is copied unchanged; text inside
|
|
such a group is parsed as a Prolog term and treated as if by
|
|
pwp:how = text.
|
|
|
|
|
|
Examples:
|
|
|
|
1. *|A "Hello World" like example|*
|
|
|
|
==
|
|
<html
|
|
xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl"
|
|
pwp:ask = "ensure_loaded(msg), once(msg(Greeting))">
|
|
<head>
|
|
<title pwp:use="Greeting"/>
|
|
</head>
|
|
<body>
|
|
<p><span pwp:use="Greeting" pwp:tag='-'/></p>
|
|
</body>
|
|
</html>
|
|
==
|
|
|
|
where msg.pl contains
|
|
|
|
==
|
|
msg('Hello, World!').
|
|
==
|
|
|
|
This example illustrates an important point. Prolog Well-Formed
|
|
Pages provide *NO* way to physically incorporate Prolog *clauses*
|
|
into a page template. Prolog clauses must be put in separate
|
|
files which can be checked by a Prolog syntax checker, compiler,
|
|
cross-referencer, &c WITHOUT the Prolog tool in question needing
|
|
to know anything whatsoever about PWP. You load the files using
|
|
pwp:ask on the root element.
|
|
|
|
2. *|Binding some variables and using them|*
|
|
|
|
==
|
|
<html
|
|
xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl">
|
|
<head><title>Example 2</title></head>
|
|
<body pwp:ask="Hello = 'Hello world', A = 20, B = 22">
|
|
<h1 pwp:use="Hello"/>
|
|
<p>The answer is <span pwp:use="C" pwp:ask="C is A+B"/>.</p>
|
|
</body>
|
|
</html>
|
|
==
|
|
|
|
3. *|Making a table|*
|
|
We are given a Prolog database staff.pl defining
|
|
staff(NickName, FullName, Office, Phone, E_Mail_Address).
|
|
status(NickName, full_time | part_time).
|
|
We want to make a phone list of full time staff.
|
|
|
|
==
|
|
<html
|
|
xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl"
|
|
pwp:ask='ensure_loaded(staff)'>
|
|
<head>
|
|
<title>Phone list for Full-Time staff.</title>
|
|
</head>
|
|
<body>
|
|
<h1>Phone list for Full-Time staff.</h1>
|
|
<table
|
|
pwp:ask = "setof(FullName-Phone,
|
|
N^O^E^(
|
|
status(N, full_time),
|
|
staff(N, FullName, O, Phone, E)
|
|
),
|
|
Staff_List)">
|
|
<tr><th>Name</th><th>Phone</th></tr>
|
|
<tr pwp:ask="member(FullName-Phone, Staff_List)">
|
|
<td pwp:use="FullName"/>
|
|
<td pwp:use="Phone"/>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
==
|
|
|
|
4. *|Substituting into an attribute|*
|
|
Same data base as before, but now we want to make a mailing list
|
|
page.
|
|
|
|
==
|
|
<html
|
|
xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl"
|
|
pwp:ask='ensure_loaded(staff)'>
|
|
<head>
|
|
<title>Phone list for Full-Time staff.</title>
|
|
</head>
|
|
<body>
|
|
<h1>Phone list for Full-Time staff.</h1>
|
|
<table
|
|
pwp:ask = "setof(FullName-E_Mail,
|
|
N^O^P^staff(N, FullName, O, P, E_Mail),
|
|
Staff_List)">
|
|
<tr><th>Name</th><th>Address</th></tr>
|
|
<tr pwp:ask="member(FullName-E_Mail, Staff_List)">
|
|
<td pwp:use="FullName"/>
|
|
<td><a pwp:use="E_Mail"
|
|
pwp:att='$' href="mailto:$(E_Mail)$"/></td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
==
|
|
|
|
5. *|If-then-else effect|*
|
|
A page that displays the value of the 'SHELL' environment
|
|
variable if it has one, otherwise displays 'There is no
|
|
default shell.'
|
|
|
|
==
|
|
<html
|
|
xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl">
|
|
<head><title>$SHELL</title></head>
|
|
<body>
|
|
<p pwp:ask="getenv('SHELL', Shell)"
|
|
>The default shell is <span pwp:tag="-" pwp:use="Shell"/>.</p>
|
|
<p pwp:ask="\+getenv('SHELL',_)">There is no default shell.</p>
|
|
</body>
|
|
</html>
|
|
==
|
|
|
|
There is one other criterion for a good server-side template
|
|
language:
|
|
|
|
It should be possible to compile templates so as to eliminate
|
|
most if not all interpretation overhead.
|
|
|
|
This particular notation satisfies that criterion with the
|
|
limitation that the conversion of a term to character data requires
|
|
run-time traversal of terms (because the terms are not known until
|
|
run time).
|
|
|
|
@author Richard O'Keefe
|
|
@tbd Support compilation of PWP input files
|
|
*/
|
|
|
|
:- use_module(library(sgml), [load_xml_file/2]).
|
|
:- use_module(library(sgml_write), [xml_write/3]).
|
|
:- use_module(library(lists), [append/3]).
|
|
|
|
:- meta_predicate
|
|
pwp_files(:, +),
|
|
pwp_stream(:, +, +),
|
|
pwp_xml(:, -, +).
|
|
|
|
|
|
%% pwp_files(:In:atom, +Out:atom) is det.
|
|
%
|
|
% loads an Xml document from the file named In,
|
|
% transforms it using the PWP attributes, and
|
|
% writes the transformed version to the new file named Out.
|
|
|
|
pwp_files(M:In, Out) :-
|
|
load_xml_file(In, Contents),
|
|
pwp_xml(M:Contents, Transformed, []), !,
|
|
setup_call_cleanup(open(Out, write, Output),
|
|
xml_write(Output, Transformed, []),
|
|
close(Output)).
|
|
|
|
|
|
%% pwp_stream(:Input:input_stream, +Output:output_stream,
|
|
%% +Context:list) is det.
|
|
%
|
|
% Loads an Xml document from the given Input stream, transforms it
|
|
% using the PWP attributes, and writes the transformed version to
|
|
% the given Output stream. Context provides initial contextual
|
|
% variables and is a list of Name=Value.
|
|
|
|
pwp_stream(M:Input, Output, Context) :-
|
|
load_xml_file(stream(Input), Contents),
|
|
pwp_xml(M:Contents, Transformed, Context), !,
|
|
xml_write(Output, Transformed, []).
|
|
|
|
|
|
/* Recall that an XML term is one of
|
|
|
|
<atom> Character Data
|
|
sdata(...) SDATA (SGML only)
|
|
ndata(...) NDATA
|
|
pi(...) Processing instruction
|
|
|
|
element(Name, [Att...], [Child...])
|
|
|
|
where Att is Attribute=Value and Child is an XML term.
|
|
|
|
We are only concerned with elements; all other XML terms are
|
|
left alone. I have given some thought to recognising
|
|
|
|
<?pwp ...Command...?>
|
|
|
|
processing instructions, executing the Command, and removing
|
|
the processing instructions, as a debugging tool. But this
|
|
is a proof-of-concept implementation; debugging features can
|
|
wait for The Real Thing.
|
|
*/
|
|
|
|
|
|
|
|
%% pwp_xml(:In:list(xml), -Out:list(xml), +Context)
|
|
%
|
|
% maps down a list of XML items, acting specially on elements and
|
|
% copying everything else unchanged, including white space.
|
|
% The Context is a list of 'VariableName'=CurrentValue bindings.
|
|
|
|
pwp_xml(M:In, Out, Context) :-
|
|
pwp_list(In, Out, M, Context).
|
|
|
|
pwp_list([], [], _, _).
|
|
pwp_list([element(Tag0,Atts0,Kids0)|Xs], Ys0, M, Context) :- !,
|
|
pwp_attributes(Atts0, Ask, Use, How, Att, Tag1, Atts1),
|
|
( nonvar(Tag1), Tag1 \== '' -> Tag2 = Tag1
|
|
; Tag2 = Tag0
|
|
),
|
|
( nonvar(Ask), Ask \== '', Ask \== 'true'
|
|
-> atom_to_term(Ask, Query, Bindings),
|
|
pwp_unite(Bindings, Context, Context1),
|
|
findall(Xml,
|
|
( M:Query,
|
|
pwp_element(Tag2, Atts1, Kids0, Use, How, Att,
|
|
M, Context1, Xml)),
|
|
NewContent)
|
|
; /* Ask is missing, empty, or true */
|
|
pwp_element(Tag2, Atts1, Kids0, Use, How, Att,
|
|
M, Context, NewContent)
|
|
),
|
|
pwp_attach(NewContent, Ys0, Ys1),
|
|
pwp_list(Xs, Ys1, M, Context).
|
|
pwp_list([X|Xs], [X|Ys], M, Context) :-
|
|
pwp_list(Xs, Ys, M, Context).
|
|
|
|
|
|
%% pwp_attributes(+Atts0:list(=(atom,atom)),
|
|
%% -Ask:atom, -Use:atom, -How:atom, -Att:atom,
|
|
%% -Tag:atom, -Atts1:list(=(atom,atom)))
|
|
%
|
|
% Walks down a list of AttributeName=ItsValue pairs, stripping out
|
|
% those whose AttributeName begins with the 'pwp:' prefix, and
|
|
% copying the rest to Atts1. Along the way, Ask/Use/How/Att/Tag
|
|
% are bound to the values of the
|
|
% pwp:ask/pwp:use/pwp:how/pwp:att/pwp:tag attributes, if any. At
|
|
% the end, any of these variables that are still unbound REMAIN
|
|
% unbound; they are not bound to default values.
|
|
|
|
pwp_attributes([], _, _, _, _, _, []).
|
|
pwp_attributes([AV|AVs], Ask, Use, How, Att, Tag, New_Atts1) :-
|
|
AV = (Name=Value),
|
|
( pwp_attr(Name, PWPName)
|
|
-> ( pwp_attr(PWPName, Value, Ask, Use, How, Att, Tag)
|
|
-> New_Atts1 = New_Atts2
|
|
; New_Atts1 = New_Atts2
|
|
)
|
|
; New_Atts1 = [AV|New_Atts2]
|
|
),
|
|
pwp_attributes(AVs, Ask, Use, How, Att, Tag, New_Atts2).
|
|
|
|
|
|
pwp_attr(ask, Value, Value, _Use, _How, _Att, _Tag).
|
|
pwp_attr(use, Value, _Ask, Value, _How, _Att, _Tag).
|
|
pwp_attr(how, Value, _Ask, _Use, Value, _Att, _Tag).
|
|
pwp_attr(att, Value, _Ask, _Use, _How, Value, _Tag).
|
|
pwp_attr(tag, Value, _Ask, _Use, _How, _Att, Value).
|
|
|
|
%% pwp_attr(+XMLAttr, -PWPLocal) is semidet.
|
|
%
|
|
% True if PWPLocal is the local name of a pwp:Local expression in
|
|
% XML. This predicate deals with the three different XML
|
|
% representations: the form is returned of XML namespace
|
|
% processing is not enabled. The second if it is enabled and the
|
|
% namespace is properly defined and the last if the namespace is
|
|
% not defined.
|
|
|
|
pwp_attr(Atom, PWP) :-
|
|
atom(Atom),
|
|
atom_concat('pwp:', PWP, Atom), !.
|
|
pwp_attr('http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl':PWP, PWP) :- !.
|
|
pwp_attr('pwp':PWP, PWP) :- !.
|
|
pwp_attr('xmlns:pwp', -).
|
|
|
|
%% pwp_unite(+Bindings, +Context0, -Context:list(=(atom,any)))
|
|
%
|
|
% merges the new Bindings with the bindings in the outer Context0,
|
|
% constructing a new list of VariableName=CurrentValue bindings in
|
|
% Context1. This is only used when the CurrentValue parts of the
|
|
% new Bindings are known to be distinct new variables, so the
|
|
% Bindings cannot possibly conflict with any existing binding in
|
|
% Context0. This is O(|Bindings|.|Context0|), which is not that
|
|
% efficient, but since we do not expect there to be very many
|
|
% variables it doesn't matter much.
|
|
|
|
pwp_unite(Bindings, Context0, Context) :-
|
|
pwp_unite(Bindings, Context0, Context0, Context).
|
|
|
|
|
|
pwp_unite([], _, Context, Context).
|
|
pwp_unite([Binding|Bindings], Context0, Context1, Context) :-
|
|
memberchk(Binding, Context0), !,
|
|
pwp_unite(Bindings, Context0, Context1, Context).
|
|
pwp_unite(['CONTEXT'=Context0|Bindings], Context0, Context1, Context) :- !,
|
|
pwp_unite(Bindings, Context0, Context1, Context).
|
|
pwp_unite([Binding|Bindings], Context0, Context1, Context) :-
|
|
pwp_unite(Bindings, Context0, [Binding|Context1], Context).
|
|
|
|
|
|
|
|
%% pwp_unite(+Bindings, +Context0: list(=(atom,any)))
|
|
%
|
|
% looks up the bindings in Bindings in the outer Context0.
|
|
% This is only used for 'pwp:use' terms (and the related terms
|
|
% in $(...)$ attribute value substitutions), so that we have
|
|
% no interest in forming a new context. (If we did, we'd use
|
|
% pwp_unite/3 instead.) This is only used when the CurrentValue
|
|
% parts of the new Bindings are known to be distinct new variables,
|
|
% so the Bindings cannot possibly conflict with any existing
|
|
% binding in Context0. However, there _could_ be new variables
|
|
% in Bindings, and that would cause problems. An XML term may
|
|
% not contain variables, and a term we want to convert to a list
|
|
% of character codes had better not contain variables either.
|
|
% One approach would be to just bind such variables to something,
|
|
% another is to throw some kind of exception. For the moment we
|
|
% call functor/3 so as to get an instantiation error.
|
|
|
|
pwp_unite([], _).
|
|
pwp_unite([Binding|Bindings], Context) :-
|
|
memberchk(Binding, Context), !,
|
|
pwp_unite(Bindings, Context).
|
|
pwp_unite([_=Value|_], _) :-
|
|
functor(Value, _, _).
|
|
|
|
%% pwp_attach(+Tree, ?Ys0: list(xml), ?Ys: list(xml))
|
|
%
|
|
% is a combination of "flatten" and "append".
|
|
% It unifies Ys0\Ys with the result of flattening Tree.
|
|
|
|
pwp_attach([], Ys, Ys) :- !.
|
|
pwp_attach([X|Xs], Ys0, Ys) :- !,
|
|
pwp_attach(X, Ys0, Ys1),
|
|
pwp_attach(Xs, Ys1, Ys).
|
|
pwp_attach(X, [X|Ys], Ys).
|
|
|
|
|
|
|
|
pwp_element('-', _, Kids, Use, How, _, M, Context, Xml) :- !,
|
|
pwp_use(Use, How, Kids, M, Context, Xml).
|
|
pwp_element(Tag, Atts, Kids, Use, How, Magic, M, Context,
|
|
element(Tag,Atts1,Kids1)) :-
|
|
( nonvar(Magic)
|
|
-> pwp_substitute(Atts, Magic, Context, Atts1)
|
|
; Atts1 = Atts
|
|
),
|
|
pwp_use(Use, How, Kids, M, Context, Kids1).
|
|
|
|
pwp_use('', _, Kids, M, Context, Kids1) :- !,
|
|
pwp_list(Kids, Kids1, M, Context).
|
|
pwp_use(Use, How, _, _, Context, Kids1) :-
|
|
atom_to_term(Use, Term, Bindings),
|
|
pwp_unite(Bindings, Context),
|
|
pwp_how(How, Term, Kids1).
|
|
|
|
pwp_how('text', Term, [CData]) :- !,
|
|
pwp_use_codes(Term, Codes, []),
|
|
atom_codes(CData, Codes).
|
|
pwp_how('xml', Term, Kids1) :-
|
|
( Term == [] -> Kids1 = Term
|
|
; Term = [_|_] -> Kids1 = Term
|
|
; Kids1 = [Term]
|
|
).
|
|
pwp_how('text-file', Term, [CData]) :-
|
|
pwp_use_codes(Term, Codes, []),
|
|
atom_codes(FileName, Codes),
|
|
read_file_to_codes(FileName, FileCodes, []),
|
|
atom_codes(CData, FileCodes).
|
|
pwp_how('xml-file', Term, Kids1) :-
|
|
pwp_use_codes(Term, Codes, []),
|
|
atom_codes(FileName, Codes),
|
|
load_xml_file(FileName, Kids1).
|
|
|
|
|
|
pwp_substitute([], _, _, []).
|
|
pwp_substitute([AV|AVs], Magic, Context, [AV1|Atts1]) :-
|
|
AV = (Name = Value),
|
|
( sub_atom(Value, _, _, _, Magic)
|
|
-> char_code(Magic, C),
|
|
atom_codes(Value, Codes),
|
|
pwp_split(Codes, C, B0, T0, A0), !,
|
|
pwp_substitute(B0, T0, A0, C, Context, V),
|
|
atom_codes(New_Value, V),
|
|
AV1 = (Name = New_Value),
|
|
pwp_substitute(AVs, Magic, Context, Atts1)
|
|
).
|
|
pwp_substitute([AV|AVs], Magic, Context, [AV|Atts1]) :-
|
|
pwp_substitute(AVs, Magic, Context, Atts1).
|
|
|
|
|
|
pwp_substitute(B0, T0, A0, C, Context, V0) :-
|
|
append(B0, V1, V0),
|
|
atom_codes(Atom, T0),
|
|
atom_to_term(Atom, Term, Bindings),
|
|
pwp_unite(Bindings, Context, _),
|
|
pwp_use_codes(Term, V1, V2),
|
|
( pwp_split(A0, C, B1, T1, A1)
|
|
-> pwp_substitute(B1, T1, A1, C, Context, V2)
|
|
; V2 = A0
|
|
).
|
|
|
|
|
|
pwp_split(Codes, C, Before, Text, After) :-
|
|
append(Before, [C,0'(|Rest], Codes),
|
|
append(Text, [0'),C|After], Rest), !.
|
|
|
|
|
|
pwp_use_codes(format(Format), S0, S) :- !,
|
|
pwp_format(Format, [], S0, S).
|
|
pwp_use_codes(format(Format,Args), S0, S) :- !,
|
|
pwp_format(Format, Args, S0, S).
|
|
pwp_use_codes(write_canonical(Datum), S0, S) :- !,
|
|
pwp_format('~k', [Datum], S0, S).
|
|
pwp_use_codes(print(Datum), S0, S) :- !,
|
|
pwp_format('~p', [Datum], S0, S).
|
|
pwp_use_codes(writeq(Datum), S0, S) :- !,
|
|
pwp_format('~q', [Datum], S0, S).
|
|
pwp_use_codes(write(Datum), S0, S) :- !,
|
|
pwp_format('~w', [Datum], S0, S).
|
|
pwp_use_codes(Atomic, S0, S) :-
|
|
atomic(Atomic), !,
|
|
( number(Atomic) -> number_codes(Atomic, Codes)
|
|
; atom(Atomic) -> atom_codes(Atomic, Codes)
|
|
; string(Atomic) -> string_to_list(Atomic, Codes)
|
|
; pwp_format('~w', [Atomic], S0, S)
|
|
),
|
|
append(Codes, S, S0).
|
|
pwp_use_codes([X|Xs], S0, S) :-
|
|
pwp_is_codes([X|Xs]), !,
|
|
append([X|Xs], S, S0).
|
|
pwp_use_codes([X|Xs], S0, S) :- !,
|
|
pwp_use_codes(Xs, X, S0, S).
|
|
pwp_use_codes(Compound, S0, S) :-
|
|
Compound =.. [_,X|Xs],
|
|
pwp_use_codes(Xs, X, S0, S).
|
|
|
|
|
|
|
|
pwp_use_codes([], X, S0, S) :- !,
|
|
pwp_use_codes(X, S0, S).
|
|
pwp_use_codes([Y|Ys], X, S0, S) :-
|
|
pwp_use_codes(X, S0, S1),
|
|
pwp_use_codes(Ys, Y, S1, S).
|
|
|
|
|
|
|
|
%% pwp_is_codes(+String: any)
|
|
%
|
|
% is true when String is a list of integers and each of those
|
|
% integers is a possible Unicode value (in the range U+0000..U+10FFFF).
|
|
% Back in the days of ISO Latin 1 we would have checked for 0..255,
|
|
% and way back in the days of ASCII for 0..127. Yes, there are more
|
|
% than a million possible characters in Unicode; currently about
|
|
% 100 000 of them are in use.
|
|
|
|
pwp_is_codes([]).
|
|
pwp_is_codes([C|Cs]) :-
|
|
integer(C), C >= 0, C =< 0x10FFFF,
|
|
pwp_is_codes(Cs).
|
|
|
|
pwp_format(Format, Arguments, S0, S) :-
|
|
format(codes(S0, S), Format, Arguments).
|