35
36:- module(pldoc_search,
37 [ search_form//1, 38 search_reply//2, 39 matching_object_table//2 40 ]). 41:- use_module(library(http/html_write)). 42:- use_module(library(http/html_head)). 43:- use_module(library(dcg/basics)). 44:- use_module(library(option)). 45:- use_module(library(pairs)). 46:- use_module(library(uri)). 47:- use_module(library(debug)). 48:- use_module(library(apply)). 49:- use_module(library(lists)). 50:- use_module(library(atom)). 51:- use_module(library(porter_stem)). 52
53:- use_module(doc_process). 54:- use_module(doc_html). 55:- use_module(doc_index). 56:- use_module(doc_util). 57:- use_module(doc_words). 58:- use_module(man_index). 59
60:- include(hooks). 61
69
70:- predicate_options(search_form//1, 1,
71 [ for(atom),
72 search_in(oneof([all,noapp,app,man])),
73 search_match(oneof([name,summary])),
74 search_options(boolean)
75 ]). 76:- predicate_options(search_reply//2, 2,
77 [ resultFormat(oneof([summary,long])),
78 search_in(oneof([all,noapp,app,man])),
79 search_match(oneof([name,summary])),
80 header(boolean),
81 private(boolean),
82 edit(boolean),
83 page(positive_integer),
84 per_page(positive_integer),
85 pass_to(pldoc_index:doc_links//2, 2)
86 ]). 87
94
95search_form(Options) -->
96 { ( option(for(Value), Options)
97 -> Extra = [value(Value)]
98 ; Extra = []
99 ),
100 option(search_in(In), Options, all),
101 option(search_match(Match), Options, summary)
102 },
103 html(form([ id('search-form'),
104 action(location_by_id(pldoc_search))
105 ],
106 [ div([ \search_field([ name(for),
107 id(for)
108 | Extra
109 ])
110 ]),
111 \search_options(In, Match, Options)
112 ])).
113
114search_options(In, Match, Options) -->
115 { option(search_options(false), Options) },
116 !,
117 hidden(in, In),
118 hidden(match, Match).
119search_options(In, Match, _Options) -->
120 html(div(class('search-options'),
121 [ span(class('search-in'),
122 [ \radio(in, all, 'All', In),
123 \radio(in, app, 'Application', In),
124 \radio(in, man, 'Manual', In)
125 ]),
126 span(class('search-match'),
127 [ \radio(match, name, 'Name', Match),
128 \radio(match, summary, 'Summary', Match)
129 ]),
130 span(class('search-help'),
131 [ a(href(location_by_id(pldoc_package)+'pldoc.html#sec:browser'),
132 'Help')
133 ])
134 ])).
135
136
142
143search_field(Options) -->
144 prolog:doc_search_field(Options),
145 !.
146search_field(Options) -->
147 html([ input(Options, []),
148 input([ id('submit-for'),
149 type(submit),
150 value('Search')
151 ])
152 ]).
153
154radio(Radio, Field, Label, In) -->
155 { Field == In
156 -> Extra = [checked]
157 ; Extra = []
158 },
159 html([ input([ type(radio),
160 name(Radio),
161 value(Field)
162 | Extra
163 ]),
164 Label
165 ]).
166
167hidden(Name, Value) -->
168 html(input([type(hidden), name(Name), value(Value)])).
169
198
199:- html_meta
200 search_header(+, html, +, ?, ?). 201
202search_reply(For, Options) -->
203 { var(For) ; For == '' },
204 !,
205 search_header('', 'Using PlDoc search', Options),
206 html([ ul( class('search-help'),
207 [ li([ 'If you pause typing, the search box will display ',
208 'an auto completion list. Selecting an object jumps ',
209 'immediately to the corresponding documentation.'
210 ]),
211 li([ 'Searching for ', i('Name/Arity'), ', ',
212 i('Name//Arity'), ', ', i('Name'), ' or ',
213 i('C-function()'), ' ensures that ',
214 'matching definitions appear first in the search ',
215 'results'
216 ]),
217 li([ 'Other searches search through the name and summary ',
218 'descriptions in the manual.'
219 ])
220 ])
221 ]).
222search_reply(For, Options) -->
223 { cached_search(For, PerCategory, Time, Options),
224 PerCategory \== [],
225 page_location(PerCategory, NPages, Offset, Limit, Options),
226 option(resultFormat(Format), Options, summary)
227 },
228 !,
229 search_header(For, [ 'Search results for ',
230 span(class(for), ['"', For, '"'])
231 ],
232 Options),
233 { DisplayOptions = [ for(For),
234 cputime(Time),
235 page_count(NPages)
236 | Options
237 ]
238 },
239 indexed_matches(Format, PerCategory, Offset, Limit, DisplayOptions),
240 search_pagination(DisplayOptions).
241search_reply(For, Options) -->
242 search_header(For, 'No matches', Options),
243 html(div(class('search-no-matches'), 'No matches')).
244
245:- dynamic
246 cached_search_result/4. 247
248cached_search(For, Result, Time, Options) :-
249 option(search_in(In), Options, all),
250 option(search_match(Match), Options, summary),
251 cached_search_result(For, In, Match, Result),
252 !,
253 Time = cached.
254cached_search(For, Result, Time, Options) :-
255 option(search_in(In), Options, all),
256 option(search_match(Match), Options, summary),
257 statistics(cputime, T0),
258 search_doc(For, PerCategory0, Options),
259 order_matches(PerCategory0, Result),
260 statistics(cputime, T1),
261 Time is T1-T0,
262 assertz(cached_search_result(For, In, Match, Result)),
263 prune_search_cache.
264
265prune_search_cache :-
266 ( predicate_property(cached_search_result(_,_,_,_),
267 number_of_clauses(Count)),
268 Del is Count - 25,
269 Del > 0
270 -> forall(between(1,Del,_), retract(cached_search_result(_,_,_,_)))
271 ; true
272 ).
273
274page_location(PerCategory, NPages, Offset, Limit, Options) :-
275 option(page(Page), Options, 1),
276 option(per_page(Limit), Options, 25),
277 Offset is (Page-1)*Limit,
278 count_matches(PerCategory, Total),
279 NPages is (Total+Limit-1)//Limit.
280
(Options) -->
282 { option(page(Page), Options, 1),
283 option(page_count(NPages), Options, 1)
284 },
285 html(div(class(pagination),
286 [ \search_prev(Page, Options),
287 span(class(current), ['Page ', Page, ' of ', NPages]),
288 \search_next(NPages, Page, Options)
289 ])).
290
291search_prev(Page, _) -->
292 { Page =:= 1 },
293 !.
294search_prev(Page, Options) -->
295 { Prev is Page - 1,
296 page_link(Prev, Link, Options)
297 },
298 html(a(href(Link), '< Prev')).
299
300search_next(NPages, Page, _) -->
301 { Page =:= NPages, ! }, [].
302search_next(_NPages, Page, Options) -->
303 { Next is Page + 1,
304 page_link(Next, Link, Options)
305 },
306 html(a(href(Link), 'Next >')).
307
308page_link(Page, '?'+QueryString, Options) :-
309 option(for(For), Options),
310 option(search_in(In), Options, all),
311 option(search_match(Match), Options, summary),
312 option(resultFormat(Format), Options, summary),
313 uri_query_components(QueryString,
314 [ for(For),
315 in(In),
316 match(Match),
317 resultFormat(Format),
318 page(Page)
319 ]).
320
(_For, _Title, Options) -->
322 { option(header(false), Options) },
323 !,
324 html_requires(pldoc).
325search_header(For, Title, Options) -->
326 html_requires(pldoc),
327 doc_links('', [for(For)|Options]),
328 html(h1(class('search-results'), Title)).
329
334
335matching_object_table(Objects, Options) -->
336 { maplist(obj_cat_sec, Objects, Pairs),
337 group_hits(Pairs, Organized),
338 option(format(Format), Options, summary)
339 },
340 indexed_matches(Format, Organized, Options).
341
342obj_cat_sec(Object, Cat-(Section-Object)) :-
343 prolog:doc_object_summary(Object, Cat, Section, _Summary).
344
345indexed_matches(Format, PerCategory, Offset, Limit, Options) -->
346 { cat_offset(Offset, _,PerCategory, PerCategory1),
347 cat_limit(Limit, _, PerCategory1, PerCategory2)
348 },
349 ( { PerCategory2 == PerCategory }
350 -> indexed_matches(Format, PerCategory, Options)
351 ; category_counts(PerCategory,
352 [ showing('Total'),
353 link(category)
354 | Options
355 ]),
356 { delete(Options, cputime(_), Options1) },
357 category_counts(PerCategory2,
358 [ showing('Showing'),
359 class([showing])
360 | Options1
361 ]),
362 search_pagination(Options),
363 matches(Format, PerCategory2, Options)
364 ).
365
368
369cat_offset(0, 0, PerCat, PerCat) :-
370 !.
371cat_offset(N, R, [C-H|T], PerCat) :-
372 H = [_-[_|_]|_],
373 !,
374 cat_offset(N, R1, H, H1),
375 ( H1 == []
376 -> !, cat_offset(R1, R, T, PerCat)
377 ; PerCat = [C-H1|T]
378 ).
379cat_offset(N, R, [_C-L|T0], PerCat) :-
380 length(L, Len),
381 Left is N-Len,
382 Left > 0,
383 !,
384 cat_offset(Left, R, T0, PerCat).
385cat_offset(N, 0, [C-L0|T], [C-L|T]) :-
386 !,
387 length(Skip, N),
388 append(Skip, L, L0).
389cat_offset(N, N, Obj, Obj).
390
391cat_limit(0, 0, _PerCat, []) :-
392 !.
393cat_limit(N, R, [C-H|T], PerCat) :-
394 H = [_-[_|_]|_],
395 !,
396 cat_limit(N, R1, H, H1),
397 ( R1 == 0
398 -> PerCat = [C-H1]
399 ; PerCat = [C-H|T1],
400 cat_limit(R1, R, T, T1)
401 ).
402cat_limit(N, R, [C-L|T0], [C-L|T]) :-
403 length(L, Len),
404 More is N - Len,
405 More >= 0,
406 !,
407 cat_limit(More, R, T0, T).
408cat_limit(N, 0, [C-L0|_], [C-L]) :-
409 !,
410 length(L, N),
411 append(L, _, L0).
412cat_limit(N, N, [], []).
413
414
420
421order_matches(PerCat0, PerCat) :-
422 maplist(order_category, PerCat0, PerCat).
423
424order_category(Cat-PerSection0, Cat-PerSection) :-
425 maplist(order_section, PerSection0, PerSectionTagged),
426 sort(1, >=, PerSectionTagged, Ordered),
427 pairs_values(Ordered, PerSection).
428
429order_section(Section-Objects0, Q-(Section-Objects)) :-
430 sort(1, >=, Objects0, Objects),
431 maplist(arg(1), Objects, QList),
432 join_quality(QList, Q).
433
434join_quality([], 0).
435join_quality([Q], Q).
436join_quality([QH|QL], Q) :-
437 join_quality(QL, QT),
438 Q is 1-(1-QH)*(1-QT).
439
440
444
445indexed_matches(Format, PerCategory, Options) -->
446 category_counts(PerCategory, Options),
447 matches(Format, PerCategory, Options).
448
449category_counts(PerCategory, Options) -->
450 { count_matches(PerCategory, Matches),
451 option(class(Classes), Options, []),
452 ( PerCategory = [_]
453 -> merge_options([link(false)], Options, Options1)
454 ; Options1 = Options
455 )
456 },
457 html([ div(class(['search-counts'|Classes]),
458 [ \category_showing(Options1),
459 Matches,
460 \count_by_category(PerCategory, Options1),
461 \search_time(Options1)
462 ])
463 ]).
464
465count_by_category([Cat-_PerFile], Options) -->
466 !,
467 html(' matches from '),
468 category_link(Cat, Options).
469count_by_category(PerCategory, Options) -->
470 html(' matches; '),
471 count_by_category_list(PerCategory, Options).
472
473count_by_category_list([], _) -->
474 [].
475count_by_category_list([Cat-PerFile|T], Options) -->
476 { count_category(PerFile, Count) },
477 html([ \category_link(Cat, Options), ': ', Count ]),
478 ( {T == []}
479 -> []
480 ; html(', '),
481 count_by_category_list(T, Options)
482 ).
483
484count_matches([], 0).
485count_matches([_-Cat|T], Count) :-
486 count_matches(T, Count0),
487 count_category(Cat, N),
488 Count is Count0 + N.
489
490count_category([], 0).
491count_category([_-Objs|T], Count) :-
492 count_category(T, Count0),
493 length(Objs, N),
494 Count is Count0 + N.
495
496category_showing(Options) -->
497 { option(showing(Showing), Options) },
498 html(span(class('search-showing'), [Showing, :])).
499category_showing(_) -->
500 [].
501
502search_time(Options) -->
503 { option(cputime(Time), Options) },
504 !,
505 ( { number(Time) }
506 -> html(span(class('search-time'), '(~2f sec.)'-[Time]))
507 ; html(span(class('search-time'), '(~w)'-[Time]))
508 ).
509search_time(_) -->
510 [].
511
517
518matches(long, PerCategory, Options) -->
519 long_matches_by_type(PerCategory, Options).
520matches(summary, PerCategory, Options) -->
521 html(table(class(summary),
522 \short_matches_by_type(PerCategory, 1, Options))).
523
524
525long_matches_by_type([], _) -->
526 [].
527long_matches_by_type([Category-PerFile|T], Options) -->
528 category_header(Category, Options),
529 long_matches(PerFile, Options),
530 long_matches_by_type(T, Options).
531
532
533long_matches([], _) -->
534 [].
535long_matches([File-Objs|T], Options) -->
536 file_header(File, Options),
537 objects(Objs, Options),
538 long_matches(T, Options).
539
(Category, _Options) -->
541 html(h1(class(category), \category_title(Category))).
542
543short_matches_by_type([], _, _) -->
544 [].
545short_matches_by_type([Category-PerFile|T], Nth, Options) -->
546 category_index_header(Category, Nth, Options),
547 short_matches(PerFile, Options),
548 { succ(Nth, Nth1) },
549 short_matches_by_type(T, Nth1, Options).
550
551short_matches([], _) -->
552 [].
553short_matches([File-Objs|T], Options) -->
554 file_index_header(File, Options),
555 object_summaries(Objs, File, Options),
556 short_matches(T, Options).
557
558
(Category, Nth, _Options) -->
560 ( { Nth > 1 }
561 -> category_sep('category-top-sep')
562 ; []
563 ),
564 html(tr(th([class(category), colspan(3)],
565 a(name(Category), \category_title(Category))))),
566 category_sep('category-bottom-sep').
567
568category_sep(Which) -->
569 html(tr(th([class(Which), colspan(3)],
570 &(nbsp)))).
571
572category_link(Category, Options) -->
573 { option(link(false), Options) },
574 !,
575 category_title(Category).
576category_link(Category, Options) -->
577 { option(link(category), Options),
578 category_link(Category, HREF, Options)
579 },
580 !,
581 html(a(href(HREF), \category_title(Category))).
582category_link(Category, _Options) -->
583 { atom_concat(#, Category, HREF) },
584 html(a(href(HREF), \category_title(Category))).
585
586category_link(Category, '?'+QueryString, Options) :-
587 ( category_abbreviation(Category, Abbrev)
588 -> true
589 ; Abbrev = Category
590 ),
591 option(for(For), Options),
592 option(search_match(Match), Options, summary),
593 option(resultFormat(Format), Options, summary),
594 uri_query_components(QueryString,
595 [ for(For),
596 in(Abbrev),
597 match(Match),
598 resultFormat(Format)
599 ]).
600
601category_title(Category) -->
602 { prolog:doc_category(Category, _Order, Title)
603 -> true
604 ; Title = Category
605 },
606 html(Title).
607
612
613search_doc(Search, PerType, Options) :-
614 findall(Tuples, matching_object(Search, Tuples, Options), Tuples0),
615 sort(Tuples0, Tuples),
616 group_hits(Tuples, PerType0),
617 prune_library(PerType0, PerType).
618
619group_hits(Tuples, PerType) :-
620 keysort(Tuples, Tuples1),
621 group_pairs_by_key(Tuples1, PerCat0),
622 key_sort_order(PerCat0, PerCat1),
623 keysort(PerCat1, PerCat2),
624 pairs_values(PerCat2, PerCat),
625 group_by_file(PerCat, PerType).
626
627key_sort_order([], []).
628key_sort_order([Cat-ByCat|T0], [Order-(Cat-ByCat)|T]) :-
629 ( prolog:doc_category(Cat, Order, _Title)
630 -> true
631 ; Order = 99
632 ),
633 key_sort_order(T0, T).
634
635
636group_by_file([], []).
637group_by_file([Type-Tuples0|T0], [Type-ByFile|T]) :-
638 keysort(Tuples0, Tuples),
639 group_pairs_by_key(Tuples, ByFile),
640 group_by_file(T0, T).
641
642
646
647prune_library(PerCat0, PerCat) :-
648 selectchk(library-InLib0, PerCat0, library-InLib, PerCat1),
649 !,
650 ( cat_objects(manual, PerCat0, Manual),
651 cat_objects(packages, PerCat0, Packages),
652 append(Manual, Packages, Objects),
653 sort(Objects, OSet0),
654 maplist(arg(2), OSet0, OSet), 655 convlist(prune_section(OSet), InLib0, InLib),
656 InLib \== []
657 -> PerCat = PerCat1
658 ; selectchk(library-_, PerCat0, PerCat)
659 ).
660prune_library(PerCat, PerCat).
661
662cat_objects(Cat, PerCat, Objects) :-
663 memberchk(Cat-Sections, PerCat),
664 !,
665 pairs_values(Sections, NestedObjects),
666 append(NestedObjects, Objects).
667cat_objects(_, _, []).
668
669prune_section(Prune, Section-Objects0, Section-Objects) :-
670 exclude(in_set(Prune), Objects0, Objects),
671 Objects \== []. 672
673in_set(Prune, q(_Q,Obj)) :-
674 memberchk(Obj, Prune),
675 !.
676in_set(Prune, q(_Q,_Module:Obj)) :-
677 memberchk(Obj, Prune).
678
679
692
693matching_object(Search, Type-(Section-q(1,Obj)), Options) :-
694 atom_concat(Function, '()', Search),
695 Obj = c(Function),
696 option(search_in(In), Options, all),
697 prolog:doc_object_summary(Obj, Type, Section, _),
698 matching_category(In, Type).
699matching_object(Search, Type-(Section-q(1,Obj)), Options) :-
700 ( atom_pi(Search, Obj0)
701 -> ground(Obj0)
702 ; catch(atom_to_term(Search, Obj0, _), _, fail),
703 nonvar(Obj0)
704 ),
705 opt_qualify(Obj0, Obj),
706 option(search_in(In), Options, all),
707 prolog:doc_object_summary(Obj, Type, Section, _),
708 matching_category(In, Type).
709matching_object(Search, Match, Options) :-
710 atom_codes(Search, Codes),
711 phrase(search_spec(For0), Codes),
712 ( For0 = not(_)
713 -> throw(error(bad_search(only_not), _))
714 ; optimise_search(For0, For),
715 exec_search(For, Match, Options)
716 ).
717
718opt_qualify(Obj0, Obj) :-
719 Obj0 = _:_,
720 !,
721 Obj = Obj0.
722opt_qualify(Obj, Obj).
723opt_qualify(Obj, _:Obj).
724
725
731
732optimise_search(and(not(A0), B0), and(B, not(A))) :-
733 !,
734 optimise_search(A0, A),
735 optimise_search(B0, B).
736optimise_search(A, A).
737
738
749
750exec_search(Spec, Match, Options) :-
751 exec_search(Spec, Match0, Q, Options),
752 add_quality(Match0, Q, Match).
753
754add_quality(Type-(Section-Obj), Q, Type-(Section-q(Q,Obj))).
755
756exec_search(and(A, B), Match, Q, Options) :-
757 !,
758 exec_search(A, Match, Q1, Options),
759 exec_search(B, Match, Q2, Options),
760 Q is 1-((1-Q1)*(1-Q2)).
761exec_search(Search, Type-(Section-Obj), Q, Options) :-
762 option(search_in(In), Options, all),
763 option(search_match(Match), Options, summary),
764 option(private(Public), Options, true),
765 prolog:doc_object_summary(Obj, Type, Section, Summary),
766 matching_category(In, Type),
767 match_private(Public, Obj),
768 ( Search = not(For)
769 -> State = s(0),
770 \+ ( match_object(For, Obj, Summary, Match, Q),
771 nb_setarg(1, State, Q)
772 ),
773 arg(1, State, Q)
774 ; match_object(Search, Obj, Summary, Match, Q)
775 ).
776
777
778matching_category(all, _).
779matching_category(noapp, Category) :-
780 !,
781 Category \== application.
782matching_category(Category, Category).
783matching_category(Abbrev, Category) :-
784 category_abbreviation(Category, Abbrev).
785
786category_abbreviation(application, app).
787category_abbreviation(manual, man).
788category_abbreviation(library, lib).
789category_abbreviation(packages, pack).
790category_abbreviation(wiki, wiki).
791
792match_private(true, _).
793match_private(false, Object) :-
794 ( Object = (Module:PI)
795 -> current_module(Module),
796 pi_head(PI, Head),
797 ( predicate_property(Module:Head, exported)
798 -> true
799 ; predicate_property(Module:Head, multifile)
800 -> true
801 ; predicate_property(Module:Head, public)
802 )
803 ; true
804 ).
805
806pi_head(Name/Arity, Head) :-
807 functor(Head, Name, Arity).
808pi_head(Name//Arity, Head) :-
809 Arity1 is Arity+ 2,
810 functor(Head, Name, Arity1).
811
815
816search_spec(Spec) -->
817 blanks,
818 prim_search_spec(A),
819 blanks,
820 ( eos
821 -> { Spec = A }
822 ; search_spec(B)
823 -> { Spec = and(A,B) }
824 ).
825
826prim_search_spec(quoted(Quoted)) -->
827 "\"", string(Codes), "\"",
828 !,
829 { tokenize_atom(Codes, Quoted)
830 }.
831prim_search_spec(Spec) -->
832 nonblanks(Codes),
833 { Codes = [0'-,C0|Rest],
834 code_type(C0, csym)
835 -> atom_codes(Word, [C0|Rest]),
836 Spec = not(Word)
837 ; Codes \== [],
838 atom_codes(Spec, Codes)
839 }.
840
841
851
852prolog:doc_object_summary(Obj, Category, File, Summary) :-
853 once(prolog_object(Obj)),
854 current_prolog_flag(home, SWI),
855 doc_comment(Obj0, File:_Line, Summary, _Comment),
856 ( is_list(Obj0)
857 -> member(Obj, Obj0)
858 ; Obj = Obj0
859 ),
860 Obj \= _:module(_Title), 861 ( sub_atom(File, 0, _, _, SWI)
862 -> Category = library
863 ; Category = application
864 ).
865
866prolog_object(Var) :- var(Var), !.
867prolog_object(_/_).
868prolog_object(_//_).
869prolog_object(_:_/_).
870prolog_object(_:_//_).
871prolog_object(module(_)).
872
873
881
882prolog:doc_category(application, 20, 'Application').
883prolog:doc_category(library, 80, 'System Libraries').
884
885
886 889
900
901match_object(For, Object, Summary, How, Quality) :-
902 ( doc_object_identifier(Object, Identitier),
903 identifier_match_quality(For, Identitier, Quality)
904 -> debug(search(rank), 'Rank "~w" in identifier "~w": ~q',
905 [For, Identitier, Quality])
906 ; How == summary,
907 summary_match_quality(For, Summary, Quality),
908 debug(search(rank), 'Rank "~w" in summary "~w": ~q',
909 [For, Summary, Quality])
910 ).
911
912summary_match_quality(For, Summary, Q) :-
913 tokenize_atom(Summary, Tokens0),
914 exclude(is_punctuation, Tokens0, Tokens),
915 Tokens \== [],
916 token_match_quality(summary, For, Tokens, Q0),
917 Q is Q0/2.
918
919is_punctuation(Token) :-
920 atom_length(Token, 1),
921 char_type(Token, punct).
922
923
924identifier_match_quality(Identifier, Identifier, 1) :-
925 !.
926identifier_match_quality(For, Identifier, Q) :-
927 dwim_match(For, Identifier, _),
928 !,
929 Q = 0.8.
930identifier_match_quality(For, Identifier, Q) :-
931 identifier_parts(Identifier, Parts),
932 Parts \== [],
933 token_match_quality(identifier, For, Parts, Q).
934
935token_match_quality(_How, quoted(Tokens), Parts, Q) :-
936 !,
937 append(Tokens, _, All),
938 append(_, All, Parts),
939 Q = 1.
940token_match_quality(How, For, Parts, Q) :-
941 length(Parts, Len),
942 ( memberchk(For, Parts)
943 -> Q0 = 1
944 ; porter_stem(For, Stem),
945 member(Part, Parts),
946 porter_stem(Part, Stem)
947 -> Q0 = 0.9
948 ; How == summary,
949 member(Part, Parts),
950 sub_atom_icasechk(Part, _, For),
951 identifier_parts(Part, SubParts),
952 token_match_quality(identifier, For, SubParts, Q00)
953 -> Q0 is Q00/2
954 ; How == summary,
955 member(Part, Parts),
956 sub_atom_icasechk(Part, 0, For),
957 is_numbered_var(Part, For)
958 -> Q0 is 0.9
959 ; doc_related_word(For, Word, Distance),
960 memberchk(Word, Parts)
961 -> Q0 = Distance
962 ),
963 Q is Q0/Len.
964
965is_numbered_var(VarName, Search) :-
966 atom_length(Search, Len),
967 sub_string(VarName, Len, _, 0, NS),
968 number_string(_, NS),
969 sub_atom(VarName, 0, 1, _, First),
970 char_type(First, prolog_var_start)