home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / bin / munchlist < prev    next >
Encoding:
Text File  |  1995-07-03  |  24.6 KB  |  739 lines

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