1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2013-2015, VU University Amsterdam 7 All rights reserved. 8 9 Redistribution and use in source and binary forms, with or without 10 modification, are permitted provided that the following conditions 11 are met: 12 13 1. Redistributions of source code must retain the above copyright 14 notice, this list of conditions and the following disclaimer. 15 16 2. Redistributions in binary form must reproduce the above copyright 17 notice, this list of conditions and the following disclaimer in 18 the documentation and/or other materials provided with the 19 distribution. 20 21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 POSSIBILITY OF SUCH DAMAGE. 33*/ 34 35:- module(quasi_quotations, 36 [ with_quasi_quotation_input/3, % +Content, -Stream, :Goal 37 phrase_from_quasi_quotation/2, % :Grammar, +Content 38 quasi_quotation_syntax_error/1, % +Error 39 quasi_quotation_syntax/1 % :Syntax 40 ]). 41:- autoload(library(error),[must_be/2]). 42:- autoload(library(pure_input),[stream_to_lazy_list/2]). 43 44 45/** <module> Define Quasi Quotation syntax 46 47Inspired by 48[Haskell](http://www.haskell.org/haskellwiki/Quasiquotation), SWI-Prolog 49support _quasi quotation_. Quasi quotation allows for embedding (long) 50strings using the syntax of an external language (e.g., HTML, SQL) in 51Prolog text and syntax-aware embedding of Prolog variables in this 52syntax. At the same time, quasi quotation provides an alternative to 53represent long strings and atoms in Prolog. 54 55The basic form of a quasi quotation is defined below. Here, `Syntax` is 56an arbitrary Prolog term that must parse into a _callable_ (atom or 57compound) term and Quotation is an arbitrary sequence of characters, not 58including the sequence =||}|=. If this sequence needs to be embedded, it 59must be escaped according to the rules of the target language or the 60`quoter' must provide an escaping mechanism. 61 62 == 63 {|Syntax||Quotation|} 64 == 65 66While reading a Prolog term, and if the Prolog flag =quasi_quotes= is 67set to =true= (which is the case if this library is loaded), the parser 68collects quasi quotations. After reading the final full stop, the parser 69makes the call below. Here, `SyntaxName` is the functor name of `Syntax` 70above and `SyntaxArgs` is a list holding the arguments, i.e., `Syntax 71=.. [SyntaxName|SyntaxArgs]`. Splitting the syntax into its name and 72arguments is done to make the quasi quotation parser a predicate with a 73consistent arity 4, regardless of the number of additional arguments. 74 75 == 76 call(+SyntaxName, +Content, +SyntaxArgs, +VariableNames, -Result) 77 == 78 79The arguments are defined as 80 81 - `SyntaxName` is the principal functor of the quasi quotation syntax. 82 This must be declared using quasi_quotation_syntax/1 and there must be 83 a predicate SyntaxName/4. 84 85 - `Content` is an opaque term that carries the content of the quasi 86 quoted material and position information about the source code. It is 87 passed to with_quasi_quote_input/3. 88 89 - `SyntaxArgs` carries the additional arguments of the `Syntax`. These are 90 commonly used to make the parameter passing between the clause and the 91 quasi quotation explicit. For example: 92 93 == 94 ..., 95 {|html(Name, Address)|| 96 <tr><td>Name<td>Address</tr> 97 |} 98 == 99 100 - `VariableNames` is the complete variable dictionary of the clause as 101 it is made available throug read_term/3 with the option 102 =variable_names=. It is a list of terms `Name = Var`. 103 104 - `Result` is a variable that must be unified to resulting term. 105 Typically, this term is structured Prolog tree that carries a 106 (partial) representation of the abstract syntax tree with embedded 107 variables that pass the Prolog parameters. This term is normally 108 either passed to a predicate that serializes the abstract syntax tree, 109 or a predicate that processes the result in Prolog. For example, HTML 110 is commonly embedded for writing HTML documents (see 111 library(http/html_write)). Examples of languages that may be embedded 112 for processing in Prolog are SPARQL, RuleML or regular expressions. 113 114The file library(http/html_quasiquotations) provides the, suprisingly 115simple, quasi quotation parser for HTML. 116 117@author Jan Wielemaker. Introduction of Quasi Quotation was suggested 118 by Michael Hendricks. 119@see [Why it's nice to be quoted: quasiquoting for 120 haskell](http://www.cs.tufts.edu/comp/150FP/archive/geoff-mainland/quasiquoting.pdf) 121*/ 122 123 124:- meta_predicate 125 with_quasi_quotation_input( , , ), 126 quasi_quotation_syntax( ), 127 phrase_from_quasi_quotation( , ). 128 129:- set_prolog_flag(quasi_quotations, true). 130 131%! with_quasi_quotation_input(+Content, -Stream, :Goal) is det. 132% 133% Process the quasi-quoted Content using Stream parsed by Goal. 134% Stream is a temporary stream with the following properties: 135% 136% - Its initial _position_ represents the position of the 137% start of the quoted material. 138% - It is a text stream, using =utf8= _encoding_. 139% - It allows for repositioning 140% - It will be closed after Goal completes. 141% 142% @arg Goal is executed as once(Goal). Goal must succeed. 143% Failure or exceptions from Goal are interpreted as 144% syntax errors. 145% @see phrase_from_quasi_quotation/2 can be used to process a 146% quotation using a grammar. 147 148with_quasi_quotation_input(Content, Stream, Goal) :- 149 functor(Content, '$quasi_quotation', 3), 150 !, 151 setup_call_cleanup( 152 '$qq_open'(Content, Stream), 153 ( call(Goal) 154 -> true 155 ; quasi_quotation_syntax_error( 156 quasi_quotation_parser_failed, 157 Stream) 158 ), 159 close(Stream)). 160 161%! phrase_from_quasi_quotation(:Grammar, +Content) is det. 162% 163% Process the quasi quotation using the DCG Grammar. Failure of 164% the grammar is interpreted as a syntax error. 165% 166% @see with_quasi_quotation_input/3 for processing quotations from 167% stream. 168 169phrase_from_quasi_quotation(Grammar, Content) :- 170 functor(Content, '$quasi_quotation', 3), 171 !, 172 setup_call_cleanup( 173 '$qq_open'(Content, Stream), 174 phrase_quasi_quotation(Grammar, Stream), 175 close(Stream)). 176 177phrase_quasi_quotation(Grammar, Stream) :- 178 set_stream(Stream, buffer_size(512)), 179 stream_to_lazy_list(Stream, List), 180 phrase(Grammar, List), 181 !. 182phrase_quasi_quotation(_, Stream) :- 183 quasi_quotation_syntax_error( 184 quasi_quotation_parser_failed, 185 Stream). 186 187%! quasi_quotation_syntax(:SyntaxName) is det. 188% 189% Declare the predicate SyntaxName/4 to implement the the quasi 190% quote syntax SyntaxName. Normally used as a directive. 191 192quasi_quotation_syntax(M:Syntax) :- 193 must_be(atom, Syntax), 194 '$set_predicate_attribute'(M:Syntax/4, quasi_quotation_syntax, true). 195 196%! quasi_quotation_syntax_error(+Error) 197% 198% Report syntax_error(Error) using the current location in the 199% quasi quoted input parser. 200% 201% @throws error(syntax_error(Error), Position) 202 203quasi_quotation_syntax_error(Error) :- 204 quasi_quotation_input(Stream), 205 quasi_quotation_syntax_error(Error, Stream). 206 207quasi_quotation_syntax_error(Error, Stream) :- 208 stream_syntax_error_context(Stream, Context), 209 throw(error(syntax_error(Error), Context)). 210 211quasi_quotation_input(Stream) :- 212 '$input_context'(Stack), 213 memberchk(input(quasi_quoted, _File, _Line, StreamVar), Stack), 214 Stream = StreamVar. 215 216 217%! stream_syntax_error_context(+Stream, -Position) is det. 218% 219% Provide syntax error location for the current position of 220% Stream. 221 222stream_syntax_error_context(Stream, file(File, LineNo, LinePos, CharNo)) :- 223 stream_property(Stream, file_name(File)), 224 position_context(Stream, LineNo, LinePos, CharNo), 225 !. 226stream_syntax_error_context(Stream, stream(Stream, LineNo, LinePos, CharNo)) :- 227 position_context(Stream, LineNo, LinePos, CharNo), 228 !. 229stream_syntax_error_context(_, _). 230 231position_context(Stream, LineNo, LinePos, CharNo) :- 232 stream_property(Stream, position(Pos)), 233 !, 234 stream_position_data(line_count, Pos, LineNo), 235 stream_position_data(line_position, Pos, LinePos), 236 stream_position_data(char_count, Pos, CharNo). 237 238 239 /******************************* 240 * SYSTEM HOOK * 241 *******************************/ 242 243% system:'$parse_quasi_quotations'(+Quotations:list, +Module) is 244% det. 245% 246% @arg Quotations is a list of terms 247% 248% quasi_quotation(Syntax, Quotation, VarNames, Result) 249 250:- public 251 system:'$parse_quasi_quotations'/2. 252 253system'$parse_quasi_quotations'([], _). 254system'$parse_quasi_quotations'([H|T], M) :- 255 qq_call(H, M), 256 system:'$parse_quasi_quotations'(T, M). 257 258qq_call(quasi_quotation(Syntax, Content, VariableNames, Result), M) :- 259 current_prolog_flag(sandboxed_load, false), 260 Syntax =.. [SyntaxName|SyntaxArgs], 261 setup_call_cleanup( 262 '$push_input_context'(quasi_quoted), 263 call(M:SyntaxName, Content, SyntaxArgs, VariableNames, Result), 264 '$pop_input_context'), 265 !. 266qq_call(quasi_quotation(Syntax, Content, VariableNames, Result), M) :- 267 current_prolog_flag(sandboxed_load, true), 268 Syntax =.. [SyntaxName|SyntaxArgs], 269 Expand =.. [SyntaxName, Content, SyntaxArgs, VariableNames, Result], 270 QExpand = M:Expand, 271 '$expand':allowed_expansion(QExpand), 272 setup_call_cleanup( 273 '$push_input_context'(quasi_quoted), 274 call(QExpand), 275 '$pop_input_context'), 276 !. 277qq_call(quasi_quotation(_Syntax, Content, _VariableNames, _Result), _M) :- 278 setup_call_cleanup( 279 '$push_input_context'(quasi_quoted), 280 with_quasi_quotation_input( 281 Content, Stream, 282 quasi_quotation_syntax_error(quasi_quote_parser_failed, Stream)), 283 '$pop_input_context'), 284 !. 285 286 287 /******************************* 288 * MESSAGES * 289 *******************************/ 290 291:- multifile 292 prolog:error_message//1. 293 294prologerror_message(syntax_error(unknown_quasi_quotation_syntax(Syntax, M))) --> 295 { functor(Syntax, Name, _) }, 296 [ 'Quasi quotation syntax ~q:~q is not defined'-[M, Name] ]. 297prologerror_message(syntax_error(invalid_quasi_quotation_syntax(Syntax))) --> 298 [ 'Quasi quotation syntax must be a callable term. Found ~q'-[Syntax] ]