1:- module(pwp, 2 [ pwp_files/2, % +FileIn, +FileOut 3 pwp_stream/3, % +StreamIn, +StreamOut, +Context 4 pwp_xml/3 % +DomIn, -DOMOut, +Context 5 ]). 6:- autoload(library(lists),[append/3]). 7:- autoload(library(readutil),[read_file_to_codes/3]). 8:- autoload(library(sgml),[load_xml_file/2]). 9:- autoload(library(sgml_write),[xml_write/3]). 10 11/** <module> Prolog Well-formed Pages 12 13PWP is an approach to server-side scripting using Prolog 14which is based on a simple key principle: 15 16 - The source form of a PWP should be WELL-FORMED XML 17 18Especially when generating XML rather than HTML, this is such an 19obvious thing to do. We have many kinds of XML checking tools. 20 21 - We can tell whether an XML document is WELL FORMED (all the 22 punctuation is right, all tags balance) using practically 23 any decent parser, including SWI Prolog's 'sgml'. 24 25 - If we can write a Document Type Description then we can check 26 that a document is VALID using tools like Open SP (formerly 27 James Clark's SP) or SWI Prolog's 'sgml'. This does not 28 guarantee that the output will be valid, but it does catch 29 a lot of errors very early. 30 31 - If we can write an XML Schema then we can check that a 32 document is schema-valid. (SWI Prolog's 'sgml' does not 33 yet come with a schema validator, but who knows what the 34 future holds?). 35 36 - Since an XML document is just a data structure, we can use 37 any checking tool that we can write in Prolog, IF the input 38 is well-formed so that we can load a template as a Prolog 39 data structure. 40 41Having decided that the input should be well formed, that means 42*|NO NEW SYNTAX|* 43 44None of the weird and horrible <% ... %> or whatever not-quite-XML 45stuff you see in other template systems, making checking so very hard 46(and therefore, making errors so distressingly common). 47 48That in turns means that PWP "markup" must be based on special 49elements or special attributes. The fact that an XML parser must 50allow undeclared attributes on any element even when validating, 51but must not allow undeclared elements, suggests doing this through 52attributes. In particular, one should be able to take an existing 53DTD, such as an XHTML DTD, and just use that without modification. 54So the design reduces to 55 56 - Allow dynamic data selection, insertion, and transformation 57 just using a small number of extra attributes. 58 59This description uses the following name space: 60 61 == 62 xmlns:pwp='http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl' 63 == 64 65The attributes are 66 67 - pwp:ask = Query 68 - pwp:use = Term 69 - pwp:how = text | xml 70 - pwp:tag = QName or '-' 71 - pwp:att = '' | 'one non-alphanumeric character' 72 73Here's what they mean. Each element is expanded in the context 74of a set of variable bindings. After expansion, if the tag is 75not mapped to '-', all attributes in the pwp: namespace are removed 76and the children elements are recursively expanded. 77 78 * pwp:ask = Query 79 80 * Query is a Prolog goal. For each solution of Query, the element 81 is further processed with the new variables of Query added to 82 the context. 83 84 * If Query is not a well formed Prolog goal, or if execution of 85 Query throws an exception, page transformation comes to a complete 86 halt and no page is generated. 87 88 * pwp:use = Term 89 * pwp:how = text | xml | text-file | xml-file | pwp-file 90 91 Term is a Prolog term; variables in Term are bound by the context. 92 An empty Term is regarded as a missing value for this attribute. 93 The Prolog variable CONTEXT refers to the entire context, a list 94 of Name = Value, where Name is a Prolog atom holding the name of the context variable and Value is an arbitrary Prolog term. 95 96 - If pwp:how is text, 97 The value of Term is used to define a sequence of characters. 98 99 - A number produces the same characters that write/1 would. 100 - An atom produces the same characters that write/1 would. 101 - A string produces the same characters that write/1 would. 102 - A list of character codes produces those characters. 103 104 - The following terms produce the same sequence of characters 105 that the corresponding goal would have sent to the current 106 output stream: 107 108 - write(Datum) 109 - writeq(Datum) 110 - write_canonical(Datum) 111 - print(Datum) 112 - print(Datum) 113 - format(Format) 114 - format(Format, Arguments) 115 116 - A singleton list [X] defines the characters that X defines. 117 - Any other term F(T1,...,Tn) defines the characters that T1 118 defines, followed by the characters that T2 defines, ..., 119 followed by the characters that Tn defines. 120 121 - If pwp:how is xml, 122 The value of Term must be an XML term as defined in the 123 SGML2PL documentation or a list of such terms. A single 124 term is taken as if it had been [Term]. The resulting 125 list of terms replaces the children of the current element 126 and will not be further processed. 127 128 - If pwp:how is text-file, 129 The value of Term is used to define a sequence of characters. 130 That sequence of characters is used as a file name. 131 The file is read as a sequence of characters, and that 132 sequence used as character data. 133 134 - If pwp:how is xml-file, 135 The value of Term is used to define a sequence of characters. 136 That sequence of characters is used as a file name. 137 The file is loaded as XML, and the sequence of XML items thus 138 obtained used. This means that PWP provides XML inclusion 139 without depending on the parser to support XInclude. 140 141 - If pwp:how is pwp-file, 142 Like xml-file, but PWP attributes are evaluated and processed. 143 The current context variables are passed to the PWP processor. 144 145 The default value for pwp:how is text. 146 147 * pwp:tag = QName or '-' 148 149 If pwp:tag is missing or the value is empty, the current element 150 appears in the output (after further processing) with its present 151 tag. If pwp:tag is a QName, the current element appears (...) 152 with that as its tag. That option is most useful in DTDs, where 153 an "authoring" DTD may use one tag and have it automatically mapped 154 to another tag in the output, e.g., <item> -> <li>. Finally, if 155 pwp:tag is '-', the children of the current element (either the 156 result of pwp:use or the transformed original children, whichever 157 applies) appear in the output but there is no element around them. 158 159 A missing or empty pwp:ask is just like pwp:ask = 'true'. 160 161 * pwp:att = '' | 'one non-alphanumeric character'. 162 163 Attributes in the pwp namespace are not affected by this attribute. 164 Such attributes are always stripped out and never substituted into. 165 166 If pwp:att is missing or empty, attributes of the current 167 element are copied over to the output unchanged. 168 169 If pwp:att = 'c' for some non-alphanumeric character c, 170 each attribute is examined for occurrences of c(...)c and c[...]c 171 which are as short as possible. 172 There is no one character which could be used every time, so you 173 have to explicitly choose a substitution marker which is safe 174 for the data you do not want to be altered. None of the pwp 175 attributes are inherited, least of all this one. 176 177 Text outside c(...)c groups is copied unchanged; text inside 178 a c(...)c group is parsed as a Prolog term and treated as if by 179 pwp:how = text. Text inside a c[...]c group is evaluated (in the 180 current context), and if it fails, the entire attribute will be 181 removed from the element. 182 183 184Examples: 185 186 1. *|A "Hello World" like example|* 187 188 == 189 <html 190 xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl" 191 pwp:ask = "ensure_loaded(msg), once(msg(Greeting))"> 192 <head> 193 <title pwp:use="Greeting"/> 194 </head> 195 <body> 196 <p><span pwp:use="Greeting" pwp:tag='-'/></p> 197 </body> 198 </html> 199 == 200 201 where msg.pl contains 202 203 == 204 msg('Hello, World!'). 205 == 206 207 This example illustrates an important point. Prolog Well-Formed 208 Pages provide *NO* way to physically incorporate Prolog *clauses* 209 into a page template. Prolog clauses must be put in separate 210 files which can be checked by a Prolog syntax checker, compiler, 211 cross-referencer, &c WITHOUT the Prolog tool in question needing 212 to know anything whatsoever about PWP. You load the files using 213 pwp:ask on the root element. 214 215 2. *|Binding some variables and using them|* 216 217 == 218 <html 219 xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl"> 220 <head><title>Example 2</title></head> 221 <body pwp:ask="Hello = 'Hello world', A = 20, B = 22"> 222 <h1 pwp:use="Hello"/> 223 <p>The answer is <span pwp:use="C" pwp:ask="C is A+B"/>.</p> 224 </body> 225 </html> 226 == 227 228 3. *|Making a table|* 229 We are given a Prolog database staff.pl defining 230 staff(NickName, FullName, Office, Phone, E_Mail_Address). 231 status(NickName, full_time | part_time). 232 We want to make a phone list of full time staff. 233 234 == 235 <html 236 xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl" 237 pwp:ask='ensure_loaded(staff)'> 238 <head> 239 <title>Phone list for Full-Time staff.</title> 240 </head> 241 <body> 242 <h1>Phone list for Full-Time staff.</h1> 243 <table 244 pwp:ask = "setof(FullName-Phone, 245 N^O^E^( 246 status(N, full_time), 247 staff(N, FullName, O, Phone, E) 248 ), 249 Staff_List)"> 250 <tr><th>Name</th><th>Phone</th></tr> 251 <tr pwp:ask="member(FullName-Phone, Staff_List)"> 252 <td pwp:use="FullName"/> 253 <td pwp:use="Phone"/> 254 </tr> 255 </table> 256 </body> 257 </html> 258 == 259 260 4. *|Substituting into an attribute|* 261 Same data base as before, but now we want to make a mailing list 262 page. 263 264 == 265 <html 266 xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl" 267 pwp:ask='ensure_loaded(staff)'> 268 <head> 269 <title>Phone list for Full-Time staff.</title> 270 </head> 271 <body> 272 <h1>Phone list for Full-Time staff.</h1> 273 <table 274 pwp:ask = "setof(FullName-E_Mail, 275 N^O^P^staff(N, FullName, O, P, E_Mail), 276 Staff_List)"> 277 <tr><th>Name</th><th>Address</th></tr> 278 <tr pwp:ask="member(FullName-E_Mail, Staff_List)"> 279 <td pwp:use="FullName"/> 280 <td><a pwp:use="E_Mail" 281 pwp:att='$' href="mailto:$(E_Mail)$"/></td> 282 </tr> 283 </table> 284 </body> 285 </html> 286 == 287 288 5. *|If-then-else effect|* 289 A page that displays the value of the 'SHELL' environment 290 variable if it has one, otherwise displays 'There is no 291 default shell.' 292 293 == 294 <html 295 xmlns:pwp="http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl"> 296 <head><title>$SHELL</title></head> 297 <body> 298 <p pwp:ask="getenv('SHELL', Shell)" 299 >The default shell is <span pwp:tag="-" pwp:use="Shell"/>.</p> 300 <p pwp:ask="\+getenv('SHELL',_)">There is no default shell.</p> 301 </body> 302 </html> 303 == 304 305 There is one other criterion for a good server-side template 306 language: 307 308 It should be possible to compile templates so as to eliminate 309 most if not all interpretation overhead. 310 311 This particular notation satisfies that criterion with the 312 limitation that the conversion of a term to character data requires 313 run-time traversal of terms (because the terms are not known until 314 run time). 315 316@author Richard O'Keefe 317@tbd Support compilation of PWP input files 318*/ 319 320:- meta_predicate 321 pwp_files( , ), 322 pwp_stream( , , ), 323 pwp_xml( , , ). 324 325 326%% pwp_files(:In:atom, +Out:atom) is det. 327% 328% loads an Xml document from the file named In, 329% transforms it using the PWP attributes, and 330% writes the transformed version to the new file named Out. 331 332pwp_files(M:In, Out) :- 333 load_xml_file(In, Contents), 334 pwp_xml(M:Contents, Transformed, []), 335 !, 336 setup_call_cleanup(open(Out, write, Output), 337 xml_write(Output, Transformed, []), 338 close(Output)). 339 340 341%! pwp_stream(:Input:input_stream, +Output:output_stream, 342%! +Context:list) is det. 343% 344% Loads an Xml document from the given Input stream, transforms it 345% using the PWP attributes, and writes the transformed version to 346% the given Output stream. Context provides initial contextual 347% variables and is a list of Name=Value. 348 349pwp_stream(M:Input, Output, Context) :- 350 load_xml_file(stream(Input), Contents), 351 pwp_xml(M:Contents, Transformed, Context), 352 !, 353 xml_write(Output, Transformed, []). 354 355 356/* Recall that an XML term is one of 357 358 <atom> Character Data 359 sdata(...) SDATA (SGML only) 360 ndata(...) NDATA 361 pi(...) Processing instruction 362 363 element(Name, [Att...], [Child...]) 364 365 where Att is Attribute=Value and Child is an XML term. 366 367 We are only concerned with elements; all other XML terms are 368 left alone. I have given some thought to recognising 369 370 <?pwp ...Command...?> 371 372 processing instructions, executing the Command, and removing 373 the processing instructions, as a debugging tool. But this 374 is a proof-of-concept implementation; debugging features can 375 wait for The Real Thing. 376*/ 377 378 379 380%% pwp_xml(:In:list(xml), -Out:list(xml), +Context) 381% 382% maps down a list of XML items, acting specially on elements and 383% copying everything else unchanged, including white space. 384% The Context is a list of 'VariableName'=CurrentValue bindings. 385 386pwp_xml(M:In, Out, Context) :- 387 pwp_list(In, Out, M, Context). 388 389pwp_list([], [], _, _). 390pwp_list([element(Tag0,Atts0,Kids0)|Xs], Ys0, M, Context) :- 391 !, 392 pwp_attributes(Atts0, Ask, Use, How, Att, Tag1, Atts1), 393 ( nonvar(Tag1), Tag1 \== '' -> Tag2 = Tag1 394 ; Tag2 = Tag0 395 ), 396 ( nonvar(Ask), Ask \== '', Ask \== 'true' 397 -> atom_to_term(Ask, Query, Bindings), 398 pwp_unite(Bindings, Context, Context1), 399 findall(Xml, 400 ( M:Query, 401 pwp_element(Tag2, Atts1, Kids0, Use, How, Att, 402 M, Context1, Xml)), 403 NewContent) 404 ; /* Ask is missing, empty, or true */ 405 pwp_element(Tag2, Atts1, Kids0, Use, How, Att, 406 M, Context, NewContent) 407 ), 408 pwp_attach(NewContent, Ys0, Ys1), 409 pwp_list(Xs, Ys1, M, Context). 410pwp_list([X|Xs], [X|Ys], M, Context) :- 411 pwp_list(Xs, Ys, M, Context). 412 413 414%! pwp_attributes(+Atts0:list(=(atom,atom)), 415%! -Ask:atom, -Use:atom, -How:atom, -Att:atom, 416%! -Tag:atom, -Atts1:list(=(atom,atom))) 417% 418% Walks down a list of AttributeName=ItsValue pairs, stripping out 419% those whose AttributeName begins with the 'pwp:' prefix, and 420% copying the rest to Atts1. Along the way, Ask/Use/How/Att/Tag 421% are bound to the values of the 422% pwp:ask/pwp:use/pwp:how/pwp:att/pwp:tag attributes, if any. At 423% the end, any of these variables that are still unbound REMAIN 424% unbound; they are not bound to default values. 425 426pwp_attributes([], _, _, _, _, _, []). 427pwp_attributes([AV|AVs], Ask, Use, How, Att, Tag, New_Atts1) :- 428 AV = (Name=Value), 429 ( pwp_attr(Name, PWPName) 430 -> ( pwp_attr(PWPName, Value, Ask, Use, How, Att, Tag) 431 -> New_Atts1 = New_Atts2 432 ; New_Atts1 = New_Atts2 433 ) 434 ; New_Atts1 = [AV|New_Atts2] 435 ), 436 pwp_attributes(AVs, Ask, Use, How, Att, Tag, New_Atts2). 437 438 439pwp_attr(ask, Value, Value, _Use, _How, _Att, _Tag). 440pwp_attr(use, Value, _Ask, Value, _How, _Att, _Tag). 441pwp_attr(how, Value, _Ask, _Use, Value, _Att, _Tag). 442pwp_attr(att, Value, _Ask, _Use, _How, Value, _Tag). 443pwp_attr(tag, Value, _Ask, _Use, _How, _Att, Value). 444 445%! pwp_attr(+XMLAttr, -PWPLocal) is semidet. 446% 447% True if PWPLocal is the local name of a pwp:Local expression in 448% XML. This predicate deals with the three different XML 449% representations: the form is returned of XML namespace 450% processing is not enabled. The second if it is enabled and the 451% namespace is properly defined and the last if the namespace is 452% not defined. 453 454pwp_attr(Atom, PWP) :- 455 atom(Atom), 456 atom_concat('pwp:', PWP, Atom), 457 !. 458pwp_attr('http://www.cs.otago.ac.nz/staffpriv/ok/pwp.pl':PWP, PWP) :- !. 459pwp_attr('pwp':PWP, PWP) :- !. 460pwp_attr('xmlns:pwp', -). 461 462%% pwp_unite(+Bindings, +Context0, -Context:list(=(atom,any))) 463% 464% merges the new Bindings with the bindings in the outer Context0, 465% constructing a new list of VariableName=CurrentValue bindings in 466% Context1. This is only used when the CurrentValue parts of the 467% new Bindings are known to be distinct new variables, so the 468% Bindings cannot possibly conflict with any existing binding in 469% Context0. This is O(|Bindings|.|Context0|), which is not that 470% efficient, but since we do not expect there to be very many 471% variables it doesn't matter much. 472 473pwp_unite(Bindings, Context0, Context) :- 474 pwp_unite(Bindings, Context0, Context0, Context). 475 476 477pwp_unite([], _, Context, Context). 478pwp_unite([Binding|Bindings], Context0, Context1, Context) :- 479 memberchk(Binding, Context0), 480 !, 481 pwp_unite(Bindings, Context0, Context1, Context). 482pwp_unite(['CONTEXT'=Context0|Bindings], Context0, Context1, Context) :- 483 !, 484 pwp_unite(Bindings, Context0, Context1, Context). 485pwp_unite([Binding|Bindings], Context0, Context1, Context) :- 486 pwp_unite(Bindings, Context0, [Binding|Context1], Context). 487 488 489 490%% pwp_unite(+Bindings, +Context0: list(=(atom,any))) 491% 492% looks up the bindings in Bindings in the outer Context0. 493% This is only used for 'pwp:use' terms (and the related terms 494% in $(...)$ attribute value substitutions), so that we have 495% no interest in forming a new context. (If we did, we'd use 496% pwp_unite/3 instead.) This is only used when the CurrentValue 497% parts of the new Bindings are known to be distinct new variables, 498% so the Bindings cannot possibly conflict with any existing 499% binding in Context0. However, there _could_ be new variables 500% in Bindings, and that would cause problems. An XML term may 501% not contain variables, and a term we want to convert to a list 502% of character codes had better not contain variables either. 503% One approach would be to just bind such variables to something, 504% another is to throw some kind of exception. For the moment we 505% call functor/3 so as to get an instantiation error. 506 507pwp_unite([], _). 508pwp_unite([Binding|Bindings], Context) :- 509 memberchk(Binding, Context), 510 !, 511 pwp_unite(Bindings, Context). 512pwp_unite([_=Value|_], _) :- 513 functor(Value, _, _). 514 515%% pwp_attach(+Tree, ?Ys0: list(xml), ?Ys: list(xml)) 516% 517% is a combination of "flatten" and "append". 518% It unifies Ys0\Ys with the result of flattening Tree. 519 520pwp_attach([], Ys, Ys) :- !. 521pwp_attach([X|Xs], Ys0, Ys) :- 522 !, 523 pwp_attach(X, Ys0, Ys1), 524 pwp_attach(Xs, Ys1, Ys). 525pwp_attach(X, [X|Ys], Ys). 526 527 528 529pwp_element('-', _, Kids, Use, How, _, M, Context, Xml) :- 530 !, 531 pwp_use(Use, How, Kids, M, Context, Xml). 532pwp_element(Tag, Atts, [Value], Use, How, Magic, M, Context, 533 element(Tag,Atts1,Kids1)) :- 534 verbatim_element(Tag), nonvar(Magic), atomic(Value), 535 !, 536 %% Apply substition of c(..)c variables also to content of 537 %% <script> and <style> tags: 538 pwp_substitute([cdata=Value|Atts], Magic, Context, 539 [cdata=Value1|Atts1]), 540 pwp_use(Use, How, [Value1], M, Context, Kids1). 541pwp_element(Tag, Atts, Kids, Use, How, Magic, M, Context, 542 element(Tag,Atts1,Kids1)) :- 543 ( nonvar(Magic) 544 -> pwp_substitute(Atts, Magic, Context, Atts1) 545 ; Atts1 = Atts 546 ), 547 pwp_use(Use, How, Kids, M, Context, Kids1). 548 549pwp_use('', _, Kids, M, Context, Kids1) :- 550 !, 551 pwp_list(Kids, Kids1, M, Context). 552pwp_use(Use, How, _, M, Context, Kids1) :- 553 atom_to_term(Use, Term, Bindings), 554 pwp_unite(Bindings, Context), 555 pwp_how(How, Term, M, Context, Kids1). 556 557pwp_how('text', Term, _,_, [CData]) :- 558 !, 559 pwp_use_codes(Term, Codes, []), 560 atom_codes(CData, Codes). 561pwp_how('xml', Term, _,_, Kids1) :- 562 ( Term == [] -> Kids1 = Term 563 ; Term = [_|_] -> Kids1 = Term 564 ; Kids1 = [Term] 565 ). 566pwp_how('text-file', Term, _,_, [CData]) :- 567 pwp_use_codes(Term, Codes, []), 568 atom_codes(FileName, Codes), 569 read_file_to_codes(FileName, FileCodes, []), 570 atom_codes(CData, FileCodes). 571pwp_how('xml-file', Term, _,_, Kids1) :- 572 pwp_use_codes(Term, Codes, []), 573 atom_codes(FileName, Codes), 574 load_xml_file(FileName, Kids1). 575pwp_how('pwp-file', Term, M,Context, Kids1) :- 576 pwp_use_codes(Term, Codes, []), 577 atom_codes(FileName, Codes), 578 ( memberchk('SCRIPT_DIRECTORY'=ScriptDir,Context) -> true 579 ; ScriptDir='.' 580 ), 581 absolute_file_name(FileName, PathName, [relative_to(ScriptDir)]), 582 load_xml_file(PathName, Kids0), 583 pwp_xml(M:Kids0, Kids1, Context), 584 !. 585 586 587pwp_substitute([], _, _, []). 588pwp_substitute([AV|AVs], Magic, Context, NewAvs) :- 589 AV = (Name = Value), 590 ( sub_atom(Value, _, _, _, Magic) 591 -> char_code(Magic, C), 592 atom_codes(Value, Codes), 593 pwp_split(Codes, C, B0, T0, A0, Type), 594 !, 595 ( pwp_substitute(B0, T0, A0, C, Context, V, Type) 596 -> NewAvs = [AV1|Atts1], 597 atom_codes(New_Value, V), 598 AV1 = (Name = New_Value) 599 ; Type == existence-> 600 NewAvs = Atts1 601 ), 602 pwp_substitute(AVs, Magic, Context, Atts1) 603 ). 604pwp_substitute([AV|AVs], Magic, Context, [AV|Atts1]) :- 605 pwp_substitute(AVs, Magic, Context, Atts1). 606 607 608pwp_substitute(B0, T0, A0, C, Context, V0, Type) :- 609 append(B0, V1, V0), 610 atom_codes(Atom, T0), 611 atom_to_term(Atom, Term, Bindings), 612 pwp_unite(Bindings, Context, _), 613 ( Type == value 614 -> pwp_use_codes(Term, V1, V2) 615 ; catch(Term, _, fail), 616 V2 = V1 617 ), 618 ( pwp_split(A0, C, B1, T1, A1, T2) 619 -> pwp_substitute(B1, T1, A1, C, Context, V2, T2) 620 ; V2 = A0 621 ). 622 623 624pwp_split(Codes, C, Before, Text, After, Type) :- 625 append(Before, [C,C1|Rest], Codes), 626 ( C1 == 0'( 627 -> Type = value, 628 C2 = 0') 629 ; C1 == 0'[, 630 Type = existence, 631 C2 = 0'] 632 ), 633 append(Text, [C2,C|After], Rest), 634 !. 635 636pwp_use_codes(format(Format), S0, S) :- 637 !, 638 pwp_format(Format, [], S0, S). 639pwp_use_codes(format(Format,Args), S0, S) :- 640 !, 641 pwp_format(Format, Args, S0, S). 642pwp_use_codes(write_canonical(Datum), S0, S) :- 643 !, 644 pwp_format('~k', [Datum], S0, S). 645pwp_use_codes(print(Datum), S0, S) :- 646 !, 647 pwp_format('~p', [Datum], S0, S). 648pwp_use_codes(writeq(Datum), S0, S) :- 649 !, 650 pwp_format('~q', [Datum], S0, S). 651pwp_use_codes(write(Datum), S0, S) :- 652 !, 653 pwp_format('~w', [Datum], S0, S). 654pwp_use_codes(Atomic, S0, S) :- 655 atomic(Atomic), 656 !, 657 ( number(Atomic) -> number_codes(Atomic, Codes) 658 ; atom(Atomic) -> atom_codes(Atomic, Codes) 659 ; string(Atomic) -> string_codes(Atomic, Codes) 660 ; pwp_format('~w', [Atomic], S0, S) 661 ), 662 append(Codes, S, S0). 663pwp_use_codes([X|Xs], S0, S) :- 664 pwp_is_codes([X|Xs]), 665 !, 666 append([X|Xs], S, S0). 667pwp_use_codes([X|Xs], S0, S) :- 668 !, 669 pwp_use_codes(Xs, X, S0, S). 670pwp_use_codes(Compound, S0, S) :- 671 Compound =.. [_,X|Xs], 672 pwp_use_codes(Xs, X, S0, S). 673 674 675 676pwp_use_codes([], X, S0, S) :- 677 !, 678 pwp_use_codes(X, S0, S). 679pwp_use_codes([Y|Ys], X, S0, S) :- 680 pwp_use_codes(X, S0, S1), 681 pwp_use_codes(Ys, Y, S1, S). 682 683 684 685%% pwp_is_codes(+String: any) 686% 687% is true when String is a list of integers and each of those 688% integers is a possible Unicode value (in the range U+0000..U+10FFFF). 689% Back in the days of ISO Latin 1 we would have checked for 0..255, 690% and way back in the days of ASCII for 0..127. Yes, there are more 691% than a million possible characters in Unicode; currently about 692% 100 000 of them are in use. 693 694pwp_is_codes([]). 695pwp_is_codes([C|Cs]) :- 696 integer(C), C >= 0, C =< 0x10FFFF, 697 pwp_is_codes(Cs). 698 699pwp_format(Format, Arguments, S0, S) :- 700 format(codes(S0, S), Format, Arguments). 701 702 703verbatim_element(script). 704verbatim_element(style)