1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
  
     | 
    
      # 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 `\<c' or `\c' or `.c' is grouped as `c'.
	local c
	static printable, specials
	initial {
		printable := &ascii[33:-1]
		specials := string(printable -- (&ucase ++ &lcase ++ &digits))
		}
	return map(
			if t ? (tab(many('\\.<')), c := move(1)) then c
			else if any(specials, t[1]) then "+"
			else if any(&digits, t[1]) then "0"
			else t[1] | "",
		&lcase, &ucase)
end
procedure get_term_list(ft, tl)
# `ft' is the full term just read in, `tl' is the current term list
# return a record containing all the terms and the position of the first
# one that's different from the previous full term
	local tl1, pos, pos1, first, k
	
	tl1 := []
	pos := 1
	every pos1 := (find("//", ft) | 0) do
		{put(tl1, ft[pos:pos1]); pos := pos1 + 2}
	first := &null	
	every k := 1 to *tl1 do
		if not(tl1[k] == tl[k]) then
			{first := k; break}
	return term_list_record(tl1, first)
end	
procedure errfiles()
	static errf
	initial
		errf := open("index.err", "w")
	suspend &errout | errf
end
 
     |