home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume10 / hum / part01 next >
Internet Message Format  |  1989-08-08  |  57KB

  1. Path: uunet!rs
  2. From: rs@uunet.UU.NET (Rich Salz)
  3. Newsgroups: comp.sources.unix
  4. Subject: v10i27:  Bill Tuthill's "hum" text concordance package, Part01/03
  5. Message-ID: <474@uunet.UU.NET>
  6. Date: 26 Jun 87 21:48:47 GMT
  7. Organization: UUNET Communications Services, Arlington, VA
  8. Lines: 1965
  9. Approved: rs@uunet.uu.net
  10.  
  11. Submitted by: John Gilmore <hoptoad!gnu>
  12. Mod.Sources: Volume 10, Number 27
  13. Archive-name: hum/Part01
  14.  
  15. I advertised this package in comp.text.desktop and a bunch of people
  16. are interested.  I find it a good bunch of tools for text analysis and
  17. I think it belongs in the archives.
  18.  
  19. It was developed by Bill Tuthill while he was at UC Berkeley.  He
  20. authorized me to pass it on to you for publication.
  21.  
  22. [ Its a pretty gud writing tool.  --r$  ]
  23. : To unbundle, sh this file
  24. echo Hum.tutorial
  25. cat >Hum.tutorial <<'@@@ Fin de Hum.tutorial'
  26. .if n .ds Q \&"
  27. .if n .ds U \&"
  28. .if t .ds Q ``
  29. .if t .ds U ''
  30. .ND 3 November 1981
  31. .nr LL 6.5i
  32. .nr FL 6.2i
  33. .RP
  34. .TL
  35. Hum \(em A Concordance and Text Analysis Package
  36. .AU
  37. William Tuthill
  38. .AI
  39. Comparative Literature Department
  40. University of California
  41. Berkeley, California  94720
  42. .AB
  43. A new package of programs for literary and linguistic
  44. computing is available, emphasizing the preparation 
  45. of concordances and supporting documents.
  46. Both keyword in context and keyword and line generators are provided,
  47. as well as exclusion routines, a reverse concordance module, 
  48. formatting programs, a dictionary maker, and lemmatization facilities.
  49. There are also word, character, and digraph
  50. frequency counting programs,
  51. word length tabulation routines,
  52. a cross reference generator, and other related utilities.
  53. The programs are written in the C programming language,
  54. and implemented on several Version 7 Unix\(dg systems at Berkeley.
  55. .FS
  56. \(dg Unix is a trademark of Bell Laboratories.
  57. .FE
  58. They should be portable to any system that has a C compiler
  59. and which supports some kind of pipe mechanism.
  60. At Berkeley, they reside in ~hum/bin; documentation is in ~hum/man.
  61. This paper constitutes a tutorial introduction to the package.
  62. Manual pages for the various programs are available separately.
  63. .AE
  64. .SH
  65. The Literary Text
  66. .PP
  67. There are two indispensable prerequisites for the analysis
  68. of a literary text by computer: the computer, and the text itself.
  69. About 98% of the work involved is entering and correcting the text,
  70. so if you can obtain your text elsewhere, you will save a lot of time.
  71. Check with the Unix consultants to see what tape formats
  72. can be read at the Berkeley installation,
  73. and have the text taped in one of these formats.
  74. It is also possible to read cards through the link
  75. to the IBM 4341; again, check with the consultants.
  76. .PP
  77. If you are forced to enter the text yourself (and most people are),
  78. you must learn to use the Unix system, and the editor.
  79. Fortunately, the Computer Center has provided
  80. good documentation at the beginning stages.
  81. Start off with the tutorial,
  82. \fICommunicating with Unix,\fP\|
  83. which will teach you how to use the Unix system.\**
  84. .FS
  85. Ricki Blau,
  86. \fICommunicating with Unix,\fP\|
  87. Computing Services, Berkeley (1980).
  88. .FE
  89. Then read through
  90. \fIEdit: A Tutorial\fP\|
  91. carefully, and learn how to use the \fBedit/ex\fP editor.\**
  92. .FS
  93. Ricki Blau and James Joyce,
  94. \fIEdit: a Tutorial,\fP\|
  95. Computing Services, Berkeley (1980).
  96. .FE
  97. At first, you will be spending most of your time inside the editor.
  98. .PP
  99. After you become comfortable with the editor,
  100. it would save you time in the long run
  101. to familiarize yourself with \fBex\fP and \fBvi\fP.
  102. For \fBvi\fP, read
  103. \fIAn Introduction to Display Editing with Vi,\fP\|
  104. a tutorial introduction that is relatively easy to understand.\**
  105. .FS
  106. William Joy and Mark Horton,
  107. \fIAn Introduction to Display Editing with Vi,\fP\|
  108. EECS Dep't, Berkeley (1980).
  109. The \fIEx Reference Manual,\fP\|
  110. EECS Dep't, Berkeley (1980),
  111. by the same two authors,
  112. can be used in conjunction with the introduction to \fBvi\fR.
  113. .FE
  114. \fBEdit\fR is a simplified but less flexible version of \fBex\fR,
  115. both being line-oriented command editors.
  116. \fBVi\fR is identical to visual mode of \fBex\fR,
  117. a screen-oriented display editor that permits
  118. intraline changes to be made easily and effectively.
  119. By the time you begin correcting your text,
  120. you should know how to use \fBvi\fP, or at least open mode of \fBex\fR.
  121. It is far easier to make changes this way
  122. than with the substitute command of \fBedit\fR.
  123. .PP
  124. When you finish correcting your text,
  125. it would be helpful for you to learn more about the C-Shell,
  126. which is the default command processing language at Berkeley.
  127. The tutorial and reference document to consult is
  128. \fIAn Introduction to the C Shell.\fP\**
  129. .FS
  130. William Joy,
  131. \fIAn Introduction to the C Shell,\fP\|
  132. EECS Dep't, Berkeley (1979).
  133. .FE
  134. Most of the text analysis is going to be done with programs
  135. called from the shell.
  136. Another good document that is slightly out of date,
  137. but which is still very helpful, is
  138. \fIUnix for Beginners.\fP\**
  139. .FS
  140. Brian Kernighan,
  141. \fIUnix for Beginners,\fP\|
  142. Bell Laboratories, Murray Hill (1978).
  143. .FE
  144. By beginner, they mean someone
  145. who is a systems programmer on another system.
  146. The article teaches you about programming the shell,
  147. and covers other advanced topics.
  148. .SH
  149. Practical Suggestions
  150. .PP
  151. Before beginning text entry,
  152. you should organize your file and directory structure.
  153. If you are encoding a novel (or a saga),
  154. you should probably make a directory for the novel,
  155. and then put each chapter in a file of its own.
  156. If you are entering an epic poem, it would be best
  157. to have a directory for the epic, and individual files for each section.
  158. And if you are typing in many short poems by various authors,
  159. put each author into a separate directory,
  160. and each poem into a file in its respective directory.
  161. In other words, let your data determine your file structure.
  162. Later, it will be easy to access and label your texts.
  163. In any case, files should not be larger than 250,000 characters,
  164. and even then they are difficult to work with.
  165. .PP
  166. At this point, you must decide how your text is to be numbered.
  167. A novel, of course, should be labelled by page number,
  168. an epic or romance by line number and perhaps section number,
  169. and lyric poems by author, poem number, and line number.
  170. A good rule is to follow the numbering in the best edition available.
  171. We will discuss section numbering and author labelling later.
  172. Line numbering is no problem,
  173. since many Unix programs are built around line structure.
  174. If you need to page number, however, reserve the equals sign,
  175. if possible, because it is normally the page indicator.
  176. Put an equals sign on a line of its own every time
  177. there is a new page in your novel,
  178. and follow line separation in your edition exactly;
  179. do not hyphenate words from one line to the next, however.
  180. The \fBkwic\fP, \fBsfind\fP, and \fBxref\fP programs will use the page indicator
  181. to label your text with the correct page numbers.
  182. .PP
  183. Text in a foreign language, especially if there are diacritical marks,
  184. requires a good deal of thought and preparation.
  185. The first rule to follow is that an accent mark
  186. cannot be a punctuation mark, and vice-versa,
  187. because an accent mark is part of a word,
  188. while a punctuation mark is not.
  189. One typical problem is the cedilla,
  190. which is often represented as an overstruck comma.
  191. A similar problem is the umlaut, which is best
  192. represented by the double quote mark.
  193. If you have cedillas or umlauts in your text,
  194. you will have to use one of two special programs,
  195. \fBcedilla\fP or \fBumlaut\fP, along with special concordance options.
  196. All of this will be explained below.
  197. A third problem area is the use of a single quote mark,
  198. which interferes with the apostrophe and the acute accent.
  199. Two solutions are to use the double quote mark all the time,
  200. or to use the grave accent for a single quote mark
  201. (provided, of course, you don't need the grave accent).
  202. .PP
  203. When you have decided what is a punctuation mark
  204. and what is a diacritical mark,
  205. you may have to establish a punctuation file (Pfile, for example)
  206. containing all punctuation marks that are not part of a word.
  207. The default punctuation marks, usable for English
  208. and some other languages, are:
  209. .DS
  210. ,.;:-"?!()[]{}
  211. .DE
  212. Note that the apostrophe is considered part of a word.
  213. If these punctuation marks are acceptable,
  214. you do not need a punctuation file.
  215. If you want a different punctuation set, however,
  216. simply type your punctuation marks onto a single line in a file.
  217. Many Humanities programs will read the file you specify
  218. after the \-d option, and reload the punctuation set
  219. with the last line of this file.
  220. .PP
  221. After text entering is all finished,
  222. you will begin the process of correcting the text,
  223. often the most laborious part of your work.
  224. There are a number of programs to help you in this endeavor,
  225. although nothing can replace careful rereading of the text.
  226. In order to save money and eyestrain, use \fBlpr\fP to get
  227. a hard-copy version of your text from the lineprinter.
  228. If you are working with modern English,
  229. \fBspell\fP may help you to identify words
  230. that are not in the on-line dictionary.
  231. If you are willing to type in your text twice,
  232. you can check one version against the other with \fBdiff\fP,
  233. a process that will eliminate most errors.
  234. All the preceding programs are part of the standard Unix system.
  235. Humanities programs that may help are \fBcfreq \-a\fP,
  236. which can alert you to the presence of strange characters,
  237. and \fBtprep \-t\fP, which gets rid of useless trailing white space.
  238. .SH
  239. Background and References
  240. .PP
  241. Generating a concordance is one of the best uses
  242. of a computer's capabilities that a Humanities scholar can make.
  243. Before the 1950's, many concordances were done by hand,
  244. and you may well imagine the time and effort that went into this.
  245. The first large-scale computer concordance was
  246. a concordance to the Revised Standard Version of the \fIBible,\fP\|
  247. done in 1956 on a Univac I computer.\**
  248. .FS
  249. John W. Ellison,
  250. \fINelson's Complete Concordance of the Revised Standard Version Bible,\fP\|
  251. New York, Thomas Nelson & Sons (1957).
  252. .FE
  253. This pioneering concordance set a standard for intelligent design
  254. and legibility that has seldom been matched since then.
  255. A hand concordance to the King James Version had been done
  256. by James Strong in the late 1800's; it took 35 years to complete.\**
  257. .FS
  258. James Strong,
  259. \fIAn Exhaustive Concordance of the Bible,\fP\|
  260. New York, Hunt & Eaton (1894).
  261. .FE
  262. .PP
  263. It is important to emphasize that not all computer concordance projects
  264. go this smoothly, showing such a dramatic improvement in efficiency.
  265. For example, a hand concordance to Shakespeare's plays was done
  266. by John Bartlett over a 15 year period in the late 1800's,
  267. using \*Qonly the leisure that [his teaching] profession allowed.\*U\**
  268. .FS
  269. John Bartlett,
  270. \fIA Complete Concordance to the Dramatic Works of Shakespeare,\fP\|
  271. New York, Macmillan & Co (1896).
  272. .FE
  273. A computer generated concordance to Shakespeare done at Harvard
  274. University, by contrast, took nearly as long to complete.
  275. It was started in the days before their computer recognized
  276. upper and lower case distinctions, and consequently,
  277. Shakespeare's text, as concorded, contains no capital letters!
  278. This work was done on an IBM 360/50 computer.
  279. A preliminary version was published by a German press in 1969,
  280. but the final typeset version was not ready until 1973.\**
  281. .FS
  282. Marvin Spevack,
  283. \fIThe Harvard Concordance to Shakespeare,\fP\|
  284. Cambridge Mass, Harvard University Press (1973).
  285. .FE
  286. .PP
  287. Since the pioneering computer concordance by Ellison,
  288. a plethora of concordances has been published,
  289. some better than others, and some almost unusable.
  290. Much work has remained, for lack of money and interest,
  291. in the form of semi-legible lineprinter output.
  292. This has resulted in a kind of academic provincialism,
  293. since these stacks of lineprinter output are seldom shared.
  294. One viable alternative to this situation would be
  295. to make and distribute microfilm concordances,
  296. drastically reducing the cost of paper, printing, and mailing.
  297. .PP
  298. There are, of course, many other ways besides the concordance
  299. to make use of the computer for literary studies.
  300. Susan Hockey's recent book gives a good introduction to
  301. the various possibilities for computer research in the Humanities.\**
  302. .FS
  303. Susan Hockey,
  304. \fIA Guide to Computer Applications in the Humanities,\fP\|
  305. Baltimore and London, Johns Hopkins University Press (1980).
  306. .FE
  307. She gives little practical advice, but does very well at
  308. pointing out the current limitations of the computer.
  309. James Joyce has written a provocative article on
  310. poetry generation and analysis by computer, which combines
  311. the intuitive and analytical approaches to the subject.\**
  312. .FS
  313. James Joyce, \*QPoetry Generation and Analysis,\*U in
  314. \fIAdvances in Computers,\fP\|
  315. vol. 13, pp. 43-72 (1975). 
  316. .FE
  317. Finally, there is a good article from Bell Labs
  318. on statistical text processing, which describes
  319. some linguistic research done on their Unix system.\**
  320. .FS
  321. L. E. McMahon, L. L. Cherry, and R. Morris,
  322. \*QStatistical Text Processing,\*U
  323. in \fIThe Bell System Technical Journal,\fP\|
  324. vol. 57, no. 6, pt. 2, pp. 2137-54 (1978).
  325. .FE
  326. .SH
  327. Making a Concordance
  328. .PP
  329. Before you start using the special package of Humanities programs,
  330. you will need to set your pathname properly, since
  331. the programs all reside in the directory \*Q~hum/bin\*U.
  332. The best way to do this is to have a \*Q.cshrc\*U file
  333. something like the following:
  334. .DS
  335. set  path =  ~hum/bin
  336. set  path = (/usr/cc/bin  /usr/ucb/bin  $path  .\|)
  337. set  history = 20
  338. set  noclobber
  339. .DE
  340. In addition to setting your pathname, this will give you
  341. a history list for performing repeat commands,
  342. and will prevent certain kinds of unintentional file destruction.
  343. After creating the .cshrc file, type the command
  344. \fBsource .cshrc\fP in order to activate the new settings.
  345. Now you can obtain an index to the concordance package
  346. by typing \fBhuman index\fP;
  347. manual pages for all the programs are available
  348. through the same command.
  349. .PP
  350. How you use the concordance package
  351. will be largely determined by how big your text is,
  352. and by what kind of concordance you want.
  353. There are two basic styles available, the
  354. Key Word In Context concordance, and the
  355. Key Word And Line concordance
  356. (hence the names \fBkwic\fP and \fBkwal\fP).
  357. At other computer installations,
  358. a \fBkwal\fP-like program might be called \*Qkwoc\*U or \*Qconc\*U.
  359. To try them out, take a short text, and type these two commands:
  360. .DS
  361. kwic  text  |  sort  |  format
  362. kwal  text  |  sort  |  format
  363. .DE
  364. Many people use the \fBkwal\fP program for poetry, and the
  365. \fBkwic\fP program for prose, but if you are doing formulaic analysis,
  366. you will probably want to use the \fBkwic\fP program for poetry as well.
  367. If you decide to use \fBkwic\fP, learn to use \fBtprep\fP,
  368. since your concordance will look much better
  369. if the end of line is trimmed and the beginning of line is padded.
  370. .PP
  371. If you have a text that is fairly long,
  372. you may want to concord it in the background,
  373. while you do other work.
  374. With background processing,
  375. you should always redirect output to a temporary file,
  376. where you can examine (and perhaps edit) the results
  377. before sending your concordance to the lineprinter.
  378. Use a command something like this:
  379. .DS
  380. kwic  text*  |  sort  |  format  >  /tmp/final   &
  381. .DE
  382. The asterisk expands to mean any file beginning with text,
  383. so if your text has several parts, this will put them all
  384. together, in the same order as they are listed with \fBls\fP.
  385. The greater-than symbol redirects output to the tempfile,
  386. and the ampersand puts the process in background.
  387. If you logout before the process is completed,
  388. then all your background processes will be halted,
  389. and you will have a partial concordance, or none at all.
  390. (You can wait for background processes to terminate
  391. by typing \*Qwait\*U before logging out.)
  392. .PP
  393. If your text is extremely long, or if you want to go home,
  394. you may want to submit the concordance,
  395. so that you can logout without ending the concording process.
  396. Submit processing is charged at the rate of $1.00 per processor minute.
  397. Use the following command:
  398. .DS
  399. submit  "kwic  text*  |  sort  |  format  >  /tmp/final"
  400. .DE
  401. The quotes are necessary, to remove the magic
  402. of the pipe and redirect symbols.
  403. A few hours later, or the next morning,
  404. you can return to examine the temporary file,
  405. and if everything is all right, you can send it to the lineprinter.
  406. Make sure to examine the end of your output file with \fBtail\fP;
  407. your concordance should end with the entries for X, Y, and Z.
  408. .PP
  409. \fBKwic\fP and \fBkwal\fP have many options,
  410. and you will probably use some or all of them.
  411. They are fully explained in the manual section,
  412. but a thumbnail sketch is also given here.
  413. The \-k option sets the keyword length;
  414. you should run the \fBmaxwd\fP program on all your texts,
  415. to determine the length of your longest word.
  416. If it is longer than 15, reset the keyword length accordingly.
  417. The \-w option can be used to label the concordance
  418. as to work or author, and the \-f can be used
  419. to label it for poem number or chapter.
  420. .PP
  421. Line numbering is done automatically,
  422. but can be affected by using the \-l and \-r options.
  423. The \fBkwic\fP program will also do page numbering,
  424. which can be controlled with the \-p and \-i options.
  425. The \fBkwal\fP program has the esoteric \-s and \-x options instead,
  426. to skip over a text-embedded lefthand identification field,
  427. and to suppress automatic linenumbering.
  428. It is possible to use \fBlno\fP to double linenumber
  429. or hemistich number your text, 
  430. and then use \-s and \-x when compiling the concordance.
  431. You may have some other system for labelling your lines.
  432. Older text entered on IBM cards often has the embedded id field.
  433. .PP
  434. With the \fBkwic\fP program, the most popular option
  435. is the \-c to set context width.
  436. The default context width is 50 characters,
  437. that is to say, 25 on either side of the keyword.
  438. This width is suitable for the CRT terminal,
  439. but to fill up a page from the lineprinter, a width 
  440. of 100 to 120 is better, depending on the \-w and \-f options.
  441. The \fBkwal\fP program, by contrast, gives context on a line by line basis,
  442. so if you have some extremely long lines,
  443. they may be too wide for the page.
  444. The \-d option, to read a punctuation file, was explained above.
  445. The + option is used along with the \fBcedilla\fP and \fBumlaut\fP programs.
  446. The \fBkwic\fP and \fBkwal\fP programs can both read from standard input,
  447. if you use a hyphen instead of a filename, but this is not recommended.
  448. .PP
  449. Ordinarily, sorting is done from the beginning of the line,
  450. so that the primary sort field is the keyword,
  451. and the secondary sort field is the line or page number.
  452. With \fBkwic\fP, if you want to sort by context rather than by text location,
  453. you will have to call the \fBsort\fP program as follows:
  454. .DS
  455. kwic  text*  |  sort  \-dft\e|  +3  +1  |  format
  456. .DE
  457. The \-d means dictionary order, ie, ignore punctuation marks.
  458. The \-f means to fold upper case to lower case, ie, ignore capitals.
  459. The \-t indicates that the tab character between fields is a
  460. vertical bar (the backslash takes away its magic meaning).
  461. The +3 makes the keyword and its following context
  462. into the primary sort field, and +1 makes
  463. the linenumber (or page number) into the secondary sort field.
  464. The results of \fBsort\fP are then piped to \fBformat\fP.
  465. If you are doing formulaic analysis, you will want to sort this way.
  466. Make sure to use \fBtprep\fP if you do this kind of sorting.
  467. .PP
  468. If you are planning to publish your concordance,
  469. or even if other people want to peruse it, you should read
  470. William Ingram's article on concordance style.\**
  471. .FS
  472. William Ingram, \*QConcordances for the Seventies,\*U
  473. in \fIComputers and the Humanities,\fP\|
  474. vol. 13, pp. 43-72 (1975).
  475. .FE
  476. Concordances for the eighties will be much like those of the seventies.
  477. Ingram gives many practical suggestions, and points out some
  478. ridiculous errors made in several recently published concordances.
  479. .SH
  480. Concordance Modules
  481. .PP
  482. The concordance package comes with a number of independent modules,
  483. in order to attain acceptable flexibility and efficiency.
  484. With these modules, you can exclude unnecessary words,
  485. create a reverse concordance,
  486. put the concordance into separate alphabetical files,
  487. lemmatize, and format the output.
  488. The \fBsort\fP program may be considered a module,
  489. although it is not technically part of the Humanities package.
  490. For convenience, these modules may be divided into two types:
  491. those used before \fBsort\fP, and those used afterwards.
  492. .PP
  493. The \fBexclude\fP program reads words in an ignore file
  494. (\*Qexclfile\*U by default),
  495. and filters out concordance entries with these keywords.
  496. It can also be used with an only file, to filter out
  497. all concordance entries except those with the desired keywords.
  498. \fBExclude\fP should immediately follow \fBkwic\fP or \fBkwal\fP,
  499. in order to reduce the amount of output going to later modules,
  500. especially \fBsort\fP, which is the most expensive module.
  501. .PP
  502. The \fBrevconc\fP program reverses the keyword,
  503. so that the concordance can be sorted by word endings
  504. rather than by word beginnings.
  505. \fBRevconc\fP should be called both before and after the \fBsort\fP program,
  506. unless you want your words backwards in the final concordance.
  507. .PP
  508. If you are using the + option of \fBkwic\fP or \fBkwal\fP,
  509. to indicate cedillas or umlauts,
  510. you will need to filter your output with \fBcedilla\fP or \fBumlaut\fP.
  511. These programs replace the plus sign with a backspace
  512. and the appropriate accent mark.
  513. .PP
  514. The \fBformat\fP program counts and removes repeated occurrences of keywords,
  515. and creates keyword section headings for the concordance.
  516. If desired, keyword counting can be suppressed,
  517. as can mapping of the keyword to upper case,
  518. and printing of the section heading itself.
  519. \fBFormat\fP is generally the last module used before the output
  520. is sent to a temporary file or to the lineprinter.
  521. .PP
  522. If you were to use all of the modules above,
  523. which is quite unlikely, this would be the proper order
  524. of the command line:
  525. .DS
  526. kwic  +  text | exclude | revconc | sort | revconc | umlaut | format
  527. .DE
  528. Of course, after all that work, it would be good to redirect the
  529. output to a temporary file somewhere.
  530. .PP
  531. For large concordance projects,
  532. you will want to reduce the size of the output files,
  533. so you can edit and print them more easily.
  534. The \fBdict\fP program sends concordance entries to their
  535. proper dictionary file, from A to Z
  536. (actually in ASCII sequence from blank to tilde).
  537. This is useful for large concordances that you must edit by hand.
  538. \fBDict\fP should be used in place of \fBformat\fP;
  539. concordances can be formatted when they are finally printed.
  540. .PP
  541. The \fBlemma\fP program takes specified words and positions them
  542. in the proper order after their specified headword.
  543. This is for keeping inflected words together in one location.
  544. \fBLemma\fP should be called only after the \fBdict\fP program
  545. has separated your concordance into alphabetical sections.
  546. It only works on groups of files created by \fBdict\fP\|.
  547. .PP
  548. Generally, it is considered bad style for a concordance
  549. to contain multiple entries when the same word occurs twice on a line.
  550. For example, Shakespeare's line, \*QTomorrow, tomorrow, and tomorrow,\*U
  551. when run through \fBkwal\fR, will produce three identical entries.
  552. \fBKwic\fR will produce three slightly different entries,
  553. but two of them will be largely redundant.
  554. In order to avoid such redundancy,
  555. use the Unix utility \fBuniq\fP as follows:
  556. .DS
  557. kwal  text*  |  sort  |  uniq  |  format
  558. kwal  text*  |  sort  \-u  |  format
  559. .DE
  560. The second line has exactly the same effect, and is somewhat faster.
  561. It is more difficult to remove redundant lines
  562. with \fBkwic\fP than with \fBkwal\fP,
  563. because \fBuniq\fP will not ignore differing context fields.
  564. However, it is possible to write an \fBawk\fP program
  565. that should work for this purpose.
  566. .PP
  567. At the current time, no facilities are provided
  568. for making interlinear concordances;
  569. it is expected that they will be provided in the future.
  570. .SH
  571. Other Programs for Textual Analysis
  572. .PP
  573. There are other methods of textual analysis besides the concordance,
  574. and programs for some of these methods are available.
  575. One common method is the word frequency count,
  576. which can be accomplished with the \fBfreq\fP program.
  577. Ordinarily, \fBfreq\fP will give frequency counts of words in your text,
  578. organized into alphabetical order.
  579. With the \-n option, it will give these words organized 
  580. by order of frequency, with the most common words first.
  581. If you run out of core with \fBfreq\fP, the same thing can
  582. be done, but much more slowly, with this command:
  583. .DS
  584. wheel  +1  text  |  sort  |  uniq  \-c
  585. .DE
  586. You will probably want to use \fBfreq\fP in conjunction
  587. with \fBpr \-n\fP, to create n-column output.
  588. See the manual page for \fBpr\fP(1) in the 
  589. \fIUnix Programmer's Manual\fP\| for details.
  590. .PP
  591. Some linguistics students may be interested in
  592. the relative frequency of different characters;
  593. the \fBcfreq\fP program may help them out.
  594. \fBCfreq\fP can also count the frequency of digraphs in a text.
  595. Comparison of word lengths may also be useful,
  596. for which purpose there is a \fBwdlen\fP program,
  597. which prints out a hologram along with word length frequencies.
  598. .PP
  599. Most good concordances nowadays are published along with
  600. a word frequency list (in numerical order),
  601. and a reverse list of the graphic forms.
  602. This is simply a list of all the different words in the text,
  603. sorted from the end to the beginning, rather than vice-versa.
  604. It is far less elaborate than the reverse concordance,
  605. but serves many of the same purposes.
  606. To create such a list, use the following program sequence:
  607. .DS
  608. wheel  +1  text  |  rev  |  sort  \-u  |  rev
  609. .DE
  610. \fBWheel +1\fP gives a word list, \fBrev\fP reverses each word,
  611. and the \-u option of \fBsort\fP suppresses all but one copy of
  612. each identical line, rather than giving you useless repeated lines.
  613. Finally \fBrev\fP restores normal order to all the words.
  614. Again, \fBpr \-n\fP may be of some use in creating multi-column output.
  615. .PP
  616. Syntactic analysis by computer is more difficult than lexical analysis,
  617. but the \fBwheel\fP program may be of some help for the former endeavor.
  618. It rolls through the text several words at a time,
  619. printing word clusters of arbitrary size on each line.
  620. These syntactic clusters can then be sorted and counted,
  621. using \fBsort\fP and \fBuniq\fP, in order to find repeated patterns.
  622. .DS
  623. wheel  +3  text  |  sort  |  uniq  \-c  |  sort  \-r
  624. .DE
  625. The above command, for example, yields a count
  626. of three-word syntactic clusters,
  627. with the most frequent ones heading the list.
  628. .PP
  629. Pattern searching is another way to study syntactic patterns
  630. of a text with the aid of a computer.
  631. The Unix utility \fBgrep\fP is useful in this context,
  632. but it is line-oriented, and many literary texts
  633. cannot be studied on a line by line basis.
  634. So there is a program called \fBsfind\fP, which searches
  635. through a text sentence by sentence for a specified pattern.
  636. The entire sentence is printed, along with the matching pattern.
  637. .PP
  638. If you do not need a full-length concordance,
  639. or if you just want a simple index that gives word locations,
  640. there is a cross reference generator named \fBxref\fP.
  641. It will index distinct words in a text by line number,
  642. or by page number, depending on how your text is labelled.
  643. It is slower than \fBfreq\fP, but much faster than a concordance series.
  644. Moreover, its output will be relatively compact.
  645. In the final concordance, you may want 
  646. to have some common words listed with \fBxref\fP indexing,
  647. and more important words listed with full concordance entries.
  648. .PP
  649. After doing a bit of work with the computer,
  650. you may feel constrained by not knowing a programming language.
  651. Many things are extremely difficult to accomplish
  652. if you do not know how to program;
  653. but with a little programming knowledge,
  654. these tasks become exceedingly trivial.
  655. If you are so inclined, you ought to learn \fBawk\fP.
  656. It has the simplicity of \fBbasic\fP,
  657. the string handling capabilites of \fBsnobol\fP,
  658. and the economy of expression found in \fBapl\fP.
  659. The tutorial, \fIAwk \(em A Pattern Scanning and Processing Language,\fP\|
  660. should be enough to get you started.\**
  661. .FS
  662. Alfred Aho, Brian Kernighan, and Peter Weinberger,
  663. \fIAwk \(em A Pattern Scanning and Processing Language,\fP\|
  664. Bell Laboratories, Murray Hill (1978).
  665. .FE
  666. .SH
  667. Related Utilities
  668. .PP
  669. For times when you have to change a print wheel or type ball,
  670. there is the \fBpause\fP program for temporarily halting output.
  671. Pause may also be helpful for generating form letters or mailing lists,
  672. since it gives you time to change paper or envelopes.
  673. .PP
  674. For those who work with textual variants,
  675. the \fBpair\fP program may be of some use.
  676. \fBPair \-m\fP intercalates two files line by line,
  677. while \fBpair\fP sets two files side by side.
  678. Different manuscript versions can be compared
  679. by means of this program.
  680. It is possible to make two textual variants parallel
  681. by inserting blank lines in one variant;
  682. then \fBdiff\fP can be used to collate the manuscripts.
  683. (There is even a \fBdiff3\fP program for examining three texts.)
  684. .PP
  685. Many novice Unix users wonder how they can linenumber a text,
  686. as with the \*Qset number\*U option of \fBex/vi\fP.
  687. There are several ways, but the best approach
  688. for Humanities users is the \fBlno\fP program.
  689. It allows you to set the linenumber
  690. to start at whatever value you want,
  691. and also supports double line numbering and hemistich numbering.
  692. .PP
  693. If your final copy is to be lineprinted, you may want to use
  694. \fBtolpr\fP to shift your text to the right, away from the holes.
  695. It is possible to do extra formatting on your concordance,
  696. and even to print your concordance using the phototypesetter.
  697. If you want to use the typesetter,
  698. use \fBtroffmt\fP instead of \fBformat\fP,
  699. and send the resulting output to \fBtroff \-Q\fP.
  700. It is not necessary to use the \-ms macros.
  701. .PP
  702. If you intend to transport tapes to or from another installation,
  703. especially an IBM-compatible machine,
  704. a number of programs in \*Q/usr/inp/bin\*U may be helpful.
  705. For importing text encoded in fixed length format,
  706. the \fBline\fP program can be used to insert missing newline characters;
  707. \fBtprep\fP can then be used to strip off trailing blanks.
  708. For exporting text, the \fBfixlen\fP program can be used to transform
  709. variable length Unix lines into IBM-style fixed length records.
  710. For rearranging fields of a record
  711. (for instance, to move linenumbers from the last few columns
  712. to the first few columns), use the \fBpermute\fP program.
  713. .SH
  714. Acknowledgements
  715. .PP
  716. The author would like to thank Professors
  717. Joseph J. Duggan and John Lindow for research funding
  718. during the early stages of this project.
  719. James Joyce gave advice on the general design of this package,
  720. and Brad Rubenstein and Bob Campbell helped immeasurably
  721. with much of the actual programming.
  722. The project was finally completed with the financial support
  723. of a grant from the National Endowment for the Humanities.
  724.  
  725. @@@ Fin de Hum.tutorial
  726. echo Makefile
  727. cat >Makefile <<'@@@ Fin de Makefile'
  728. BIN=
  729.  
  730. all:    $(BIN)accent $(BIN)cfreq $(BIN)dict $(BIN)exclude $(BIN)format\
  731.     $(BIN)freq $(BIN)kwal $(BIN)kwic $(BIN)lno $(BIN)med\
  732.     $(BIN)maxwd $(BIN)pair $(BIN)pause $(BIN)revconc $(BIN)sfind\
  733.     $(BIN)skel $(BIN)togrk $(BIN)tolpr $(BIN)tosel $(BIN)tprep\
  734.     $(BIN)troffmt $(BIN)wdlen $(BIN)wheel $(BIN)xref
  735.  
  736. clean:
  737.     rm -f $(BIN)accent $(BIN)cfreq $(BIN)dict $(BIN)exclude $(BIN)format
  738.     rm -f $(BIN)freq $(BIN)kwal $(BIN)kwic $(BIN)lno $(BIN)med
  739.     rm -f $(BIN)maxwd $(BIN)pair $(BIN)pause $(BIN)revconc $(BIN)sfind
  740.     rm -f $(BIN)skel $(BIN)togrk $(BIN)tolpr $(BIN)tosel $(BIN)tprep
  741.     rm -f $(BIN)troffmt $(BIN)wdlen $(BIN)wheel $(BIN)xref
  742.     rm -f $(BIN)cedilla $(BIN)umlaut
  743.  
  744. $(BIN)accent: accent.c
  745.     cc accent.c -O -s -o $(BIN)accent
  746.     ln $(BIN)accent $(BIN)cedilla
  747.     ln $(BIN)accent $(BIN)umlaut
  748. $(BIN)cfreq: cfreq.c
  749.     cc cfreq.c -O -s -o $(BIN)cfreq
  750. $(BIN)dict: dict.c
  751.     cc dict.c -O -s -o $(BIN)dict
  752. $(BIN)exclude: exclude.c
  753.     cc exclude.c -O -s -o $(BIN)exclude
  754. $(BIN)format: format.c
  755.     cc format.c -O -s -o $(BIN)format
  756. $(BIN)freq: freq.c                # separate i&d space
  757.     cc freq.c -O -s -o $(BIN)freq
  758. $(BIN)kwal: kwal.c
  759.     cc kwal.c -O -s -o $(BIN)kwal
  760. $(BIN)kwic: kwic.c
  761.     cc kwic.c -O -s -o $(BIN)kwic
  762. $(BIN)lno: lno.c
  763.     cc lno.c -O -s -o $(BIN)lno
  764. $(BIN)med: med.c
  765.     cc med.c -O -s -o $(BIN)med -lcurses -ltermcap
  766. $(BIN)maxwd: maxwd.c
  767.     cc maxwd.c -O -s -o $(BIN)maxwd
  768. $(BIN)pair: pair.c
  769.     cc pair.c -O -s -o $(BIN)pair
  770. $(BIN)pause: pause.c
  771.     cc pause.c -O -s -o $(BIN)pause
  772. $(BIN)revconc: revconc.c
  773.     cc revconc.c -O -s -o $(BIN)revconc
  774. $(BIN)sfind: sfind.c
  775.     cc sfind.c -O -s -o $(BIN)sfind
  776. $(BIN)skel: skel.c
  777.     cc skel.c -O -s -o $(BIN)skel
  778. $(BIN)togrk: togrk.c
  779.     cc togrk.c -O -s -o $(BIN)togrk
  780. $(BIN)tolpr: tolpr.c
  781.     cc tolpr.c -O -s -o $(BIN)tolpr
  782. $(BIN)tosel: tosel.c
  783.     cc tosel.c -O -s -o $(BIN)tosel
  784. $(BIN)tprep: tprep.c
  785.     cc tprep.c -O -s -o $(BIN)tprep
  786. $(BIN)troffmt: troffmt.c
  787.     cc troffmt.c -O -s -o $(BIN)troffmt
  788. $(BIN)wdlen: wdlen.c
  789.     cc wdlen.c -O -s -o $(BIN)wdlen
  790. $(BIN)wheel: wheel.c
  791.     cc wheel.c -O -s -o $(BIN)wheel
  792. $(BIN)xref: xref.c
  793.     cc xref.c -O -s -o $(BIN)xref
  794. @@@ Fin de Makefile
  795. echo ORIGIN
  796. cat >ORIGIN <<'@@@ Fin de ORIGIN'
  797. Unix-From: sun!cairo!tut Thu Oct 10 20:50:28 1985
  798. Date: Thu, 10 Oct 85 14:31:16 PDT
  799. From: sun!cairo!tut (Bill Tuthill)
  800. Message-Id: <8510102131.AA08577@cairo.sun.uucp>
  801. To: gnu@l5.uucp
  802. Subject: Re: Hum tutorial
  803.  
  804. Date: Fri, 19 Jun 87 16:45:45 PDT
  805. From: sun!tut (Bill Tuthill)
  806. Message-Id: <8706192345.AA04406@cairo.sun.uucp>
  807. To: hoptoad!gnu
  808. Subject: Re:  Hum programs
  809.  
  810. It's fine with me if you submit Hum to Rich $alz for mod.sources.
  811. How did he get such a $trange letter in his la$t name?
  812. @@@ Fin de ORIGIN
  813. echo README
  814. cat >README <<'@@@ Fin de README'
  815.     Here is the source code for the "hum" concordance package.
  816. To compile all the programs, just type "make", and the programs will
  817. all be compiled, one by one, and put in "../bin".  You can change
  818. the destination bin if you like (for example, to "/usr/hum/bin") by
  819. changing the first line in the "Makefile".  All the programs here
  820. are self-contained, requiring no special libraries, and having no
  821. dependencies.  If you do extensive revising, especially on the longer
  822. programs, you will probably want to split them up, and change the
  823. "Makefile" accordingly.
  824.  
  825.     Several programs here are new and undocumented.  The "togrk"
  826. program employs Wes Walton's Greek transcription system to convert
  827. English letters to Greek character strings for "nroff/troff".  The
  828. "troffmt" program replaces "format" for the phototypesetter, and
  829. requires the macro package "../tmac/trmacs" to work correctly.
  830. It has not been fully debugged.
  831.  
  832.     The old "lemma" program was not good enough to distribute.
  833. It is currently in the process of revision so it can be used along
  834. with "dict".  Disk seeks and reads will replace sequential I/O. 
  835. If you need the new "lemma" program, contact Bill Tuthill for a
  836. software update.
  837. @@@ Fin de README
  838. echo accent.c
  839. cat >accent.c <<'@@@ Fin de accent.c'
  840. # include <stdio.h>            /* accent.c (rev3.7) */
  841.  
  842. char mode = 'a';        /* accent mode: a=file c=cedilla u=umlaut */
  843. char accfile[15] = "accfile";    /* file containing accent mark definitions */
  844.  
  845. usage()            /* print proper usage and exit */
  846. {
  847.     fprintf(stderr, "Usage: accent [-a accfile] [filename(s)]\t(rev3.7)\n");
  848.     fprintf(stderr, "\taccent mark definitions read from accfile\n");
  849.     exit(1);
  850. }
  851.  
  852. main(argc, argv)    /* user-controlled module for accent marks */
  853. int argc;
  854. char *argv[];
  855. {
  856.     FILE *fopen(), *fp;
  857.     int n, i;
  858.  
  859.     n = strlen(argv[0]);
  860.     /* umlaut */
  861.     if (argv[0][n-2] == 'u' && argv[0][n-1] == 't')
  862.         mode = 'u';
  863.     /* cedilla */
  864.     if (argv[0][n-2] == 'l' && argv[0][n-1] == 'a')
  865.         mode = 'c';
  866.     i = 1;
  867.     if (mode == 'a' && argv[i][0] == '-')
  868.     {
  869.         if (argv[i][1] == 'a' && argc >= 3)
  870.         {
  871.             strcpy(accfile, argv[++i]);
  872.             argc -= 2;
  873.             i++;
  874.         }
  875.         else
  876.             usage();
  877.     }
  878.     if (mode == 'a')
  879.         getacc(accfile);
  880.  
  881.     fp = stdin;
  882.     do {
  883.         if (argc > 1 && (fp = fopen(argv[i], "r")) == NULL)
  884.         {
  885.             fprintf(stderr, "%s: can't access file %s\n",
  886.                 argv[0], argv[i]);
  887.             exit(1);
  888.         }
  889.         else {
  890.             if (mode == 'a')
  891.                 accent(fp);
  892.             else /* c or u mode */
  893.                 ced_um(fp);
  894.             fclose(fp);
  895.         }
  896.     } while (++i < argc);
  897.     exit(0);
  898. }
  899.  
  900. char from[BUFSIZ/8];    /* accent marks placed in text */
  901. char into[BUFSIZ/8];    /* accent marks to be output */
  902.  
  903. getacc(file)        /* retrieve accent mark definitions */
  904. char *file;
  905. {
  906.     FILE *afp, *fopen();
  907.     char str[BUFSIZ/16];
  908.     register int n;
  909.  
  910.     if ((afp = fopen(file, "r")) == NULL)
  911.     {
  912.         fprintf(stderr, "can't find accent file: %s\n", file);
  913.         exit(1);
  914.     }
  915.     for (n = 0; fgets(str, BUFSIZ/16, afp) && n < BUFSIZ/8; n++)
  916.     {
  917.         if (strlen(str) == 4)
  918.         {
  919.             from[n] = str[0];
  920.             into[n] = str[2];
  921.         }
  922.         else {
  923.             fprintf(stderr,
  924.                 "syntax error in line %d of %s\n", n+1, file);
  925.             fprintf(stderr,
  926.                 "usage: <fromchar> <space> <intochar>\n");
  927.             exit(1);
  928.         }
  929.     }
  930.     from[n] = into[n] = NULL;
  931. }
  932.  
  933. accent(fp)        /* change accent marks into <BS><acc> */
  934. FILE *fp;
  935. {
  936.     register int c, n;
  937.  
  938.     while ((c = getc(fp)) != EOF)
  939.     {
  940.         n = position(from, c);
  941.         if (n >= 0)
  942.         {
  943.             putchar('\b');
  944.             putchar(into[n]);
  945.         }
  946.         else
  947.             putchar(c);
  948.     }
  949. }
  950.  
  951. ced_um(fp)        /* change + into cedilla: <BS>, or umlaut: <BS>" */
  952. FILE *fp;
  953. {
  954.     register int c;
  955.  
  956.     while ((c = getc(fp)) != EOF)
  957.     {
  958.         if (c == '+')
  959.         {
  960.             putchar('\b');
  961.             if (mode == 'c')
  962.                 putchar(',');
  963.             if (mode == 'u')
  964.                 putchar('"');
  965.         }
  966.         else
  967.             putchar(c);
  968.     }
  969. }
  970.  
  971. position(str, c)    /* return location of c in str, -1 if not */
  972. char str[], c;
  973. {
  974.     register int i;
  975.  
  976.     for (i = 0; str[i]; i++)
  977.         if (str[i] == c)
  978.             return(i);
  979.     return(-1);
  980. }
  981. @@@ Fin de accent.c
  982. echo cfreq.c
  983. cat >cfreq.c <<'@@@ Fin de cfreq.c'
  984. # include <stdio.h>            /* cfreq.c (rev 3.7) */
  985. # include <ctype.h>
  986.  
  987. char *chars[512] =
  988. {
  989.     "^@",    "^A",    "^B",    "^C",    "^D",    "^E",    "^F",    "^G",
  990.     "^H",    "^I",    "^J",    "^K",    "^L",    "^M",    "^N",    "^O",
  991.     "^P",    "^Q",    "^R",    "^S",    "^T",    "^U",    "^V",    "^W",
  992.     "^X",    "^Y",    "^Z",    "^[",    "^\\",    "^]",    "^^",    "^_",
  993.     " ",    "!",    "\"",    "#",    "$",    "%",    "&",    "'",
  994.     "(",    ")",    "*",    "+",    ",",    "-",    ".",    "/",    
  995.     "0",    "1",    "2",    "3",    "4",    "5",    "6",    "7",    
  996.     "8",    "9",    ":",    ";",    "<",    "=",    ">",    "?",    
  997.     "@",    "A",    "B",    "C",    "D",    "E",    "F",    "G",    
  998.     "H",    "I",    "J",    "K",    "L",    "M",    "N",    "O",    
  999.     "P",    "Q",    "R",    "S",    "T",    "U",    "V",    "W",    
  1000.     "X",    "Y",    "Z",    "[",    "\\",    "]",    "^",    "_",    
  1001.     "`",    "a",    "b",    "c",    "d",    "e",    "f",    "g",    
  1002.     "h",    "i",    "j",    "k",    "l",    "m",    "n",    "o",    
  1003.     "p",    "q",    "r",    "s",    "t",    "u",    "v",    "w",    
  1004.     "x",    "y",    "z",    "{",    "|",    "}",    "~",    "^?",    
  1005. };
  1006.  
  1007. struct tnode        /* binary tree for digraphs and count */
  1008. {
  1009.     char *word;
  1010.     int count;
  1011.     struct tnode *left;
  1012.     struct tnode *right;
  1013. };
  1014.  
  1015. long count[128];    /* count of individual characters */
  1016. long total;        /* total number of characters */
  1017. char printable = 0;    /* toggle printable chars option */
  1018. char ascii = 0;        /* toggle all ascii chars option */
  1019. char digraph = 0;    /* toggle digraph frequency count */
  1020. char nomap = 0;        /* turn off digraph mapping to lower case */
  1021.  
  1022. main(argc,argv)        /* count frequency of characters or digraphs */
  1023. int argc;
  1024. char *argv[];
  1025. {
  1026.     FILE *fopen(), *fp;
  1027.     struct tnode *tree(), *root;
  1028.     char dgraphs[3];
  1029.     int i;
  1030.  
  1031.     root = NULL;
  1032.     dgraphs[2] = NULL;
  1033.     if (argc == 1)
  1034.     {
  1035.         puts("Usage: cfreq [-p -a -d -m -] filename(s)\t(rev3.7)");
  1036.         puts("-p: list all printable characters (blank - '~')");
  1037.         puts("-a: list all ascii characters (null - delete)");
  1038.         puts("-d: count digraphs rather than single characters");
  1039.         puts("-m: disable mapping of digraphs to lower case");
  1040.         puts("- : read standard input instead of files");
  1041.         exit(1);
  1042.     }
  1043.     for (i = 1; i < argc; i++)
  1044.     {
  1045.         if (*argv[i] == '-')
  1046.             getflag(argv[i]);
  1047.         else if ((fp = fopen(argv[i], "r")) != NULL)
  1048.         {
  1049.             if (digraph)
  1050.                 while (dfreq(dgraphs, fp))
  1051.                     root = tree(root, dgraphs);
  1052.             else
  1053.                 cfreq(fp);
  1054.             fclose(fp);
  1055.         }
  1056.         else  /* cannot open file */
  1057.         {
  1058.             fprintf(stderr,
  1059.             "Cfreq cannot access the file: %s\n", argv[i]);
  1060.             exit(1);
  1061.         }
  1062.     }
  1063.     if (digraph)
  1064.     {
  1065.         printf("Digraph:  Freq:\n");
  1066.         treeprint(root);
  1067.     }
  1068.     else
  1069.         pr_results();
  1070.     exit(0);
  1071. }
  1072.  
  1073. getflag(f)        /* parses command line to set options */
  1074. char *f;
  1075. {
  1076.     struct tnode *tree(), *root;
  1077.     char dgraphs[3];
  1078.  
  1079.     f++;
  1080.     switch(*f)
  1081.     {
  1082.         case 'p':
  1083.             printable = 1;
  1084.             break;
  1085.         case 'a':
  1086.             ascii = 1;
  1087.             break;
  1088.         case 'd':
  1089.             digraph = 1;
  1090.             break;
  1091.         case 'm':
  1092.             nomap = 1;
  1093.             break;
  1094.         case NULL:
  1095.             root = NULL;
  1096.             dgraphs[2] = NULL;
  1097.             if (digraph)
  1098.             {
  1099.                 while (dfreq(dgraphs, stdin))
  1100.                     root = tree(root, dgraphs);
  1101.                 printf("Digraph:  Freq:\n");
  1102.                 treeprint(root);
  1103.                 exit(1);
  1104.             }
  1105.             else
  1106.                 cfreq(stdin);
  1107.             break;
  1108.         default:
  1109.             fprintf(stderr, "Invalid cfreq flag: -%s\n", f);
  1110.             exit(1);
  1111.             break;
  1112.     }
  1113. }
  1114.  
  1115. cfreq(fp)        /* run through files counting characters */
  1116. FILE *fp;
  1117. {
  1118.     register int c;
  1119.  
  1120.     while ((c = getc(fp)) != EOF)
  1121.     {
  1122.         count[(int)c]++;
  1123.         total++;
  1124.     }
  1125. }
  1126.  
  1127. pr_results()        /* print out table of character counts */
  1128. {
  1129.     int i;
  1130.  
  1131.     printf("Char:\t  Freq:\n");
  1132.     if (printable)
  1133.         for (i = 32; i < 127; i++)
  1134.             printf("%s\t%6ld\n", chars[i], count[i]);
  1135.     else if (ascii)
  1136.         for (i = 0; i < 128; i++)
  1137.             printf("%s\t%6ld\n", chars[i], count[i]);
  1138.     else
  1139.     {
  1140.         for (i = 65; i < 91; i++)
  1141.             printf("%s\t%6ld\n", chars[i], count[i]);
  1142.         for (i = 97; i < 123; i++)
  1143.             printf("%s\t%6ld\n", chars[i], count[i]);
  1144.     }
  1145.     printf("Total:\t%6ld\n", total);
  1146. }
  1147.  
  1148. dfreq(dgraphs,fp)    /* drives program through text by digraphs */
  1149. char dgraphs[];
  1150. FILE *fp;
  1151. {
  1152.     register int c;
  1153.  
  1154.     if ((c = getc(fp)) != EOF)
  1155.     {
  1156.         if (c == '\n' || c == '\t')
  1157.             dgraphs[0] = ' ';
  1158.         else if (!nomap && isupper(c))
  1159.             dgraphs[0] = tolower(c);
  1160.         else dgraphs[0] = c;
  1161.     }
  1162.     else return(0);
  1163.     if ((c = getc(fp)) != EOF)
  1164.     {
  1165.         if (c == '\n' || c == '\t')
  1166.             dgraphs[1] = ' ';
  1167.         else if (!nomap && isupper(c))
  1168.             dgraphs[1] = tolower(c);
  1169.         else dgraphs[1] = c;
  1170.     }
  1171.     else return(0);
  1172.     ungetc(c, fp);
  1173.     return(1);
  1174. }
  1175.  
  1176. struct tnode *tree(p, w)    /* build tree beginning at root */
  1177. struct tnode *p;
  1178. char *w;
  1179. {
  1180.     struct tnode *talloc();
  1181.     char *strsave();
  1182.     int cond;
  1183.  
  1184.     if (p == NULL)
  1185.     {
  1186.         p = talloc();
  1187.         p->word = strsave(w);
  1188.         p->count = 1;
  1189.         p->left = p->right = NULL;
  1190.     }
  1191.     else if ((cond = strcmp(w, p->word)) == 0)
  1192.         p->count++;
  1193.     else if (cond < 0)
  1194.         p->left = tree(p->left, w);
  1195.     else /* if cond > 0 */
  1196.         p->right = tree(p->right, w);
  1197.     return(p);
  1198. }
  1199.  
  1200. treeprint(p)        /* print out contents of binary tree */
  1201. struct tnode *p;
  1202. {
  1203.     if (p != NULL)
  1204.     {
  1205.         treeprint(p->left);
  1206.         printf("%s\t %5d\n", p->word, p->count);
  1207.         treeprint(p->right);
  1208.     }
  1209. }
  1210.  
  1211. struct tnode *talloc()        /* core allocator for tree */
  1212. {
  1213.     struct tnode *p;
  1214.     char *malloc();
  1215.  
  1216.     if ((p = ((struct tnode *)malloc(sizeof(struct tnode)))) != NULL)
  1217.         ;  /* will return */
  1218.     else /* if (p == NULL) */
  1219.         overflow();
  1220.     return(p);
  1221. }
  1222.  
  1223. char *strsave(s)    /* allocate space for string of text */
  1224. char *s;
  1225. {
  1226.     char *p, *malloc(), *strcpy();
  1227.  
  1228.     if ((p = malloc((unsigned)(strlen(s)+1))) != NULL)
  1229.         strcpy(p, s);
  1230.     else /* if (p == NULL) */
  1231.         overflow();
  1232.     return(p);
  1233. }
  1234.  
  1235. overflow()        /* exit gracefully in case of core overflow */
  1236. {
  1237.     fprintf(stderr,
  1238.     "Cfreq: no more core available (maximum on PDP is 64K bytes).\n");
  1239.     exit(1);
  1240. }
  1241. @@@ Fin de cfreq.c
  1242. echo dict.c
  1243. cat >dict.c <<'@@@ Fin de dict.c'
  1244. # include <stdio.h>            /* dict.c (rev3.7) */
  1245.  
  1246.  
  1247. main(argc, argv)    /* split file into dictionary sections */
  1248. int argc;
  1249. char *argv[];
  1250. {
  1251.     FILE *fp, *fopen();
  1252.     char *strcpy(), root[15];    /* root name of outfile */
  1253.  
  1254.     if (argc == 1 || argc > 3)
  1255.     {
  1256.         puts("Usage: dict [-]filename [outroot]\t\t(rev3.7)");
  1257.         puts("- : read standard input rather than file");
  1258.         exit(1);
  1259.     }
  1260.     if (argc == 2)
  1261.         *root = 'X';
  1262.     if (argc == 3)
  1263.         strcpy(root, argv[2]);
  1264.  
  1265.     if (argv[1][0] == '-' && argv[1][1] == NULL)
  1266.         dict(stdin, root);
  1267.     else if ((fp = fopen(argv[1], "r")) != NULL)
  1268.     {
  1269.         dict(fp, root);
  1270.         fclose(fp);
  1271.     }
  1272.     else  /* can't open input file */
  1273.     {
  1274.         fprintf(stderr,
  1275.         "Dict cannot access the file: %s\n", argv[1]);
  1276.         exit(1);
  1277.     }
  1278.     exit(0);
  1279. }
  1280.  
  1281. dict(fp, root)        /* write each letter to separate file */
  1282. FILE *fp;
  1283. char *root;
  1284. {
  1285.     FILE *sp, *fopen();
  1286.     char s[BUFSIZ], fname[20], ch = 'A';
  1287.     int first = 1, len;
  1288.  
  1289.     sp = NULL;
  1290.     while (fgets(s, BUFSIZ, fp))
  1291.     {
  1292.         if (s[0] != ch)        /* new letter found */
  1293.         {
  1294.             ch = s[0];
  1295.             first = 1;
  1296.         }
  1297.         if (!first && s[0] == ch)    /* same letter */
  1298.         {
  1299.             fputs(s, sp);
  1300.             if (ferror(sp))
  1301.             {
  1302.                 perror(fname);
  1303.                 exit(1);
  1304.             }
  1305.         }
  1306.         if (first)    /* first encounter with this letter */
  1307.         {
  1308.             strcpy(fname, root);    /* derive filename */
  1309.             len = strlen(fname);
  1310.             fname[len] = ch;
  1311.             fname[++len] = NULL;
  1312.  
  1313.             if (sp != NULL)
  1314.                 fclose(sp);
  1315.             if ((sp = fopen(fname, "a")) == NULL)
  1316.             {
  1317.                 perror(fname);
  1318.                 exit(1);
  1319.             }
  1320.             fputs(s, sp);
  1321.             if (ferror(sp))
  1322.             {
  1323.                 perror(fname);
  1324.                 exit(1);
  1325.             }
  1326.             first = 0;
  1327.         }
  1328.     }
  1329. }
  1330. @@@ Fin de dict.c
  1331. echo exclude.c
  1332. cat >exclude.c <<'@@@ Fin de exclude.c'
  1333. # include <stdio.h>            /* exclude.c (rev3.7) */
  1334. # include <ctype.h>
  1335. # define MXWDS 500
  1336.  
  1337. char mode = 'i';    /* default is ignore mode, not only mode */
  1338.  
  1339. usage()            /* print proper usage and exit */
  1340. {
  1341.     puts("Usage: exclude [-i -o exclfile] [filename(s)]\t(rev3.7)");
  1342.     puts("-i: exclfile contains words to be ignored, one per line");
  1343.     puts("-o: exclfile has only words to be printed, one per line");
  1344.     puts("With no options, excluded words should be in \"exclfile\".");
  1345.     exit(1);
  1346. }
  1347.  
  1348. main(argc, argv)    /* exclude common words from concordance */
  1349. int argc;
  1350. char *argv[];
  1351. {
  1352.     FILE *fp, *fopen();
  1353.     int i = 1;
  1354.  
  1355.     if (argc == 1)
  1356.     {
  1357.         rdexclf("exclfile");
  1358.         exclude(stdin);
  1359.         exit(0);
  1360.     }
  1361.     if (argv[1][0] == '-')
  1362.     {
  1363.         if (argc == 2)
  1364.             usage();
  1365.         if (argv[1][1] == 'i')
  1366.             mode = 'i';
  1367.         else if (argv[1][1] == 'o')
  1368.             mode = 'o';
  1369.         else  /* bad flag */
  1370.             usage();
  1371.         rdexclf(argv[2]);
  1372.         if (argc == 3)
  1373.             exclude(stdin);
  1374.         i = 3;
  1375.     }
  1376.     else  /* no options used */
  1377.         rdexclf("exclfile");
  1378.     for (; i < argc; i++)
  1379.     {
  1380.         if ((fp = fopen(argv[i], "r")) != NULL)
  1381.         {
  1382.             exclude(fp);
  1383.             fclose(fp);
  1384.         }
  1385.         else  /* can't open input file */
  1386.         {
  1387.             fprintf(stderr,
  1388.             "Exclude cannot access the file: %s\n", argv[i]);
  1389.             continue;
  1390.         }
  1391.     }
  1392.     exit(0);
  1393. }
  1394.  
  1395. char *wdptr[MXWDS];    /* array of pointers to excluded words */
  1396. int nwds = 0;        /* the number of excluded words in core */
  1397.  
  1398. rdexclf(fname)        /* load structure with words from exclfile */
  1399. char fname[];
  1400. {
  1401.     FILE *efp, *fopen();
  1402.     char wd[512], *p, *malloc(), *strcpy();
  1403.  
  1404.     if ((efp = fopen(fname, "r")) == NULL)
  1405.     {
  1406.         fprintf(stderr,
  1407.         "Cannot access exclude file: %s\n", fname);
  1408.         usage();
  1409.         exit(1);
  1410.     }
  1411.     while (fgets(wd, 512, efp))
  1412.     {
  1413.         if (nwds >= MXWDS)
  1414.         {
  1415.             fprintf(stderr,
  1416.             "Maximum of %d exclude words allowed.\n", MXWDS);
  1417.             exit(1);
  1418.         }
  1419.         else if ((p = malloc((unsigned)(strlen(wd)+1))) == NULL)
  1420.         {
  1421.             fprintf(stderr,
  1422.             "Exclude: no more space left in core.\n");
  1423.             exit(1);
  1424.         }
  1425.         else  /* everything is OK */
  1426.         {
  1427.             strcpy(p, wd);
  1428.             wdptr[nwds++] = p;
  1429.         }
  1430.     }
  1431.     return;
  1432. }
  1433.  
  1434. exclude(fp)        /* filter out excluded words, i or o mode */
  1435. FILE *fp;
  1436. {
  1437.     char s[512], word[512];
  1438.  
  1439.     while (fgets(s, 512, fp))
  1440.     {
  1441.         if (firstword(s, word) == 0)
  1442.             continue;
  1443.         if (mode == 'i')
  1444.         {
  1445.             if (!inlist(word))
  1446.                 fputs(s, stdout);
  1447.         }
  1448.         if (mode == 'o')
  1449.         {
  1450.             if (inlist(word))
  1451.                 fputs(s, stdout);
  1452.         }
  1453.     }
  1454. }
  1455.  
  1456. firstword(s, wd)    /* return first word of string s */
  1457. char s[], *wd;
  1458. {
  1459.     int i = 0;
  1460.  
  1461.     if (isspace(s[i]))
  1462.         return(0);
  1463.     while (!isspace(s[i]))
  1464.         *wd++ = s[i++];
  1465.     *wd++ = '\n';
  1466.     *wd = NULL;
  1467.     return(1);
  1468. }
  1469.  
  1470. inlist(word)        /* check to see if word is in exclude list */
  1471. char word[];
  1472. {
  1473.     int i;
  1474.  
  1475.     for (i = 0; i < nwds; i++)
  1476.     {
  1477.         if (strcmp(word, wdptr[i]) == 0)
  1478.             return(1);
  1479.     }
  1480.     return(0);
  1481. }
  1482. @@@ Fin de exclude.c
  1483. echo format.c
  1484. cat >format.c <<'@@@ Fin de format.c'
  1485. # include <stdio.h>            /* format.c (rev3.7) */
  1486. # include <ctype.h>
  1487. # include <signal.h>
  1488.  
  1489. char *tempfile;        /* to store overflow while counting */
  1490. int nomap = 0;        /* toggle for mapping keyword to lcase */
  1491. int nocnt = 0;        /* toggle for counting keyword */
  1492. int nokwd = 0;        /* toggle for suppressing keyword */
  1493.  
  1494. usage()            /* print proper usage and exit */
  1495. {
  1496.     puts("Usage: format [-mck] [filename(s)]\t\t(rev3.7)");
  1497.     puts("-m: keywords not mapped from lower to upper case");
  1498.     puts("-c: suppress counting of keyword frequency");
  1499.     puts("-k: entirely suppress printing of keyword");
  1500.     exit(1);
  1501. }
  1502.  
  1503. main(argc, argv)    /* make keyword headings with count */
  1504. int argc;
  1505. char *argv[];
  1506. {
  1507.     FILE *fopen(), *fp;
  1508.     int i, j, onintr();
  1509.     char *mktemp();
  1510.  
  1511.     if (signal(SIGINT, SIG_IGN) != SIG_IGN)
  1512.         signal(SIGINT, onintr);
  1513.  
  1514.     tempfile = "/tmp/FmtXXXXX";
  1515.     mktemp(tempfile);
  1516.  
  1517.     for (i = 1; *argv[i] == '-'; i++)
  1518.     {
  1519.         for (j = 1; argv[i][j] != NULL; j++)
  1520.         {
  1521.             if (argv[i][j] == 'm')
  1522.                 nomap = 1;
  1523.             else if (argv[i][j] == 'c')
  1524.                 nocnt = 1;
  1525.             else if (argv[i][j] == 'k')
  1526.                 nokwd = 1;
  1527.             else  /* bad option */
  1528.             {
  1529.                 fprintf(stderr,
  1530.                 "Illegal format flag: -%c\n", argv[i][j]);
  1531.                 usage();
  1532.             }
  1533.         }
  1534.     }
  1535.     if (i == argc)
  1536.     {
  1537.         if (nokwd)
  1538.             rmkwds(stdin);
  1539.         else if (nocnt)
  1540.             ffmt(stdin);
  1541.         else
  1542.             format(stdin);
  1543.     }
  1544.     for (; i < argc; i++)
  1545.     {
  1546.         if ((fp = fopen(argv[i], "r")) != NULL)
  1547.         {
  1548.             if (nokwd)
  1549.                 rmkwds(fp);
  1550.             else if (nocnt)
  1551.                 ffmt(fp);
  1552.             else
  1553.                 format(fp);
  1554.             fclose(fp);
  1555.         }
  1556.         else  /* attempt to open file failed */
  1557.         {
  1558.             fprintf(stderr,
  1559.             "Format cannot access the file: %s\n", argv[i]);
  1560.             continue;
  1561.         }
  1562.     }
  1563.     unlink(tempfile);
  1564.     exit(0);
  1565. }
  1566.  
  1567. char buff[BUFSIZ*8];    /* tempfile buffer for storing contexts */
  1568. int bufflen;        /* total length of contexts in buffer */
  1569. int fulltf = 0;        /* does the tempfile contain something? */
  1570. FILE *tf = NULL;    /* file pointer for tempfile routines */
  1571.  
  1572. format(fp)          /* print keyword and count only if different */
  1573. FILE *fp;
  1574. {
  1575.     char s[BUFSIZ], okw[BUFSIZ/2], nkw[BUFSIZ/2], cntxt[BUFSIZ];
  1576.     char *sp, *kwp, *cxp, *strcpy();
  1577.     int kwfreq = 0;
  1578.  
  1579.     strcpy(okw,"~~~~~");    /* make sure 1st keyword is printed */
  1580.  
  1581.     while (fgets(s, BUFSIZ, fp))
  1582.     {
  1583.         for (sp = s, kwp = nkw; *sp && *sp != '|'; sp++, kwp++)
  1584.         {
  1585.             if (!nomap && islower(*sp))
  1586.                 *kwp = toupper(*sp);
  1587.             else
  1588.                 *kwp = *sp;
  1589.         }
  1590.         *kwp = NULL;
  1591.  
  1592.         for (++sp, cxp = cntxt; *sp && *sp != '\n'; sp++, cxp++)
  1593.         {
  1594.             if (*sp == '|') {
  1595.                 *cxp = ' '; *++cxp = ' '; *++cxp = ' ';
  1596.             } else
  1597.                 *cxp = *sp;
  1598.         }
  1599.         *cxp = '\n';
  1600.         *++cxp = NULL;
  1601.  
  1602.         if (strcmp(nkw, okw) != 0)  /* kwds different */
  1603.         {
  1604.             if (kwfreq != 0)
  1605.             {
  1606.                 getbuff(kwfreq);
  1607.                 putchar('\n');
  1608.             }
  1609.             *buff = NULL;
  1610.             bufflen = 0;
  1611.             fputs(nkw, stdout);
  1612.             putbuff(cntxt);
  1613.             kwfreq = 1;
  1614.         }
  1615.         else  /* if keywords are the same */
  1616.         {
  1617.             putbuff(cntxt);
  1618.             kwfreq++;
  1619.         }
  1620.         strcpy(okw, nkw);
  1621.     }
  1622.     getbuff(kwfreq);
  1623. }
  1624.  
  1625. putbuff(cntxt)        /* cache routine to buffer tempfile */
  1626. char cntxt[];
  1627. {
  1628.     char *strcat();
  1629.  
  1630.     if (!fulltf)
  1631.     {
  1632.         bufflen += strlen(cntxt);
  1633.         if (bufflen < BUFSIZ*8)
  1634.             strcat(buff, cntxt);
  1635.         else {
  1636.             fulltf = 1;
  1637.             if ((tf = fopen(tempfile, "w")) == NULL)
  1638.                 perror(tempfile);
  1639.             fputs(buff, tf);
  1640.             *buff = NULL;
  1641.             bufflen = 0;
  1642.         }
  1643.     }
  1644.     else  /* fulltf */
  1645.         fputs(cntxt, tf);
  1646. }
  1647.  
  1648. getbuff(kwfreq)        /* print frequency and context buffer */
  1649. int kwfreq;
  1650. {
  1651.     char str[BUFSIZ];
  1652.  
  1653.     printf("(%d)\n", kwfreq);
  1654.     if (!fulltf)
  1655.         fputs(buff, stdout);
  1656.     else
  1657.     {
  1658.         fclose(tf);
  1659.         if ((tf = fopen(tempfile, "r")) == NULL)
  1660.             perror(tempfile);
  1661.         while (fgets(str, BUFSIZ, tf))
  1662.             fputs(str, stdout);
  1663.         fclose(tf);
  1664.         fulltf = 0;
  1665.     }
  1666. }
  1667.  
  1668. int onintr()        /* remove tempfile in case of interrupt */
  1669. {
  1670.     fprintf(stderr, "\nInterrupt\n");
  1671.     unlink(tempfile);
  1672.     exit(1);
  1673. }
  1674.  
  1675. ffmt(fp)          /* if different, print keyword without count */
  1676. FILE *fp;
  1677. {
  1678.     char s[BUFSIZ], okw[BUFSIZ/2], nkw[BUFSIZ/2], cntxt[BUFSIZ];
  1679.     char *sp, *kwp, *cxp, *strcpy();
  1680.  
  1681.     strcpy(okw,"~~~~~");    /* make sure 1st keyword is printed */
  1682.     while (fgets(s, BUFSIZ, fp))
  1683.     {
  1684.         for (sp = s, kwp = nkw; *sp && *sp != '|'; sp++, kwp++)
  1685.         {
  1686.             if (!nomap && islower(*sp))
  1687.                 *kwp = toupper(*sp);
  1688.             else
  1689.                 *kwp = *sp;
  1690.         }
  1691.         *kwp = NULL;
  1692.  
  1693.         for (++sp, cxp = cntxt; *sp && *sp != '\n'; sp++, cxp++)
  1694.         {
  1695.             if (*sp == '|') {
  1696.                 *cxp = ' '; *++cxp = ' '; *++cxp = ' ';
  1697.             } else
  1698.                 *cxp = *sp;
  1699.         }
  1700.         *cxp = '\n';
  1701.         *++cxp = NULL;
  1702.  
  1703.         if (strcmp(nkw, okw) != 0)  /* kwds different */
  1704.             printf("\n%s\n %s", nkw, cntxt);
  1705.         else  /* if keywords are the same */
  1706.             printf(" %s", cntxt);
  1707.         strcpy(okw, nkw);
  1708.     }
  1709. }
  1710.  
  1711. rmkwds(fp)        /* completely suppress printing of keyword */
  1712. FILE *fp;
  1713. {
  1714.     char s[BUFSIZ], *sp;
  1715.  
  1716.     while (fgets(s, BUFSIZ, fp))
  1717.     {
  1718.         for (sp = s; *sp && *sp != '|'; sp++)
  1719.             ;
  1720.         for (; *sp; sp++)
  1721.         {
  1722.             if (*sp == '|')
  1723.                 printf("   ");
  1724.             else
  1725.                 putchar(*sp);
  1726.         }
  1727.     }
  1728. }
  1729. @@@ Fin de format.c
  1730. echo freq.c
  1731. cat >freq.c <<'@@@ Fin de freq.c'
  1732. # include <stdio.h>            /* freq.c (rev3.7) */
  1733. # include <ctype.h>
  1734.  
  1735. struct tnode        /* binary tree for word and count */
  1736. {
  1737.     char *word;
  1738.     int count;
  1739.     struct tnode *left;
  1740.     struct tnode *right;
  1741. };
  1742.  
  1743. char punctuation[BUFSIZ] = ",.;:-?!\"()[]{}" ;
  1744.  
  1745. long int total = 0;        /* total number of words */
  1746. long int different = 0;        /* number of different words */
  1747. char numfreq = 0;        /* toggle for numerical freq */
  1748. char nomap = 0;            /* do not map to lower case */
  1749.  
  1750. usage()            /* print proper usage and exit */
  1751. {
  1752.     puts("Usage: freq [-n -m -dF -] filename(s)\t\t(rev3.7)");
  1753.     puts("-n: list words in numerical order of frequency");
  1754.     puts("-m: disable mapping of words to lower case");
  1755.     puts("-d: define punctuation set according to file F");
  1756.     puts("- : read standard input instead of files");
  1757.     exit(1);
  1758. }
  1759.  
  1760. main(argc, argv)    /* tabulate word frequencies of a text */
  1761. int argc;
  1762. char *argv[];
  1763. {
  1764.     FILE *fopen(), *fp;
  1765.     struct tnode *root, *tree();
  1766.     char word[BUFSIZ];
  1767.     int i;
  1768.  
  1769.     if (argc == 1)
  1770.         usage();
  1771.     root = NULL;            /* initialize tree */
  1772.     for (i = 1; i < argc; i++)
  1773.     {
  1774.         if (*argv[i] == '-')
  1775.             getflag(argv[i]);
  1776.         else if ((fp = fopen(argv[i], "r")) != NULL)
  1777.         {
  1778.             while (getword(word, fp))
  1779.             {
  1780.                 ++total;
  1781.                 root = tree(root, word);
  1782.             }
  1783.             fclose(fp);
  1784.         }
  1785.         else  /* attempt to open file failed */
  1786.         {
  1787.             fprintf(stderr,
  1788.             "Freq cannot access the file: %s\n", argv[i]);
  1789.             exit(1);
  1790.         }
  1791.     }
  1792.     if (numfreq)            /* print results */
  1793.         treesort(root);
  1794.     else
  1795.         treeprint(root, stdout);
  1796.  
  1797.     printf("------------------------------\n");
  1798.     printf("%5ld  Total number of words\n", total);
  1799.     printf("%5ld  Different words used\n", different);
  1800.     exit(0);
  1801. }
  1802.  
  1803. getflag(f)        /* parses command line to set options */
  1804. char *f;
  1805. {
  1806.     char *pfile, word[BUFSIZ];
  1807.     struct tnode *root, *tree();
  1808.  
  1809.     f++;
  1810.     switch(*f++)
  1811.     {
  1812.         case 'n':
  1813.             numfreq = 1;
  1814.             break;
  1815.         case 'm':
  1816.             nomap = 1;
  1817.             break;
  1818.         case 'd':
  1819.             pfile = f;
  1820.             getpunct(pfile);
  1821.             break;
  1822.         case NULL:
  1823.             root = NULL;
  1824.             while (getword(word, stdin))
  1825.             {
  1826.                 ++total;
  1827.                 root = tree(root, word);
  1828.             }
  1829.             if (numfreq)
  1830.                 treesort(root);
  1831.             else
  1832.                 treeprint(root, stdout);
  1833.             break;
  1834.         default:
  1835.             fprintf(stderr,
  1836.             "Invalid freq flag: -%s\n", --f);
  1837.             exit(1);
  1838.             break;
  1839.     }
  1840. }
  1841.  
  1842. getpunct(pfile)        /* read user's punctuation from pfile */
  1843. char *pfile;
  1844. {
  1845.     FILE *pfp, *fopen();
  1846.     char s[BUFSIZ], *strcpy();
  1847.  
  1848.     if ((pfp = fopen(pfile, "r")) == NULL)
  1849.     {
  1850.         fprintf(stderr,
  1851.         "Freq cannot access Pfile: %s\n", pfile);
  1852.         exit(1);
  1853.     }
  1854.     else
  1855.         while (fgets(s, BUFSIZ, pfp))
  1856.             strcpy(punctuation, s);
  1857. }
  1858.  
  1859. getword(word, fp)    /* drives program through text word by word */
  1860. char word[];
  1861. FILE *fp;
  1862. {
  1863.     while ((*word = getc(fp)) && isskip(*word) && *word != EOF)
  1864.         ;
  1865.     if (*word == EOF)
  1866.         return(0);
  1867.     if (!nomap && isupper(*word))
  1868.         *word = tolower(*word);
  1869.  
  1870.     while ((*++word = getc(fp)) && !isskip(*word) && *word !=EOF)
  1871.     {
  1872.         if (!nomap && isupper(*word))
  1873.             *word = tolower(*word);
  1874.     }
  1875.     *word = NULL;
  1876.     return(1);
  1877. }
  1878.  
  1879. isskip(c)        /* function to evaluate punctuation */
  1880. char c;
  1881. {
  1882.     char *ptr;
  1883.  
  1884.     if (isspace(c))
  1885.         return(1);
  1886.     for (ptr = punctuation; *ptr != c && *ptr != NULL; ptr++)
  1887.         ;
  1888.     if (*ptr == NULL)
  1889.         return(0);
  1890.     else
  1891.         return(1);
  1892. }
  1893.  
  1894. struct tnode *tree(p, w)    /* build tree beginning at root */
  1895. struct tnode *p;
  1896. char *w;
  1897. {
  1898.     struct tnode *talloc();
  1899.     char *strsave();
  1900.     int cond;
  1901.  
  1902.     if (p == NULL)
  1903.     {
  1904.         p = talloc();
  1905.         p->word = strsave(w);
  1906.         p->count = 1;
  1907.         p->left = p->right = NULL;
  1908.     }
  1909.     else if ((cond = strcmp(w, p->word)) == 0)
  1910.         p->count++;
  1911.     else if (cond < 0)
  1912.         p->left = tree(p->left, w);
  1913.     else /* if cond > 0 */
  1914.         p->right = tree(p->right, w);
  1915.     return(p);
  1916. }
  1917.  
  1918. treesort(p)        /* sort contents of binary tree and print */
  1919. struct tnode *p;
  1920. {
  1921.     FILE *pfp, *popen();
  1922.  
  1923.     pfp = popen("sort +0rn -1 +1", "w");
  1924.     if (p != NULL)
  1925.         treeprint(p, pfp);
  1926.     pclose(pfp);
  1927. }
  1928.  
  1929. treeprint(p, fp)    /* write tree onto fp file stream */
  1930. struct tnode *p;
  1931. FILE *fp;
  1932. {
  1933.     if (p != NULL)
  1934.     {
  1935.         treeprint(p->left, fp);
  1936.         fprintf(fp, "%5d  %s\n", p->count, p->word);
  1937.         ++different;
  1938.         treeprint(p->right, fp);
  1939.     }
  1940. }
  1941.  
  1942. struct tnode *talloc()        /* core allocator for tree */
  1943. {
  1944.     struct tnode *p;
  1945.     char *malloc();
  1946.  
  1947.     if ((p = ((struct tnode *)malloc(sizeof(struct tnode)))) != NULL)
  1948.         ;  /* will return */
  1949.     else /* if (p == NULL) */
  1950.         overflow();
  1951.     return(p);
  1952. }
  1953.  
  1954. char *strsave(s)    /* allocate space for string of text */
  1955. char *s;
  1956. {
  1957.     char *p, *malloc();
  1958.  
  1959.     if ((p = malloc((unsigned)(strlen(s)+1))) != NULL)
  1960.         strcpy(p, s);
  1961.     else /* if (p == NULL) */
  1962.         overflow();
  1963.     return(p);
  1964. }
  1965.  
  1966. overflow()        /* exit gracefully in case of core overflow */
  1967. {
  1968.     fprintf(stderr,
  1969.     "Freq: no more core available (maximum on PDP 11/70 is 64K bytes).\n");
  1970.     fprintf(stderr,
  1971.     "You might try: dissolve filename(s) | sort | uniq -c\n");
  1972.     exit(1);
  1973. }
  1974. @@@ Fin de freq.c
  1975. exit 0
  1976.