# This Icon program is the second pass of the indexing process for TeX for the # Impatient. It must be preceded by index1 and by a sort of the intermediate # file. # This program was written by Paul Abrahams and is public domain. record topic_entry(term, type, groupchar, pages, level) record pgrec(number, flags) record term_list_record(term_list, start) procedure main(a) local gen, pages, term, topic local groupchar write(&errout, "Second indexing pass has started.") # Each pass through this loop produces the entry for a single topic # or subtopic, including both the text of the topic and its pages. every topic := get_topic_info() do { # If we're starting a new group (initial character), produce the macro # for it. if topic.level = 1 then { # only primary topics affect the group if not(\groupchar == topic.groupchar) then write("\\indexgroup ", groupchar := topic.groupchar) } else topic.type := "N" # subtopics are always printed normally # Write the index term writes("\\indexentry {", topic.level - 1, "}{", edit_term(topic.term), "}{", topic.type, "}{") # Write the list of pages write(edit_pages(topic.pages, topic.term), "}") } end procedure get_topic_info() local page, type, full_term, flags # info in an index item local term # the index term to be printed (part of full_term) local item_text # holds an input item to be parsed local topic # the topic we're now working on local term_list_info # returned term_list_record from get_term_list local term_list # list of index terms extracted from the input item local first # position of first thing in term_list to print local t # loop variable local term1 # first term in full_term, usually the only one term_list := [] # At the start of each pass through this loop, `topic' contains the text of # the index topic most recently seen together with the pages seen so far for # that index topic. every !&input ? (tab(find("@@@")\1), move(3), item_text := tab(0)) do { # Dissect the original index item, discarding the key item_text ? (full_term := tab(find("::")), move(2), type := tab(find("::")), move(2), page := tab(many('-0123456789*')), flags := tab(0)) # a page of * indicates a see-also term_list_info := get_term_list(full_term, term_list) term_list := term_list_info.term_list if type == (\topic).type then # no change of type first := term_list_info.start else first := 1 # change of type, so all terms are different term1 := term_list[\first] # If we've finished the current topic, produce it and start the next one if \first then { suspend \topic topic := topic_entry(term1, type, find_groupchar(term1), [], first) every t := !term_list[first + 1:0] do { suspend topic topic.term := t; topic.type := "N"; topic.level +:= 1 } } put(topic.pages, if page == "*" then flags # flags here is the see-also else pgrec(page, cset(flags))) } suspend topic fail end procedure edit_term(term) # This procedure edits `term' into a proper argument for \indexterm if term == " " then term := "\\visiblespace" else if *term = 1 then term := "\\char `\\" || term else if match("^^", term) then term := "\\twocarets " || term[3:0] else if term == "$$" then term := "\\$\\$" # $$ is the only other 2-character sequence that has to be protected. return term end procedure edit_pages(l, term) # edit_pages removes duplicate pages from the page list, produces the # macro call for a principal entry, and coalesces page ranges. # It also converts negative numbers to roman numerals. # Each element of l is a pgrec, except that the last (and possibly only) # element may be a see-also string starting with *. # The result is a list of strings local pg, n, m, pf, see_also, pagelist local l1, k # If the last element of l is a string, remove it and set it aside. # It's a see-also. if type(l[-1]) == "string" then {see_also := l[-1]; l := l[1:-1]} # First pass through the page list, coalescing duplicates and combining # their flags. l1 := [] while *l > 0 do { pg := pop(l); n := pg.number; pf := pg.flags # Loop over pages 2..k within a group while n = l[1].number do pf ++:= pop(l).flags if *(pf ** 'BE') = 2 then # delete B and E if they both occur pf --:= 'BE' put(l1, pgrec(n, pf)) } # Now l1 has no duplicates and no trivial page ranges. Replace each # page range by a single entry, inverting the order for negative page # numbers since those indicate roman numerals. # When we're done, l1 has a list of strings rather than a list of pgrecs. l := l1; l1 := [] while *l > 0 do { pg := pop(l); n := pg.number; pf := pg.flags if *(pf ** 'E') > 0 then { every write(errfiles(), "Unmatched end of page range, page ", integer(n), ", index term `", term, "'!") pf --:= 'E' } if *(pf ** 'B') > 0 then { # beginning a page range every k := 1 to *l do { pf ++:= l[k].flags if *(pf ** 'E') > 0 then break } if *(pf ** 'E') = 0 then { every write(errfiles(), "Unmatched beginning of page range, page ", integer(n), ", index term `", term, "'!") pf := pg.flags } else { m := l[k].number if m < 0 then { # roman numerals m := "\\r" || -m n := "\\r" || -n } n := string(n || "--" || m) l := l[k+ 1:0] } } else if n < 0 then n := "\\r" || -n if *(pf ** 'P') > 0 then n := "\\pp{" || n || "}" put(l1, n) } # Now l1 is a list of page numbers and page ranges. # If it's empty and we have a see-also, make it a \see and return it. if *l1 = 0 then return "\\see{" || \see_also || "}" | "" # Turn l1 into a string and insert the comma commands \ic and \c # \ic goes at the beginning, \c between the remaining elements. pagelist := "\\ic " || pop(l1) | "" every pagelist ||:= "\\c " || !l1 # Now attach the see-also to pagelist if we had one and return the result return pagelist || ("\\seealso{" || \see_also || "}" | "") end procedure find_groupchar(t) # This procedure finds the character that heads the group containing # the index term `t'. We want all special characters in a single group # and all digits in a single group. # A term that begins with `\