home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / isp31b3.zip / ispell / munchlist.sh < prev    next >
Text File  |  1995-07-10  |  27KB  |  761 lines

  1. : Use /usr/bin/bash.exe
  2. #
  3. # $Id: munchlist.X,v 1.53 1995/01/08 23:23:36 geoff Exp $
  4. #
  5. # Copyright 1987, 1988, 1989, 1992, 1993, Geoff Kuenning, Granada Hills, CA
  6. # All rights reserved.
  7. #
  8. # Redistribution and use in source and binary forms, with or without
  9. # modification, are permitted provided that the following conditions
  10. # are met:
  11. #
  12. # 1. Redistributions of source code must retain the above copyright
  13. #    notice, this list of conditions and the following disclaimer.
  14. # 2. Redistributions in binary form must reproduce the above copyright
  15. #    notice, this list of conditions and the following disclaimer in the
  16. #    documentation and/or other materials provided with the distribution.
  17. # 3. All modifications to the source code must be clearly marked as
  18. #    such.  Binary redistributions based on modified source code
  19. #    must be clearly marked as modified versions in the documentation
  20. #    and/or other materials provided with the distribution.
  21. # 4. All advertising materials mentioning features or use of this software
  22. #    must display the following acknowledgment:
  23. #      This product includes software developed by Geoff Kuenning and
  24. #      other unpaid contributors.
  25. # 5. The name of Geoff Kuenning may not be used to endorse or promote
  26. #    products derived from this software without specific prior
  27. #    written permission.
  28. #
  29. # THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS IS'' AND
  30. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  31. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  32. # ARE DISCLAIMED.  IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS BE LIABLE
  33. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  34. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  35. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  36. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  37. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  38. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  39. # SUCH DAMAGE.
  40. #
  41. #    Given a list of words for ispell, generate a reduced list
  42. #    in which all possible affixes have been collapsed.  The reduced
  43. #    list will match the same list as the original.
  44. #
  45. #    Usage:
  46. #
  47. #    munchlist [-l lang] [-c lang] [-s hashfile] [-D] [-w chars] [-v] \
  48. #      [file] ...
  49. #
  50. #    Options:
  51. #
  52. #    -l lang    Specifies the language table to be used.  The default
  53. #        is "$LIBDIR/english.aff".
  54. #    -c lang    Specifies "conversion" language table.  If this option is
  55. #        given, the input file(s) will be assumed to be described by
  56. #        this table, rather than the table given in the -l option.
  57. #        This may be used to convert between incompatible language
  58. #        tables.  (When in doubt, use this option -- it doesn't
  59. #        hurt, and it may save you from creating a dictionary that has
  60. #        illegal words in it).  The default is no conversion.
  61. #    -T suff Specifies that the source word lists are in the format
  62. #        of a "suff"-suffixed file, rather than in the
  63. #        canonical form.  For example, "-T tex" specifies that
  64. #        string characters in the word lists are in TeX format.
  65. #        The string character conversions are taken from the language
  66. #        table specified by the "-l" switch.
  67. #    -s    Remove any words that are already covered by the
  68. #        dictionary in 'hashfile'.  The words will be removed
  69. #        only if all affixes are covered.  This option should not be
  70. #        specified when the main dictionary is being munched.
  71. #        'Hashfile' must have been created with the language
  72. #        table given in the -l option, but this is not checked.
  73. #    -D    Leave temporary files for debugging purposes
  74. #    -w    Passed on to ispell (specify chars that are part of a word)
  75. #        Unfortunately, special characters must be quoted twice
  76. #        rather than once when invoking this script.  Also, since
  77. #        buildhash doesn't accept this option, the final ispell -l
  78. #        step ignores it, making it somewhat less than useful.
  79. #    -v    Report progress to stderr.
  80. #
  81. #    The given input files are merged, then processed by 'ispell -c'
  82. #    to generate possible affix lists;  these are then combined
  83. #    and reduced.  The final result is written to standard output.
  84. #
  85. #    For portability to older systems, I have avoided getopt.
  86. #
  87. #        Geoff Kuenning
  88. #        2/28/87
  89. #
  90. # $Log: munchlist.X,v $
  91. # Revision 1.53  1995/01/08  23:23:36  geoff
  92. # Support variable hashfile suffixes for DOS purposes.
  93. #
  94. # Revision 1.52  1994/12/27  23:08:46  geoff
  95. # Dynamically determine how to pass backslashes to 'tr' so that it'll
  96. # work on any machine.  Define LC_CTYPE to work around yet more
  97. # internationalized sort programs.  Work around a bug in GNU uniq that
  98. # uses the wrong separator between counts and duplicated lines.
  99. #
  100. # Revision 1.51  1994/11/21  07:02:54  geoff
  101. # Correctly quote the arguments to 'tr' when detecting systems with
  102. # unsigned sorts.  Be sure to provide a zero exit status on all systems,
  103. # even if MUNCHDEBUG is not set.
  104. #
  105. # Revision 1.50  1994/10/25  05:46:05  geoff
  106. # Export values for LANG and LOCALE in an attempt to override some
  107. # stupidly-internationalized sort programs.
  108. #
  109. # Revision 1.49  1994/10/04  03:51:30  geoff
  110. # Add the MUNCHMAIL feature.  If the MUNCHMAIL environment variable is
  111. # set to an email address, debugging information about the munchlist run
  112. # will automatically be collected and mailed to that address.
  113. #
  114. # Revision 1.48  1994/05/17  06:32:06  geoff
  115. # Don't look for affix tables in LIBDIR if the name contains a slash
  116. #
  117. # Revision 1.47  1994/04/27  02:50:48  geoff
  118. # Fix some cosmetic flaws in the verbose-mode messages.
  119. #
  120. # Revision 1.46  1994/01/25  07:11:59  geoff
  121. # Get rid of all old RCS log lines in preparation for the 3.1 release.
  122. #
  123. #
  124. if [ "X$MUNCHMAIL" != X ]
  125. then
  126.     exec 2> /tmp/munchlist.mail
  127.     echo "munchlist $*" 1>&2
  128.     set -vx
  129. fi
  130. LIBDIR=/usr/ispell
  131. TDIR=${TMPDIR-/usr/tmp}
  132. TMP=${TDIR}/munch$$
  133. SORTTMP="-T ${TDIR}"            # !!SORTTMP!!
  134. if [ -r ./icombine ]
  135. then
  136.     COMBINE=./icombine
  137. else
  138.     COMBINE=icombine
  139. fi
  140. if [ -r ./ijoin ]
  141. then
  142.     JOIN=./ijoin
  143. else
  144.     JOIN=ijoin
  145. fi
  146.  
  147. #
  148. # The following is necessary so that some internationalized versions of
  149. # sort(1) don't confuse things by sorting into a nonstandard order.
  150. #
  151. LANG=C
  152. LOCALE=C
  153. LC_CTYPE=C
  154. export LANG LOCALE LC_CTYPE
  155.  
  156. debug=no
  157. dictopt=
  158. langtabs=${LIBDIR}/english.aff
  159. convtabs=
  160. strip=no
  161. icflags=
  162. verbose=false
  163. # The following value of "wchars" is necessary to prevent ispell from
  164. # receiving a null argument if -w is not specified.  As long as "A" is
  165. # a member of the existing character set, ispell will ignore the argument.
  166. wchars=-wA
  167. while [ $# != 0 ]
  168. do
  169.     case "$1" in
  170.     -l)
  171.         case "$2" in
  172.         */*)
  173.             langtabs=$2
  174.             ;;
  175.         *)
  176.             if [ -r "$2" ]
  177.             then
  178.             langtabs="$2"
  179.             else
  180.             langtabs="${LIBDIR}/$2"
  181.             fi
  182.             ;;
  183.         esac
  184.         if [ ! -r "$langtabs" ]
  185.         then
  186.         echo "Can't open language table '$2'" 1>&2
  187.         exit 1
  188.         fi
  189.         shift
  190.         ;;
  191.     -c)
  192.         if [ -r "$2" ]
  193.         then
  194.         convtabs="$2"
  195.         elif [ -r "${LIBDIR}/$2" ]
  196.         then
  197.         convtabs="${LIBDIR}/$2"
  198.         else
  199.         echo "Can't open conversion language table '$2'" 1>&2
  200.         exit 1
  201.         fi
  202.         shift
  203.         ;;
  204.     -s)
  205.         dictopt="-d $2"
  206.         strip=yes
  207.         shift
  208.         ;;
  209.     -D)
  210.         debug=yes
  211.         ;;
  212.     -T)
  213.         icflags="-T $2"
  214.         shift
  215.         ;;
  216.     -v)
  217.         verbose=true
  218.         ;;
  219.     -w)
  220.         wchars="-w$2"
  221.         shift
  222.         ;;
  223.     --)
  224.         shift
  225.         break
  226.         ;;
  227.     -)
  228.         break
  229.         ;;
  230.     -*)
  231.         echo 'Usage: munchlist [-l lang] [-c lang] [-T suff] [-s hashfile] [-D] [-w chars] [-v] [file] ...' \
  232.           1>&2
  233.         exit 2
  234.         ;;
  235.     *)
  236.         break
  237.         ;;
  238.     esac
  239.     shift
  240. done
  241. if [ "X$MUNCHMAIL" != X ]
  242. then
  243.     verbose=true
  244.     debug=yes
  245. fi
  246. trap "rm -f ${TMP}*; exit 1" 1 2 13 15
  247. #
  248. # Names of temporary files.  This is just to make the code a little easier
  249. # to read.
  250. #
  251. EXPANDEDINPUT=${TMP}a
  252. STRIPPEDINPUT=${TMP}b
  253. CRUNCHEDINPUT=${TMP}c
  254. PRODUCTLIST=${TMP}d
  255. EXPANDEDPAIRS=${TMP}e
  256. LEGALFLAGLIST=${TMP}f
  257. JOINEDPAIRS=${TMP}g
  258. MINIMALAFFIXES=${TMP}h
  259. CROSSROOTS=${TMP}i
  260. CROSSEXPANDED=${TMP}j
  261. CROSSPAIRS=${TMP}k
  262. CROSSILLEGAL=${TMP}l
  263. ILLEGALCOMBOS=${TMP}m
  264. FAKEDICT=${TMP}n
  265. # Ispell insists that hash files have a ".hash" suffix
  266. FAKEHASH=${TMP}o.hash
  267. AWKSCRIPT=${TMP}p
  268. if [ "$debug" = yes ]
  269. then
  270.     touch $EXPANDEDINPUT $STRIPPEDINPUT $CRUNCHEDINPUT $PRODUCTLIST \
  271.       $EXPANDEDPAIRS $LEGALFLAGLIST $JOINEDPAIRS $MINIMALAFFIXES \
  272.       $CROSSROOTS $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL $ILLEGALCOMBOS \
  273.       $FAKEDICT $FAKEHASH $AWKSCRIPT
  274.     rm -f ${TDIR}/EXPANDEDINPUT ${TDIR}/STRIPPEDINPUT ${TDIR}/CRUNCHEDINPUT \
  275.       ${TDIR}/PRODUCTLIST ${TDIR}/EXPANDEDPAIRS ${TDIR}/LEGALFLAGLIST \
  276.       ${TDIR}/JOINEDPAIRS ${TDIR}/MINIMALAFFIXES ${TDIR}/CROSSROOTS \
  277.       ${TDIR}/CROSSEXPANDED ${TDIR}/CROSSPAIRS ${TDIR}/CROSSILLEGAL \
  278.       ${TDIR}/ILLEGALCOMBOS ${TDIR}/FAKEDICT ${TDIR}/FAKEHASH.hash \
  279.       ${TDIR}/AWKSCRIPT ${TDIR}/CROSSROOTS.[0-9]* ${TDIR}/CROSSEXP.[0-9]* \
  280.       ${TDIR}/CROSSPAIRS.[0-9]* ${TDIR}/CROSSILLEGAL.[0-9]*
  281.     ln $EXPANDEDINPUT ${TDIR}/EXPANDEDINPUT
  282.     ln $STRIPPEDINPUT ${TDIR}/STRIPPEDINPUT
  283.     ln $CRUNCHEDINPUT ${TDIR}/CRUNCHEDINPUT
  284.     ln $PRODUCTLIST ${TDIR}/PRODUCTLIST
  285.     ln $EXPANDEDPAIRS ${TDIR}/EXPANDEDPAIRS
  286.     ln $LEGALFLAGLIST ${TDIR}/LEGALFLAGLIST
  287.     ln $JOINEDPAIRS ${TDIR}/JOINEDPAIRS
  288.     ln $MINIMALAFFIXES ${TDIR}/MINIMALAFFIXES
  289.     ln $CROSSROOTS ${TDIR}/CROSSROOTS
  290.     ln $CROSSEXPANDED ${TDIR}/CROSSEXPANDED
  291.     ln $CROSSPAIRS ${TDIR}/CROSSPAIRS
  292.     ln $CROSSILLEGAL ${TDIR}/CROSSILLEGAL
  293.     ln $ILLEGALCOMBOS ${TDIR}/ILLEGALCOMBOS
  294.     ln $FAKEDICT ${TDIR}/FAKEDICT
  295.     ln $FAKEHASH ${TDIR}/FAKEHASH.hash
  296.     ln $AWKSCRIPT ${TDIR}/AWKSCRIPT
  297. fi
  298. #
  299. # Create a dummy dictionary to hold a compiled copy of the language
  300. # table.  Initially, it holds the conversion table, if it exists.
  301. #
  302. case "X$convtabs" in
  303.     X)
  304.     convtabs="$langtabs"
  305.     ;;
  306. esac
  307. echo 'QQQQQQQQ' > $FAKEDICT
  308. buildhash -s $FAKEDICT $convtabs $FAKEHASH \
  309.   ||  (echo "Couldn't create fake hash file" 1>&2; rm -f ${TMP}*; exit 1) \
  310.   ||  exit 1
  311. #
  312. # Figure out how 'sort' sorts signed fields, for arguments to ijoin.
  313. # This is a little bit of a tricky pipe, but the result is that SIGNED
  314. # is set to "-s" if characters with the top bit set sort before those
  315. # without, and "-u" if the reverse is true.  How does it work?  The
  316. # first "tr" step generates two lines, one containing "-u", the other
  317. # with the same but with the high-order bit set.  The second "tr"
  318. # changesthe high-bit "-u" back to "-s".  If the high-bit "-u" was
  319. # sorted first, the sed step will select "-s" for SIGNED; otherwise
  320. # it'll pick "-u".  We have to be careful about backslash quoting
  321. # conventions, because some systems differ.
  322. #
  323. backslash=\\
  324. for i in 0 1 2 3
  325. do
  326.     if [ `echo a | tr "${backslash}141" b` = b ]
  327.     then
  328.     break
  329.     fi
  330.     backslash="$backslash$backslash"
  331. done
  332. SIGNED=`echo '-s
  333. -u' | tr s "${backslash}365" | sort | tr "${backslash}365" s | sed 1q`
  334. #
  335. # Collect all the input and expand all the affix options (ispell -e),
  336. # and preserve (sorted) for later joining in EXPANDEDINPUT.  The icombine
  337. # step is to make sure that unneeded capitalizations (e.g., Farmer and farmer)
  338. # are weeded out.  The first sort must be folded for icombine;  the second
  339. # must be unfolded for join.
  340. #
  341. $verbose  &&  echo "Collecting input." 1>&2
  342. if [ $# -eq 0 ]
  343. then
  344.     ispell "$wchars" -e1 -d $FAKEHASH -p /dev/nul | tr " " '
  345. '
  346. else
  347.     cat "$@" | ispell "$wchars" -e1 -d $FAKEHASH -p /dev/nul | tr " " '
  348. '
  349. fi \
  350.   | sort $SORTTMP -u +0f -1 +0 \
  351.   | $COMBINE $icflags $langtabs \
  352.   | sort $SORTTMP -u > $EXPANDEDINPUT
  353. #
  354. # If a conversion table existed, recreate the fake hash file with the
  355. # "real" language table.
  356. #
  357. case "$convtabs" in
  358.     $langtabs)
  359.     ;;
  360.     *)
  361.     buildhash -s $FAKEDICT $langtabs $FAKEHASH \
  362.       ||  (echo "Couldn't create fake hash file" 1>&2; \
  363.         rm -f ${TMP}*; exit 1) \
  364.       ||  exit 1
  365.     ;;
  366. esac
  367. rm -f ${FAKEDICT}*
  368. #
  369. # If the -s (strip) option was specified, remove all
  370. # expanded words that are covered by the dictionary.  This produces
  371. # the final list of expanded words that this dictionary must cover.
  372. # Leave the list in STRIPPEDINPUT.
  373. #
  374. if [ "X$strip" = "Xno" ]
  375. then
  376.     rm -f $STRIPPEDINPUT
  377.     ln $EXPANDEDINPUT $STRIPPEDINPUT
  378.     if [ "$debug" = yes ]
  379.     then
  380.     rm -f ${TDIR}/STRIPPEDINPUT
  381.     ln $STRIPPEDINPUT ${TDIR}/STRIPPEDINPUT
  382.     fi
  383. else
  384.     $verbose  &&  echo "Stripping words already in the dictionary." 1>&2
  385.     ispell "$wchars" -l $dictopt -p /dev/nul < $EXPANDEDINPUT \
  386.       > $STRIPPEDINPUT
  387. fi
  388. #
  389. # Figure out what the flag-marking character is.
  390. #
  391. $verbose  &&  echo "Finding flag marker." 1>&2
  392. flagmarker=`ispell -D -d $FAKEHASH \
  393.   | sed -n '/^flagmarker/s/flagmarker //p'`
  394. case "$flagmarker" in
  395.     \\*)
  396.     flagmarker=`expr "$flagmarker" : '.\(.\)'`
  397.     ;;
  398. esac    
  399. #
  400. # Munch the input to generate roots and affixes (ispell -c).  We are
  401. # only interested in words that have at least one affix (egrep $flagmarker);
  402. # the next step will pick up the rest.  Some of the roots are illegal.  We
  403. # use join to restrict the output to those root words that are found
  404. # in the original dictionary.
  405. #
  406. $verbose  &&  echo "Generating roots and affixes." 1>&2
  407. ispell "$wchars" -c -W0 -d $FAKEHASH -p /dev/nul < $STRIPPEDINPUT \
  408.   | tr " " '
  409. ' \
  410.   | egrep "$flagmarker" | sort $SORTTMP -u "-t$flagmarker" +0 -1 +1 \
  411.   | $JOIN $SIGNED "-t$flagmarker" - $EXPANDEDINPUT > $CRUNCHEDINPUT
  412. #
  413. # We now have a list of legal roots, and of affixes that apply to the
  414. # root words.  However, it is possible for some affix flags to generate more
  415. # than one output word.  For example, with the flag table entry
  416. #
  417. #    flag R:    . > ER
  418. #        . > ERS
  419. #
  420. # the input "BOTHER" will generate an entry "BOTH/R" in CRUNCHEDINPUT.  But
  421. # this will accept "BOTHER" and "BOTHERS" in the dictionary, which is
  422. # wrong (in this case, though it's good English).
  423. #
  424. # To cure this problem, we first have to know which flags generate which
  425. # expansions.  We use ispell -e3 to expand the flags (the second e causes
  426. # the root and flag to be included in the output), and get pairs
  427. # suitable for joining.  In the example above, we would get
  428. #
  429. #    BOTH/R BOTHER
  430. #    BOTH/R BOTHERS
  431. #
  432. # We save this in EXPANDEDPAIRS for the next step.
  433. #
  434. $verbose  &&  echo 'Expanding dictionary into EXPANDEDPAIRS.' 1>&2
  435. ispell "$wchars" -e3 -d $FAKEHASH -p /dev/nul < $CRUNCHEDINPUT \
  436.   | sort $SORTTMP +1 > $EXPANDEDPAIRS
  437. #
  438. # Now we want to extract the lines in EXPANDEDPAIRS in which the second field
  439. # is *not* listed in the original dictionary EXPANDEDINPUT;  these illegal
  440. # lines contain the flags we cannot include without accepting illegal words.
  441. # It is somewhat easier to extract those which actually are listed (with
  442. # join), and then use comm to strip these from EXPANDEDPAIRS to get the
  443. # illegal expansions, together with the flags that generate them (we must
  444. # re-sort EXPANDEDPAIRS before running comm).  Sed
  445. # gets rid of the expansion and uniq gets rid of duplicates.  Comm then
  446. # selects the remainder of the list from CRUNCHEDINPUT and puts it in
  447. # LEGALFLAGLIST.  The final step is to use a sort and icombine to put
  448. # the list into a one-entry-per-root format.
  449. #
  450. # BTW, I thought of using cut for the sed step (on systems that have it),
  451. # but it turns out that sed is faster!
  452. #
  453. $JOIN -j1 2 -o 1.1 1.2 $SIGNED $EXPANDEDPAIRS $EXPANDEDINPUT \
  454.   | sort $SORTTMP -u > $JOINEDPAIRS
  455.  
  456. sort $SORTTMP -o $EXPANDEDPAIRS $EXPANDEDPAIRS
  457. sort $SORTTMP -o $CRUNCHEDINPUT $CRUNCHEDINPUT
  458.  
  459. $verbose  &&  echo 'Creating list of legal roots/flags.' 1>&2
  460. comm -13 $JOINEDPAIRS $EXPANDEDPAIRS \
  461.   | (sed -e 's; .*$;;' ; rm -f $JOINEDPAIRS $EXPANDEDPAIRS) \
  462.   | uniq \
  463.   | (comm -13 - $CRUNCHEDINPUT ; rm -f $CRUNCHEDINPUT) \
  464.   | sort $SORTTMP -u "-t$flagmarker" +0f -1 +0 \
  465.   | $COMBINE $langtabs > $LEGALFLAGLIST
  466.  
  467. #
  468. # LEGALFLAGLIST now contains root/flag combinations that, when expanded,
  469. # produce only words from EXPANDEDPAIRS.  However, there is still a
  470. # problem if the language tables have any cross-product flags.  A legal
  471. # root may appear in LEGALFLAGLIST with two flags that participate
  472. # in cross-products.  When such a dictionary entry is expanded,
  473. # the cross-products will generate some extra words that may not
  474. # be in EXPANDEDPAIRS.  We need to remove these from LEGALFLAGLIST.
  475. #
  476. # The first step is to collect the names of the flags that participate
  477. # in cross-products.  Ispell will dump the language tables for us, and
  478. # sed is a pretty handy way to strip out extra information.  We use
  479. # uniq -c and a numerical sort to put the flags in approximate order of how
  480. # "productive" they are (in terms of how likely they are to generate a lot
  481. # of output words).  The least-productive flags are given last and will
  482. # be removed first.
  483. #
  484. $verbose \
  485.   &&  echo 'Creating list of flags that participate in cross-products.' 1>&2
  486. ispell -D -d $FAKEHASH \
  487.   | sed -n '1,$s/:.*$//
  488.     /^flagmarker/d
  489.     /^prefixes/,/^suffixes/s/^  flag \*/p /p
  490.     /^suffixes/,$s/^  flag \*/s /p' \
  491.   | sort $SORTTMP \
  492.   | uniq -c \
  493.   | tr '    ' ' ' \
  494.   | sort $SORTTMP +0rn -1 +2 > $PRODUCTLIST
  495.  
  496. if [ `egrep ' p ' $PRODUCTLIST | wc -l` -gt 0 \
  497.   -a `egrep ' s ' $PRODUCTLIST | wc -l` -gt 0 ]
  498. then
  499.     #
  500.     # The language tables allow cross products.  See if LEGALFLAGLIST has
  501.     # any roots with multiple cross-product flags.  Put them in CROSSROOTS.
  502.     #
  503.     $verbose  &&  echo 'Finding prefix and suffix flags.' 1>&2
  504.     preflags=`sed -n 's/^[ 0-9]*p //p' $PRODUCTLIST | tr -d '
  505. '`
  506.     sufflags=`sed -n 's/^[ 0-9]*s //p' $PRODUCTLIST | tr -d '
  507. '`
  508.     egrep "$flagmarker.*[$preflags].*[$sufflags]|$flagmarker.*[$sufflags].*[$preflags]" \
  509.       $LEGALFLAGLIST \
  510.       > $CROSSROOTS
  511.  
  512.     #
  513.     # We will need an awk script;  it's so big that it core-dumps my shell
  514.     # under certain conditions.  The rationale behind the script is commented
  515.     # where the script is used.  Note that you may want to change this
  516.     # script for languages other than English.
  517.     #
  518.     case "$flagmarker" in
  519.     /)
  520.         sedchar=:
  521.         ;;
  522.     *)
  523.         sedchar=/
  524.         ;;
  525.     esac
  526.     $verbose  &&  echo 'Creating awk script.' 1>&2
  527.     sed -e "s/PREFLAGS/$preflags/" -e "s/SUFFLAGS/$sufflags/" \
  528.       -e "s;ILLEGALCOMBOS;$ILLEGALCOMBOS;" \
  529.       -e "s${sedchar}FLAGMARKER${sedchar}$flagmarker${sedchar}" \
  530.       > $AWKSCRIPT << 'ENDOFAWKSCRIPT'
  531.     BEGIN \
  532.         {
  533.         preflags = "PREFLAGS"
  534.         sufflags = "SUFFLAGS"
  535.         illegalcombos = "ILLEGALCOMBOS"
  536.         flagmarker = "FLAGMARKER"
  537.         pflaglen = length (preflags)
  538.         for (i = 1;  i <= pflaglen;  i++)
  539.         pflags[i] = substr (preflags, i, 1);
  540.         sflaglen = length (sufflags)
  541.         for (i = 1;  i <= sflaglen;  i++)
  542.         sflags[i] = substr (sufflags, i, 1);
  543.         }
  544.         {
  545.         len = length ($2)
  546.         pnew2 = ""
  547.         snew2 = ""
  548.         pbad = ""
  549.         sbad = ""
  550.         sufs = 0
  551.         pres = 0
  552.         for (i = 1;  i <= len;  i++)
  553.         {
  554.         curflag = substr ($2, i, 1)
  555.         for (j = 1;  j <= pflaglen;  j++)
  556.             {
  557.             if (pflags[j] == curflag)
  558.             {
  559.             pres++
  560.             pnew2 = substr ($2, 1, i - 1) substr ($2, i + 1)
  561.             pbad = curflag
  562.             }
  563.             }
  564.         for (j = 1;  j <= sflaglen;  j++)
  565.             {
  566.             if (sflags[j] == curflag)
  567.             {
  568.             sufs++
  569.             snew2 = substr ($2, 1, i - 1) substr ($2, i + 1)
  570.             sbad = curflag
  571.             }
  572.             }
  573.         }
  574.         if (pres == 1)
  575.         {
  576.         print $1 flagmarker pnew2
  577.         print $1 flagmarker pbad >> illegalcombos
  578.         }
  579.         else if (sufs == 1)
  580.         {
  581.         print $1 flagmarker snew2
  582.         print $1 flagmarker sbad >> illegalcombos
  583.         }
  584.         else if (pres > 0)
  585.         {
  586.         print $1 flagmarker pnew2
  587.         print $1 flagmarker pbad >> illegalcombos
  588.         }
  589.         else
  590.         {
  591.         print $1 flagmarker snew2
  592.         print $1 flagmarker sbad >> illegalcombos
  593.         }
  594.         }
  595. ENDOFAWKSCRIPT
  596.     : > $ILLEGALCOMBOS
  597.     dbnum=0
  598.     while [ -s $CROSSROOTS ]
  599.     do
  600.     #
  601.     # CROSSROOTS contains the roots whose cross-product expansions
  602.     # might be illegal.  We now need to locate the actual illegal ones.
  603.     # We do this in much the same way we created LEGALFLAGLIST from
  604.     # CRUNCHEDINPUT.  First we make CROSSEXPANDED, which is analogous
  605.     # to EXPANDEDPAIRS.
  606.     #
  607.     $verbose  &&  echo "Creating cross expansions (pass $dbnum)." 1>&2
  608.     ispell "$wchars" -e3 -d $FAKEHASH -p /dev/nul < $CROSSROOTS \
  609.       | sort $SORTTMP +1 > $CROSSEXPANDED
  610.     #
  611.     # Now we join CROSSEXPANDED against EXPANDEDINPUT to produce
  612.     # CROSSPAIRS, and then comm that against CROSSEXPANDED to
  613.     # get CROSSILLEGAL, the list of illegal cross-product flag
  614.     # combinations.
  615.     #
  616.     $JOIN -j1 2 -o 1.1 1.2 $SIGNED $CROSSEXPANDED $EXPANDEDINPUT \
  617.       | sort $SORTTMP -u > $CROSSPAIRS
  618.  
  619.     sort $SORTTMP -u -o $CROSSEXPANDED $CROSSEXPANDED
  620.  
  621.     $verbose \
  622.       &&  echo "Finding illegal cross expansions (pass $dbnum)." 1>&2
  623.     comm -13 $CROSSPAIRS $CROSSEXPANDED \
  624.       | sed -e 's; .*$;;' \
  625.       | uniq > $CROSSILLEGAL
  626.  
  627.     if [ "$debug" = yes ]
  628.     then
  629.         mv $CROSSROOTS $TDIR/CROSSROOTS.$dbnum
  630.         ln $CROSSEXPANDED $TDIR/CROSSEXP.$dbnum
  631.         ln $CROSSPAIRS $TDIR/CROSSPAIRS.$dbnum
  632.         ln $CROSSILLEGAL $TDIR/CROSSILLEGAL.$dbnum
  633.     fi
  634.     #
  635.     # Now it is time to try to clear up the illegalities.  For 
  636.     # each word in the illegal list, remove one of the cross-product
  637.     # flags.  The flag chosen is selected in an attempt to cure the
  638.     # problem quickly, as follows:  (1) if there is only one suffix
  639.     # flag or only one prefix flag, we remove that.  (2) If there is
  640.     # a prefix flag, we remove the "least desirable" (according to
  641.     # the order of preflags). (This may be pro-English prejudice,
  642.     # and you might want to change this if your language is prefix-heavy).
  643.     # (3) Otherwise we remove the least-desirable suffix flag
  644.     #
  645.     # The output of the awk script becomes the new CROSSROOTS.  In
  646.     # addition, we add the rejected flags to ILLEGALCOMBOS (this is done
  647.     # inside the awk script) so they can be removed from LEGALFLAGLIST
  648.     # later.
  649.     #
  650.     awk "-F$flagmarker" -f $AWKSCRIPT $CROSSILLEGAL > $CROSSROOTS
  651.     if [ "$debug" = yes ]
  652.     then
  653.         rm -f $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL
  654.     fi
  655.     dbnum=`expr $dbnum + 1`
  656.     done
  657.     rm -f $CROSSEXPANDED $CROSSPAIRS $CROSSILLEGAL $AWKSCRIPT
  658.     #
  659.     # Now we have, in ILLEGALCOMBOS, a list of root/flag combinations
  660.     # that must be removed from LEGALFLAGLIST to get the final list
  661.     # of truly legal flags.  ILLEGALCOMBOS has one flag per line, so
  662.     # by turning LEGALFLAGLIST into this form (sed), it's an
  663.     # easy task for comm.  We have to recombine flags again after the
  664.     # extraction, to get all flags for a given root on the same line so that
  665.     # cross-products will come out right.
  666.     #
  667.     if [ -s $ILLEGALCOMBOS ]
  668.     then
  669.     sort $SORTTMP -u -o $ILLEGALCOMBOS $ILLEGALCOMBOS
  670.     $verbose  &&  echo 'Finding roots of cross expansions.' 1>&2
  671.     sort $SORTTMP $LEGALFLAGLIST \
  672.       | sed '/\/../{
  673.           s;^\(.*\)/\(.\)\(.*\);\1/\2\
  674. \1/\3;
  675.           P
  676.           D
  677.           }' \
  678.       | comm -23 - $ILLEGALCOMBOS \
  679.       | sort $SORTTMP -u "-t$flagmarker" +0f -1 +0 \
  680.       | $COMBINE $langtabs > $CROSSROOTS
  681.     mv $CROSSROOTS $LEGALFLAGLIST
  682.     if [ "$debug" = yes ]
  683.     then
  684.         rm -f ${TDIR}/LEGALFLAGLIST1
  685.         ln $LEGALFLAGLIST ${TDIR}/LEGALFLAGLIST1
  686.     fi
  687.     fi
  688. fi
  689. rm -f $PRODUCTLIST $CROSSROOTS $ILLEGALCOMBOS $EXPANDEDINPUT
  690. #
  691.  
  692. # We now have (in LEGALFLAGLIST) a list of roots and flags which will
  693. # accept words taken from EXPANDEDINPUT and no others (though some of
  694. # EXPANDEDINPUT is not covered by this list).  However, many of the
  695. # expanded words can be generated in more than one way.  For example,
  696. # "bather" can be generated from "bath/R" and "bathe/R".  This wastes
  697. # unnecessary space in the raw dictionary and, in some cases, in the
  698. # hash file as well.  The solution is to list the various ways of
  699. # getting a given word and choose exactly one.  All other things being
  700. # equal, we want to choose the one with the highest expansion length
  701. # to root length ratio.  The ispell -e4 option takes care of this by
  702. # providing us with a field to sort on.
  703. #
  704. # The ispell/awk combination is similar to the ispell/sed pipe used to
  705. # generate EXPANDEDPAIRS, except that ispell adds an extra field
  706. # giving the sort order.  The first sort gets things in order so the
  707. # first root listed is the one we want, and the second sort (-um) then
  708. # selects that first root.  Sed strips the expansion from the root,
  709. # and a final sort -u generates MINIMALAFFIXES, the final list of
  710. # affixes that (more or less) minimally covers what it can from
  711. # EXPANDEDINPUT.
  712. #
  713. $verbose  &&  echo 'Eliminating non-optimal affixes.' 1>&2
  714. ispell "$wchars" -e4 -d $FAKEHASH -p /dev/nul < $LEGALFLAGLIST \
  715.   | sort $SORTTMP +1 -2 +2rn -3 +0 -1 \
  716.   | sort $SORTTMP -um +1 -2 \
  717.   | sed -e 's; .*$;;' \
  718.   | sort $SORTTMP -u "-t$flagmarker" +0f -1 +0 > $MINIMALAFFIXES
  719. rm -f $LEGALFLAGLIST
  720. #
  721. # Now we're almost done.  MINIMALAFFIXES covers some (with luck, most)
  722. # of the words in STRIPPEDINPUT.  Now we must create a list of the remaining
  723. # words (those omitted by MINIMALAFFIXES) and add it to MINIMALAFFIXES.
  724. # The best way to do this is to actually build a partial dictionary from
  725. # MINIMALAFFIXES in FAKEHASH, and then use ispell -l to list the words that
  726. # are not covered by this dictionary.  This must then be combined with the
  727. # reduced version of MINIMALAFFIXES and sorted to produce the final result.
  728. #
  729. $verbose  &&  echo "Generating output word list." 1>&2
  730. if [ -s $MINIMALAFFIXES ]
  731. then
  732.     buildhash -s $MINIMALAFFIXES $langtabs $FAKEHASH > /dev/nul \
  733.       ||  (echo "Couldn't create intermediate hash file" 1>&2;
  734.     rm -f ${TMP}*;
  735.     exit 1) \
  736.       ||  exit 1
  737.     if [ "$debug" = yes ]
  738.     then
  739.     rm -f ${TDIR}/MINAFFIXES..cnt \
  740.       ${TDIR}/MINAFFIXES.stat
  741.     ln $MINIMALAFFIXES..cnt ${TDIR}/MINAFFIXES..cnt
  742.     ln $MINIMALAFFIXES.stat ${TDIR}/MINAFFIXES.stat
  743.     fi
  744.     (ispell "$wchars" -l -d $FAKEHASH -p /dev/nul < $STRIPPEDINPUT; \
  745.     $COMBINE $langtabs < $MINIMALAFFIXES) \
  746.       | sort $SORTTMP "-t$flagmarker" -u +0f -1 +0
  747. else
  748.     # MINIMALAFFIXES is empty;  just produce a sorted version of STRIPPEDINPUT
  749.     sort $SORTTMP "-t$flagmarker" -u +0f -1 +0 $STRIPPEDINPUT
  750. fi
  751. rm -f ${TMP}*
  752. if [ "X$MUNCHMAIL" != X ]
  753. then
  754.     (
  755.     ls -ld ${TDIR}/[A-Z]*
  756.     cat /tmp/munchlist.mail
  757.     ) | mail "$MUNCHMAIL"
  758.     rm -f /tmp/munchlist.mail
  759. fi
  760. exit 0
  761.