############################################################################ # # Name: srchutil.icn # # Title: search utilities for bibleref # # Author: Richard L. Goerwitz # # Version: 1.14 # ############################################################################ # # Contains: # # display_search(), which prompts the user, if necessary, for # search strings and operators, performs the search, and # then puts the results into the global list of lists, # # compose_search(), which compiles a little automaton which, when # run via do_search(), returns a list of hits (i.e. retrieve- # format bitmaps), # # do_search(), on which see above, # # various other utilities (e.g. compose_spaced_search()) # ############################################################################ # # Links: ./complete.icn ./retrieve.icn, probably others # # See also: bibleref.icn # ############################################################################ # for debugging # link ximage procedure search_database() # # Search database for word or patterns matching whole words. # local search_machine, result, string_memb, tmp, excl search_machine := compose_search() | { err_message("No search performed. Aborting.") fail } # for debugging purposes # write(&errout, ximage(search_machine)) if *search_machine > 4 then message("Executing complex search...") else message("Executing search...") *(result := do_search(search_machine)[1]) > 0 | { err_message("No hits.") fail } # # Nasty kludge to see what search strings were incorporated into the # search_machine. # string_memb := "???" tmp := search_machine[2] excl := (\search_machine[4], "! ") | "" repeat { if type(tmp) == "string" then string_memb := excl || tmp & break else if type(tmp) == "list" then { excl := (\tmp[4], "! ") | "" tmp := tmp[2] } else break } if *search_machine > 4 then string_memb ||:= "..." if type(result) == "set" then result := sort(result) put(lists, lst(result, 0, &null, string_memb)) message("Done.") return lists[-1] end procedure compose_search() # # Put together a little search machine out of patterns specified by # the user. Don't execute, though. Just return a list containing # the user's directions to the calling procedure, and let it handle # execution (via do_search()). # local pattern, status, sense_of_search, rsp, result, u, r static blanks initial blanks := ' \t,' if pos(0) then rsp := trim(snarf_input("Enter word (q to abort): "), blanks) else return compose_spaced_search(blanks) rsp == (""|"q") & fail if rsp ? (="!", pattern := tab(many(blanks)), tab(0)) then sense_of_search := "inverted" else pattern := rsp result := [retrieve, pattern, kjv_filename, sense_of_search] repeat { status := map(snarf_input("f to finish, or a/o/n (q aborts): ")) status == (""|"q") & fail if upto(blanks, rsp) then # And together all words in the input string. return rsp ? compose_spaced_search(blanks) if status == "f" then return result else if status == ("a"|"n"|"o") then { if status ~== "o" then { u := GetUnit() | next r := GetRange() | next } return case status of { "a" : [r_and, result, compose_search(), kjv_filename, u, r] "o" : [r_or, result, compose_search(), kjv_filename, u, r] "n" : [r_and_not, result, compose_search(), kjv_filename, u, r] } | fail } else if status == (""|"q") then fail else err_message("F = finish, a = and, o = or, n = and-not.") } end procedure GetUnit() local resp if filestats[kjv_filename].IS.no = 3 then repeat { resp := map(snarf_input("Enter unit (b/c/v): ")) case resp of { "b" : return 1 "c" : return 2 "v" : return 3 "q"|"" : fail default : { err_message("Enter b (book), c (chapter), or v (verse).") next } } } else if filestats[kjv_filename].IS.no = 2 then repeat { resp := map(snarf_input("Enter unit (c/v): ")) case resp of { "c" : return 1 "v" : return 2 "q"|"" : fail default : { err_message("Enter c (chapter), or v (verse).") next } } } else abort("GetUnit", "not configured for IS.no not = 2 or 3", 69) end procedure GetRange() local resp repeat { resp := map(snarf_input("Enter range: ")) if resp := integer(resp) then return resp else { resp == ("q"|"") & fail err_message("Enter an integer (usually 1 or 0).") next } } end procedure do_search(l) # # Executes the little machine put together by compose_search(). # if *l = 0 then return l case type(l[1]) of { "list" : return do_search(l[1]) ||| do_search(l[2:0]) "procedure" : return [l[1]!do_search(l[2:0])] | [[]] default : return [l[1]] ||| do_search(l[2:0]) } end procedure compose_spaced_search(blanks) # # Try to turn searches with spaces in them (e.g. "sackcloth and ashes") # into separate searches for each constituent word anded together. # This routine is set up, though, to handle single words or patterns # as well (e.g. "sackcloth"). # local token, sense_of_search, search_list static wordchars initial wordchars := ~blanks # # Whoops, no string. This shouldn't happen, but just in case I screw # up somewhere in the code, and forget to strip out superfluous blanks # typed in by the user... # tab(upto(wordchars)) | { err_message("No search string. Aborting.") fail } if ="!" then { sense_of_search := 1 tab(upto(wordchars)) } token := tab(many(wordchars)) | { err_message("Unexpected end of input. Aborting") fail } # # Make sure tokens aren't just wildcard patterns! Also, warn the # user about searches containing really common words. # upto(&letters, token) | { err_message("Token "||token||" has no letters in it!") fail } # # If we've reached the end of the search string, return what we # have... # search_list := [retrieve, token, kjv_filename, sense_of_search] pos(0) & (return search_list) # # ...otherwise and this search string together with the next one, # return [r_and, search_list, compose_spaced_search(blanks), kjv_filename, filestats[kjv_filename].IS.no, 0] end