35
36:- module(term_html,
37 [ term//2 38 ]). 39:- use_module(library(http/html_write)). 40:- use_module(library(option)). 41:- use_module(library(error)). 42:- use_module(library(debug)). 43:- use_module(library(http/json)). 44
45:- multifile
46 blob_rendering//3, 47 portray//2, 48 layout/3. 49
50:- meta_predicate
51 term(+, :, ?, ?).
79term(Term, Options) -->
80 { must_be(acyclic, Term),
81 meta_options(is_meta, Options, Options1),
82 merge_options(Options1,
83 [ priority(1200),
84 max_depth(1 000 000 000),
85 depth(0)
86 ],
87 Options2),
88 dict_options(Dict, Options2)
89 },
90 any(Term, Dict),
91 finalize_term(Term, Dict).
92
93is_meta(emit).
94
95:- html_meta
96 embrace(html,+,?,?),
97 emit(html,+,?,?).
104emit(HTML, Options) -->
105 { get_dict(emit, Options, Closure) },
106 !,
107 call(Closure, HTML).
108emit(HTML, _Options) -->
109 html(HTML).
116any(_, Options) -->
117 { Options.depth >= Options.max_depth },
118 !,
119 emit(span(class('pl-ellipsis'), ...), Options).
120any(Term, Options) -->
121 ( { nonvar(Term)
122 ; attvar(Term)
123 }
124 -> portray(Term, Options)
125 ),
126 !.
127any(Term, Options) -->
128 { primitive(Term, Class0),
129 !,
130 quote_atomic(Term, S, Options),
131 primitive_class(Class0, Term, S, Class)
132 },
133 emit(span([class(Class)], S), Options).
134any(Term, Options) -->
135 { blob(Term,Type), Term \== [] },
136 !,
137 ( blob_rendering(Type,Term,Options)
138 -> []
139 ; emit(span(class('pl-blob'),['<',Type,'>']), Options)
140 ).
141any(Term, Options) -->
142 { is_dict(Term), !
143 },
144 dict(Term, Options).
145any(Term, Options) -->
146 { assertion((compound(Term);Term==[]))
147 },
148 compound(Term, Options).
154compound('$VAR'(Var), Options) -->
155 { ( Options.get(numbervars) == true
156 ; Options.get(portray) == true
157 ),
158 !,
159 format(string(S), '~W', ['$VAR'(Var), [numbervars(true)]]),
160 ( S == "_"
161 -> Class = 'pl-anon'
162 ; Class = 'pl-var'
163 )
164 },
165 emit(span([class(Class)], S), Options).
166compound(List, Options) -->
167 { ( List == []
168 ; List = [_|_] 169 ),
170 !,
171 arg_options(Options, _{priority:999}, ArgOptions)
172 },
173 list(List, ArgOptions).
174compound({X}, Options) -->
175 !,
176 { arg_options(Options, _{priority:1200}, ArgOptions) },
177 emit(span(class('pl-curl'), [ '{', \any(X, ArgOptions), '}' ]), Options).
178compound(OpTerm, Options) -->
179 { compound_name_arity(OpTerm, Name, 1),
180 is_op1(Name, Type, Pri, ArgPri, Options),
181 \+ Options.get(ignore_ops) == true
182 },
183 !,
184 op1(Type, Pri, OpTerm, ArgPri, Options).
185compound(OpTerm, Options) -->
186 { compound_name_arity(OpTerm, Name, 2),
187 is_op2(Name, Type, LeftPri, Pri, RightPri, Options),
188 \+ Options.get(ignore_ops) == true
189 },
190 !,
191 op2(Pri, OpTerm, Type, LeftPri, RightPri, Options).
192compound(Compound, Options) -->
193 { compound_name_arity(Compound, Name, Arity),
194 quote_atomic(Name, S, Options.put(embrace, never)),
195 arg_options(Options, _{priority:999}, ArgOptions),
196 extra_classes(Compound, Classes, Attrs, Options)
197 },
198 emit(span([ class(['pl-compound','pl-adaptive'|Classes]),
199 'data-arity'(Arity),
200 'data-name'(Name)
201 | Attrs
202 ],
203 [ span(class(['pl-functor', 'pl-trigger']),
204 [ S, \punct('(', Options) ]),
205 span(class('pl-compound-args'),
206 [ \args(0, Arity, Compound, ArgOptions)
207 ])
208 ]), Options).
209
(Term, Classes, OAttrs, Options) :-
211 findall(A, extra_attr(Term, A, Options), Attrs),
212 partition(is_class_attr, Attrs, CAttrs, OAttrs),
213 maplist(arg(1), CAttrs, Classes).
214
215is_class_attr(class(_)).
216
(_, class('pl-level-0'), Options) :-
218 Options.depth == 0.
219extra_attr(Term, 'data-layout'(Data), Options) :-
220 layout(Term, Layout, Options),
221 ( is_dict(Layout)
222 -> atom_json_dict(Data, Layout, [])
223 ; Data = Layout
224 ).
232arg_options(Options, Options.put(depth, NewDepth)) :-
233 NewDepth is Options.depth+1.
234arg_options(Options, Extra, Options.put(depth, NewDepth).put(Extra)) :-
235 NewDepth is Options.depth+1.
241args(Arity, Arity, _, _) --> !.
242args(I, Arity, Compound, Options) -->
243 { NI is I + 1,
244 arg(NI, Compound, Arg)
245 },
246 ( {NI == Arity}
247 -> emit([ span(class('pl-compound-arg'), \any(Arg, Options)),
248 span(class(['pl-compound-close', 'pl-punct']), ')')
249 ], Options)
250 ; emit(span(class('pl-compound-arg'),
251 [ \any(Arg, Options), \punct(',', Options) ]), Options),
252 args(NI, Arity, Compound, Options)
253 ).
254
255punct(Punct, Options) -->
256 emit(span(class('pl-punct'), Punct), Options).
262list(List, Options) -->
263 { '$skip_list'(Length, List, Tail),
264 ( Tail == []
265 -> Attr = ['data-length'(Length)]
266 ; Attr = ['data-length'(Length), 'data-partial'(true)]
267 )
268 },
269 emit(span([ class(['pl-list','pl-adaptive'])
270 | Attr
271 ],
272 [ span(class(['pl-list-open', 'pl-trigger', 'pl-punct']), '['),
273 \list_content(List, Options),
274 span(class(['pl-list-close', 'pl-punct']), ']')
275 ]), Options).
276
277list_content([], _Options) -->
278 !,
279 [].
280list_content([H|T], Options) -->
281 !,
282 { arg_options(Options, ArgOptions),
283 ( T == []
284 -> Sep = [],
285 Next = end
286 ; Options.depth + 1 >= Options.max_depth
287 -> Sep = [span(class('pl-punct'), '|')],
288 Next = depth_limit
289 ; (var(T) ; \+ T = [_|_])
290 -> Sep = [span(class('pl-punct'), '|')],
291 Next = tail
292 ; Sep = [span(class('pl-punct'), [',', ' '])],
293 Next = list
294 )
295 },
296 emit(span(class('pl-list-el'),
297 [ \any(H, Options) | Sep ]), Options),
298 list_next(Next, T, ArgOptions).
299
300list_next(end, _, _) --> !.
301list_next(depth_limit, _, Options) -->
302 !,
303 emit(span(class('pl-ellipsis'), ...), Options).
304list_next(tail, Value, Options) -->
305 { var(Value)
306 -> Class = 'pl-var-tail'
307 ; Class = 'pl-nonvar-tail'
308 },
309 emit(span(class(Class), \any(Value, Options)), Options).
310list_next(list, Tail, Options) -->
311 list_content(Tail, Options).
317is_op1(Name, Type, Pri, ArgPri, Options) :-
318 operator_module(Module, Options),
319 current_op(Pri, OpType, Module:Name),
320 argpri(OpType, Type, Pri, ArgPri),
321 !.
322
323argpri(fx, prefix, Pri0, Pri) :- Pri is Pri0 - 1.
324argpri(fy, prefix, Pri, Pri).
325argpri(xf, postfix, Pri0, Pri) :- Pri is Pri0 - 1.
326argpri(yf, postfix, Pri, Pri).
327
331
332is_op2(Name, Type, LeftPri, Pri, RightPri, Options) :-
333 operator_module(Module, Options),
334 current_op(Pri, Type, Module:Name),
335 infix_argpri(Type, LeftPri, Pri, RightPri),
336 !.
337
338infix_argpri(xfx, ArgPri, Pri, ArgPri) :- ArgPri is Pri - 1.
339infix_argpri(yfx, Pri, Pri, ArgPri) :- ArgPri is Pri - 1.
340infix_argpri(xfy, ArgPri, Pri, Pri) :- ArgPri is Pri - 1.
346operator_module(Module, Options) :-
347 Module = Options.get(module),
348 !.
349operator_module(TypeIn, _) :-
350 '$module'(TypeIn, TypeIn).
354op1(Type, Pri, Term, ArgPri, Options) -->
355 { Pri > Options.priority },
356 !,
357 embrace(\op1(Type, Term, ArgPri, Options), Options).
358op1(Type, _, Term, ArgPri, Options) -->
359 op1(Type, Term, ArgPri, Options).
360
361op1(prefix, Term, ArgPri, Options) -->
362 { Term =.. [Functor,Arg],
363 arg_options(Options, DepthOptions),
364 FuncOptions = DepthOptions.put(embrace, never),
365 ArgOptions = DepthOptions.put(priority, ArgPri),
366 quote_atomic(Functor, S, FuncOptions),
367 extra_classes(Term, Classes, Attrs, Options.put(op, prefix))
368 },
369 emit(span([ class(['pl-compound', 'pl-op', 'pl-prefix-op'|Classes]),
370 'data-arity'(1),
371 'data-name'(Functor)
372 | Attrs
373 ],
374 [ span(class(['pl-functor', 'pl-trigger']), S),
375 \space(Functor, Arg, o, a, FuncOptions, ArgOptions),
376 \op_arg(Arg, ArgOptions)
377 ]), Options).
378op1(postfix, Term, ArgPri, Options) -->
379 { Term =.. [Functor,Arg],
380 arg_options(Options, DepthOptions),
381 ArgOptions = DepthOptions.put(priority, ArgPri),
382 FuncOptions = DepthOptions.put(embrace, never),
383 quote_atomic(Functor, S, FuncOptions),
384 extra_classes(Term, Classes, Attrs, Options.put(op, postfix))
385 },
386 emit(span([ class(['pl-compound', 'pl-op', 'pl-postfix-op'|Classes]),
387 'data-arity'(1),
388 'data-name'(Functor)
389 | Attrs
390 ],
391 [ \op_arg(Arg, ArgOptions),
392 \space(Arg, Functor, a, o, ArgOptions, FuncOptions),
393 span(class('pl-functor'), S)
394 ]), Options).
398op2(Pri, Term, Type, LeftPri, RightPri, Options) -->
399 { Pri > Options.priority },
400 !,
401 embrace(\op2(Term, Type, LeftPri, RightPri, Options), Options).
402op2(_, Term, Type, LeftPri, RightPri, Options) -->
403 op2(Term, Type, LeftPri, RightPri, Options).
404
405op2(Term, xfy, LeftPri, RightPri, Options) -->
406 { functor(Term, Functor, 2),
407 quote_op(Functor, S, Options),
408 xfy_list(Term, Functor, List),
409 List \== [],
410 !,
411 arg_options(Options, DepthOptions),
412 ArgOptions = DepthOptions.put(#{priority:LeftPri, quoted_op:S}),
413 extra_classes(Term, Classes, Attrs, Options.put(op, infix))
414 },
415 emit(span([ class(['pl-op-seq', 'pl-adaptive'|Classes])
416 | Attrs
417 ],
418 \op_seq(List, Functor, RightPri, ArgOptions)), Options).
419op2(Term, _Type, LeftPri, RightPri, Options) -->
420 { Term =.. [Functor,Left,Right],
421 arg_options(Options, DepthOptions),
422 LeftOptions = DepthOptions.put(priority, LeftPri),
423 FuncOptions = DepthOptions.put(embrace, never),
424 RightOptions = DepthOptions.put(priority, RightPri),
425 ( ( need_space(Left, Functor, a, o, LeftOptions, FuncOptions)
426 ; need_space(Functor, Right, o, a, FuncOptions, RightOptions)
427 )
428 -> Space = ' '
429 ; Space = ''
430 ),
431 quote_op(Functor, S, Options),
432 extra_classes(Term, Classes, Attrs, Options.put(op, infix))
433 },
434 emit(span([ class([ 'pl-compound', 'pl-adaptive', 'pl-op', 'pl-infix-op'
435 | Classes
436 ]),
437 'data-arity'(2),
438 'data-name'(Functor)
439 | Attrs
440 ],
441 [ \op_arg(Left, LeftOptions),
442 Space,
443 span(class(['pl-functor', 'pl-trigger']), S),
444 Space,
445 \op_arg(Right, RightOptions)
446 ]), Options).
450op_arg(Atom, Options) -->
451 { atom(Atom),
452 operator_module(Module, Options),
453 current_op(_,_,Module:Atom)
454 }, !,
455 embrace(\any(Atom, Options.put(embrace, never)), Options).
456op_arg(Any, Options) -->
457 any(Any, Options).
458
459op_seq([Last], _Functor, LastPri, Options) -->
460 !,
461 { LastOptions = Options.put(priority, LastPri)
462 },
463 emit(span(class('pl-op-seq-el'), \op_arg(Last, LastOptions)), Options).
464op_seq([H|T], Functor, LastPri, Options) -->
465 emit(span(class('pl-op-seq-el'),
466 [ \op_arg(H, Options),
467 \left_space(H, Functor, Options),
468 span(class('pl-infix'), Options.quoted_op)
469 ]), Options),
470 op_seq(T, Functor, LastPri, Options).
471
472left_space(Left, Functor, Options) -->
473 { need_space(Left, Functor, a, o, Options, Options.put(embrace, never))
474 },
475 !,
476 emit(' ', Options).
477left_space(_,_,_) -->
478 [].
479
480xfy_list(Term, Name, List),
481 compound(Term),
482 compound_name_arguments(Term, Name, [A,B]) =>
483 List = [A|T],
484 xfy_list(B, Name, T).
485xfy_list(Term, _, List) =>
486 List = [Term].
493embrace(HTML, Options) -->
494 emit(span(class('pl-embrace'),
495 [ span(class('pl-parenthesis'), '('),
496 span(class('pl-embraced'),\emit(HTML, Options)),
497 span(class('pl-parenthesis'), ')')
498 ]), Options).
505space(T1, T2, C1, C2, LeftOptions, RightOptions) -->
506 { need_space(T1, T2, C1, C2, LeftOptions, RightOptions) },
507 emit(' ', RightOptions).
508space(_, _, _, _, _, _) -->
509 [].
510
511need_space(T1, T2, _, _, _, _) :-
512 ( is_solo(T1)
513 ; is_solo(T2)
514 ),
515 !,
516 fail.
517need_space(T1, T2, C1, C2, LeftOptions, RightOptions) :-
518 end_code_type(T1, C1, TypeR, LeftOptions.put(side, right)),
519 end_code_type(T2, C2, TypeL, RightOptions.put(side, left)),
520 \+ no_space(TypeR, TypeL).
521
522no_space(punct, _).
523no_space(_, punct).
524no_space(quote(R), quote(L)) :-
525 !,
526 R \== L.
527no_space(alnum, symbol).
528no_space(symbol, alnum).
536end_code_type(Atom, a, Type, Options) :-
537 atom(Atom),
538 operator_module(Module, Options),
539 current_op(_,_,Module:Atom),
540 !,
541 Type = punct.
542end_code_type(Atom, _, Type, Options) :-
543 end_code_type(Atom, Type, Options).
544
545end_code_type(_, Type, Options) :-
546 Options.depth >= Options.max_depth,
547 !,
548 Type = symbol.
549end_code_type(Term, Type, Options) :-
550 primitive(Term, _),
551 !,
552 quote_atomic(Term, S, Options),
553 end_type(S, Type, Options).
554end_code_type(Dict, Type, Options) :-
555 is_dict(Dict, Tag),
556 !,
557 ( Options.side == left
558 -> end_code_type(Tag, Type, Options)
559 ; Type = punct
560 ).
561end_code_type('$VAR'(Var), Type, Options) :-
562 Options.get(numbervars) == true,
563 !,
564 format(string(S), '~W', ['$VAR'(Var), [numbervars(true)]]),
565 end_type(S, Type, Options).
566end_code_type(List, Type, _) :-
567 ( List == []
568 ; List = [_|_]
569 ),
570 !,
571 Type = punct.
572end_code_type(Blob, Type, Options) :-
573 blob(Blob, Tag),
574 !, 575 ( Options.side == left
576 -> end_code_type(Tag, Type, Options)
577 ; Type = symbol
578 ).
579end_code_type(OpTerm, Type, Options) :-
580 compound_name_arity(OpTerm, Name, 1),
581 is_op1(Name, OpType, Pri, ArgPri, Options),
582 \+ Options.get(ignore_ops) == true,
583 !,
584 ( Pri > Options.priority
585 -> Type = punct
586 ; ( OpType == prefix, Options.side == left
587 -> end_code_type(Name, Type, Options)
588 ; OpType == postfix, Options.side == right
589 -> end_code_type(Name, Type, Options)
590 ; arg(1, OpTerm, Arg),
591 arg_options(Options, ArgOptions),
592 op_end_code_type(Arg, Type, ArgOptions.put(priority, ArgPri))
593 )
594 ).
595end_code_type(OpTerm, Type, Options) :-
596 compound_name_arity(OpTerm, Name, 2),
597 is_op2(Name, _Type, LeftPri, Pri, RightPri, Options),
598 \+ Options.get(ignore_ops) == true,
599 !,
600 ( Pri > Options.priority
601 -> Type = punct
602 ; Options.side == left
603 -> arg(1, OpTerm, Arg),
604 arg_options(Options, ArgOptions),
605 op_end_code_type(Arg, Type, ArgOptions.put(priority, LeftPri))
606 ; Options.side == right
607 -> arg(2, OpTerm, Arg),
608 arg_options(Options, ArgOptions),
609 op_end_code_type(Arg, Type, ArgOptions.put(priority, RightPri))
610 ).
611end_code_type(Compound, Type, Options) :-
612 compound_name_arity(Compound, Name, _),
613 end_code_type(Name, Type, Options).
614
615op_end_code_type(Atom, Type, Options) :-
616 end_code_type(Atom, a, Type, Options).
617
618end_type(S, Type, Options) :-
619 number(S),
620 !,
621 ( (S < 0 ; S == -0.0),
622 Options.side == left
623 -> Type = symbol
624 ; Type = alnum
625 ).
626end_type(S, Type, Options) :-
627 Options.side == left,
628 !,
629 sub_string(S, 0, 1, _, Start),
630 syntax_type(Start, Type).
631end_type(S, Type, _) :-
632 sub_string(S, _, 1, 0, End),
633 syntax_type(End, Type).
634
635syntax_type("\"", quote(double)) :- !.
636syntax_type("\'", quote(single)) :- !.
637syntax_type("\`", quote(back)) :- !.
638syntax_type(S, Type) :-
639 string_code(1, S, C),
640 ( code_type(C, prolog_identifier_continue)
641 -> Type = alnum
642 ; code_type(C, prolog_symbol)
643 -> Type = symbol
644 ; code_type(C, space)
645 -> Type = layout
646 ; Type = punct
647 ).
652dict(Term, Options) -->
653 { dict_pairs(Term, Tag, Pairs),
654 quote_atomic(Tag, S, Options.put(embrace, never)),
655 arg_options(Options, ArgOptions)
656 },
657 emit(span(class(['pl-dict', 'pl-adaptive']),
658 [ span(class(['pl-tag', 'pl-trigger']), S),
659 span(class(['pl-dict-open', 'pl-punct']), '{'),
660 span(class('pl-dict-body'),
661 [ span(class('pl-dict-kvs'),
662 \dict_kvs(Pairs, ArgOptions)),
663 span(class(['pl-dict-close', 'pl-punct']), '}')
664 ])
665 ]), Options).
666
667dict_kvs([], _) --> [].
668dict_kvs(_, Options) -->
669 { Options.depth >= Options.max_depth },
670 !,
671 emit(span(class('pl-ellipsis'), ...), Options).
672dict_kvs(KVs, Options) -->
673 dict_kvs2(KVs, Options).
674
675dict_kvs2([], _) -->
676 [].
677dict_kvs2([K-V|T], Options) -->
678 { quote_atomic(K, S, Options),
679 end_code_type(V, VType, Options.put(side, left)),
680 ( VType == symbol
681 -> VSpace = ' '
682 ; VSpace = ''
683 ),
684 arg_options(Options, ArgOptions),
685 ( T == []
686 -> Sep = []
687 ; Sep = [\punct(',', Options), ' ']
688 )
689 },
690 emit(span(class('pl-dict-kv'),
691 [ span(class('pl-key'), [S, \punct(:, Options)]),
692 VSpace,
693 span(class('pl-dict-value'),
694 [ \any(V, ArgOptions)
695 | Sep
696 ])
697 ]), Options),
698 dict_kvs2(T, Options).
699
700quote_atomic(Float, String, Options) :-
701 float(Float),
702 Format = Options.get(float_format),
703 !,
704 format(string(String), Format, [Float]).
705quote_atomic(Plain, String, Options) :-
706 atomic(Plain),
707 Format = Options.get(format),
708 !,
709 format(string(String), Format, [Plain]).
710quote_atomic(Plain, String, Options) :-
711 rational(Plain),
712 \+ integer(Plain),
713 !,
714 operator_module(Module, Options),
715 format(string(String), '~W', [Plain, [module(Module)]]).
716quote_atomic(Plain, Plain, _) :-
717 number(Plain),
718 !.
719quote_atomic(Plain, String, Options) :-
720 Options.get(quoted) == true,
721 !,
722 ( Options.get(embrace) == never
723 -> format(string(String), '~q', [Plain])
724 ; format(string(String), '~W', [Plain, Options])
725 ).
726quote_atomic(Var, String, Options) :-
727 var(Var),
728 !,
729 format(string(String), '~W', [Var, Options]).
730quote_atomic(Plain, Plain, _).
731
732quote_op(Op, S, _Options) :-
733 is_solo(Op),
734 !,
735 S = Op.
736quote_op(Op, S, Options) :-
737 quote_atomic(Op, S, Options.put(embrace,never)).
738
739is_solo(Var) :-
740 var(Var), !, fail.
741is_solo(',').
742is_solo(';').
743is_solo('!').
750primitive(Term, Type) :- var(Term), !, Type = 'pl-avar'.
751primitive(Term, Type) :- atom(Term), !, Type = 'pl-atom'.
752primitive(Term, Type) :- string(Term), !, Type = 'pl-string'.
753primitive(Term, Type) :- integer(Term), !, Type = 'pl-int'.
754primitive(Term, Type) :- rational(Term), !, Type = 'pl-rational'.
755primitive(Term, Type) :- float(Term), !, Type = 'pl-float'.
762primitive_class('pl-atom', Atom, String, Class) :-
763 \+ atom_string(Atom, String),
764 !,
765 Class = 'pl-quoted-atom'.
766primitive_class(Class, _, _, Class).
772finalize_term(Term, Options) -->
773 ( { true == Options.get(full_stop) }
774 -> space(Term, '.', o, o, Options, Options),
775 ( { true == Options.get(nl) }
776 -> emit(['.', br([])], Options)
777 ; emit('. ', Options)
778 )
779 ; ( { true == Options.get(nl) }
780 -> emit(br([]), Options)
781 ; []
782 )
783 ).
784
785
786
Represent Prolog terms as HTML
This file is primarily designed to support running Prolog applications over the web. It provides a replacement for write_term/2 which renders terms as structured HTML. */