-- The Program Fog.
-- Fog measures the reading complexity of English text.
-- The text is taken from the composition window.
-- The results of the analysis are given in the composition window.

-- Documentation on [Ivy]<McKeeman>FogReport.press.
-- Last edited by McKeeman on December 23, 1980 9:32 AM
-- Last edited by Brotz on December 12, 1980 2:23 PM
-- Version for Laurel RUN command.
-- Author: W.M. McKeeman.
-- Consultant: J.J. Horning.

DIRECTORY
Ascii,
FogDefs;
FogImpl:
PROGRAM IMPORTS FogDefs EXPORTS FogDefs=
{
OPEN FogDefs;
-- The parameters to the fog index computation.
Cw:LONG INTEGER = 20;
Cs:LONG INTEGER = 1;
Cp:LONG INTEGER = 2;
cw:LONG INTEGER = 5;
cs:LONG INTEGER = 3;
cp:LONG INTEGER = 2;

-- The counters used to produce the fog analysis.
TotalWords:LONG INTEGER ← 0;
WordPenalties:LONG INTEGER ← 0;
TotalSentences:LONG INTEGER ← 0; -- Carried x100 because of no REAL
SentencePenalties:LONG INTEGER ← 0;
TotalParagraphs:LONG INTEGER ← 0;
ParagraphPenalties:LONG INTEGER ← 0; -- Carried x100 because of no REAL
ReportParagraphs:LONG INTEGER ← 0;
TotalFog:LONG INTEGER ← 0;
RecordFog:LONG INTEGER ← 0;
Reports:LONG INTEGER ← 0;

ComputeAndReportStatistics
:
PROCEDURE =
{
Index: LONG INTEGER;
Fogp, Fogs, Fogw: LONG INTEGER;
Fogp ← Cp*ParagraphPenalties/100/TotalParagraphs;
Fogs ← Cs*SentencePenalties*100/TotalSentences;
Fogw ← (Cw*WordPenalties)/TotalWords;

Index ← (Fogp+Fogs+Fogw)/3;

ReportStatistics[Index, Fogp, Fogs, Fogw];

Reports ← Reports + 1;
ReportParagraphs ← ReportParagraphs + TotalParagraphs;
TotalParagraphs ← TotalWords ← 0;
TotalSentences ← 0;
ParagraphPenalties ← 0;
SentencePenalties ← WordPenalties ← 0;
IF Index > RecordFog THEN RecordFog ← Index;
TotalFog ← TotalFog + Index;
};

UpperCaseLetter:
PROCEDURE[ch: CHARACTER] RETURNS[BOOLEAN] =
{
RETURN[’A<=ch AND ch<=’Z]
};
Letter:
PROCEDURE[ch: CHARACTER] RETURNS[BOOLEAN] =
{
RETURN[(’a<=ch AND ch<=’z) OR (’A<=ch AND ch<=’Z)]
};

Digit
:
PROCEDURE[ch: CHARACTER] RETURNS[BOOLEAN] =
{
RETURN[’0<=ch AND ch<=’9]
};

Invisible:
PROCEDURE[ch: CHARACTER] RETURNS[BOOLEAN] =
{
RETURN[ch=Ascii.SP OR ch=Ascii.NUL OR ch=Ascii.DEL OR ch=Ascii.DEL
OR ch=Ascii.CR OR ch=Ascii.FF OR ch=Ascii.LF OR ch=Ascii.ControlZ];
};

Bracket:
PROCEDURE[ch: CHARACTER] RETURNS[BOOLEAN] =
{
RETURN[ch = ’" OR ch = ’’ OR ch = ’[ OR ch = ’] OR ch = ’( OR ch = ’)
OR ch = ’{ OR ch = ’}];
};

SentenceTerminator:
PROCEDURE RETURNS[BOOLEAN] =
{
RETURN[(Ch[0]=’. OR Ch[0]=’! OR Ch[0]=’? OR Ch[0]=’; OR Ch[0]=’:)
AND (Invisible[Ch[1]] OR Bracket[Ch[1]])];
};

ParagraphTerminator
:
PROCEDURE RETURNS[BOOLEAN] =
{ -- True when paragraph termination is detected.
IF Ch[0]=Ascii.CR AND Invisible[Ch[1]] THEN RETURN[TRUE];
IF Ch[0]=Ascii.ControlZ THEN RETURN[TRUE];
IF Ch[0]=Ascii.FF THEN RETURN[TRUE];
IF Ch[0]=Ascii.LF AND Invisible[Ch[1]] THEN RETURN[TRUE];
RETURN[FALSE];
};

DiscardNonPeriod:
PROCEDURE =
{ -- Discard ’. if it is not a sentence terminator.
-- Entered only on Ch[0] = ’.
IF Bracket[Ch[1]] THEN RETURN
ELSE IF ~Invisible[Ch[1]] THEN StepInputOutput[]
ELSE IF Ch[1] = Ascii.SP AND Bracket[Ch[2]] THEN RETURN
ELSE IF Ch[1] = Ascii.SP AND ~Invisible[Ch[2]] THEN StepInputOutput[];
};
ProcessWord:
PROCEDURE[FirstWord: BOOLEAN] =
{ -- Count overlength penalties.
-- Entered only on Letter[Ch[0]].
Letters: INTEGER ← 0;
-- Assume capitalization indicates a proper name except for first word of sentence.
Proper: BOOLEAN ← UpperCaseLetter[Ch[0]] AND ~FirstWord;

WHILE Letter[Ch[0]] OR Digit[Ch[0]] DO
StepInputOutput[];
Letters ← Letters+1;
ENDLOOP;
-- Check for abbreviations.
IF Ch[0]=’. THEN DiscardNonPeriod[];
-- Shorter than cw is free.
IF ~Proper AND Letters>cw THEN WordPenalties ← WordPenalties + Letters-cw;
};
ProcessNumber:
PROCEDURE =
{ -- Process numbers including decimal points.
-- Entered only on Digit[Ch[0]] or Ch[0]="." .
WHILE Digit[Ch[0]] DO StepInputOutput[] ENDLOOP;
IF Ch[0]=’. AND Digit[Ch[1]] THEN
{ -- Process rest of number.
StepInputOutput[];
WHILE Digit[Ch[0]] DO StepInputOutput[] ENDLOOP;
}
ELSE IF Ch[0]=’. THEN DiscardNonPeriod[];
};
ProcessJunkBetweenTokens:
PROCEDURE =
{
DO
IF ParagraphTerminator[] THEN EXIT;
IF SentenceTerminator[] THEN EXIT;
IF Letter[Ch[0]] THEN EXIT;
IF Digit[Ch[0]] THEN EXIT;
StepInputOutput[];
ENDLOOP;
};

ProcessSentence:
PROCEDURE[FirstWord: BOOLEAN] RETURNS[Weight: LONG INTEGER] =
{ -- Entered only on Letter[Ch[0]] or Digit[Ch[0]].
Words: LONG INTEGER ← 0;
ProcessWord[FirstWord];
DO
Words ← Words + 1;
ProcessJunkBetweenTokens[];
IF ParagraphTerminator[] THEN EXIT
ELSE IF SentenceTerminator[] THEN EXIT
ELSE IF Digit[Ch[0]] THEN ProcessNumber[]
ELSE ProcessWord[FALSE];
ENDLOOP;
-- Shorter than cs is free.
IF Words>cs THEN SentencePenalties ← SentencePenalties + Words - cs;
TotalWords ← TotalWords + Words;
-- Keep weighted count of sentence structure.
IF Ch[0]=’: THEN Weight ← 50
ELSE IF Ch[0]=’; THEN Weight ← 75
ELSE Weight ← 100;
};

ProcessJunkBetweenSentences:
PROCEDURE =
{
DO
IF ParagraphTerminator[] THEN EXIT;
IF Digit[Ch[0]] THEN EXIT;
IF Letter[Ch[0]] THEN EXIT;
StepInputOutput[];
ENDLOOP;
};

ProcessParagraph
:
PROCEDURE =
{ -- Enter only on Letter[Ch[0]] or Digit[Ch[0]].
Sentences: LONG INTEGER ← 0; -- Carried x100 because of no REAL
Weight: LONG INTEGER ← ProcessSentence[TRUE];
DO
Sentences ← Sentences + Weight;
ProcessJunkBetweenSentences[];
IF ParagraphTerminator[] THEN EXIT;
-- FirstWord=true signifies that capitalization may not reflect proper noun.
Weight ← ProcessSentence[Weight=100];
ENDLOOP;
-- Shorter than cp is free.
IF Sentences>100*cp THEN ParagraphPenalties ← ParagraphPenalties + Sentences - 100*cp;
TotalSentences ← TotalSentences + Sentences; -- Carried x100 because of no REAL
};
ProcessJunkBetweenParagraphs:
PROCEDURE =
{
DO
-- Discard Bravo trailer information.
IF Ch[0]=Ascii.ControlZ THEN WHILE Ch[0]#Ascii.CR DO StepInput[] ENDLOOP;
IF Letter[Ch[0]] THEN EXIT;
IF Digit[Ch[0]] THEN EXIT;
StepInputOutput[];
ENDLOOP;
};

ComputeFog:
PUBLIC PROCEDURE RETURNS[LONG INTEGER, LONG INTEGER, LONG INTEGER, LONG INTEGER] =
{
-- Catch end-of-stream in the middle of things.
ENABLE EoS => GOTO Escape;
DO
ProcessJunkBetweenParagraphs[];
ProcessParagraph[];
TotalParagraphs ← TotalParagraphs+1;
-- Lump headings and short paragraphs together.
IF TotalSentences>0 AND TotalWords>100 THEN ComputeAndReportStatistics[];
ENDLOOP;

EXITS Escape=>
{ -- tag end of file.
IF TotalSentences>0 AND TotalWords>0 THEN
{ -- Give a report if possible.
TotalParagraphs ← TotalParagraphs+1;
ComputeAndReportStatistics[];
};
RETURN[ReportParagraphs, RecordFog, TotalFog, Reports];
};
};
}.