-- RStatsImpl.Mesa, last edit January 18, 1983 1:20 pm
-- Pilot 6.0/ Mesa 7.0
--
-- to compute statistics
--
-- RStats -i Cedar3.1.DF
--
-- then
--   RStats -b	for the bcds
--					(counts bcds, sizes, etc.)
--   RStats -d Cedar3.1.DF		for the DF files
--					(counts DF files, produces DF-DF xref and file-DF xref)
--   RStats -e	for the bcd dependency analysis
--					(for bcd definitions file - DF file xref)
--   RStats -f	for the bcd dependency analysis
--					(where Special files (from a list) are looked for, e.g. Exec)
--   RStats -r	for the source files (broken down by every subdirectory)
--					(source file totals)
--   RStats -s	for the source files
--					(source file totals)
--   RStats -t	for totals on # and size of files
--					(overall totals)

-- alternatively
--	RStats -a Cedar3.1.DF
--			which does a  "-i, -d, -b, -s, -t" in sequence (takes about 3 hours)

 DIRECTORY
  BcdDefs: TYPE USING[Base, MTIndex],
  BcdOps: TYPE USING[BcdBase, MTHandle],
  ConvertUnsafe: TYPE USING[ToRope],
  CS: TYPE USING[EquivalentRS, Flat, Init, z],
  CWF: TYPE USING[SWF4, SetWriteProcedure],
  DFSubr: TYPE USING[StripLongName],
  Directory: TYPE USING[DeleteFile, Error],
  FileIO: TYPE USING[Open],
  FQ: TYPE USING[FileQuery, Result],
  IFSFile: TYPE USING[CantOpen, Close, Error, FileHandle, GetLength, UnableToLogin],
  IO: TYPE USING[card, CharProc, Close, CreateDribbleStream,
  		EndOf, Flush, GetSequence, Handle, int, 
  		Put, PutChar, PutF, PutFR, ResetUserAbort, rope, string, UserAborted],
  LeafSubr: TYPE USING[PrintLeafAccessFailure, PrintLeafProblem, RemoteMap, StopLeaf],
  Process: TYPE USING[Detach],
  Rope: TYPE USING[Cat, Fetch, Flatten, ROPE, Text],
  RStatsSupport: TYPE USING[BcdDep, BreakUp, ComputeFileList, DFDep, DFRec, GetLine,
  	LeafOpenWithCreate,  ProcessBcdAnalysis, ProcessDFList, SubString],
  Space: TYPE USING[Create, Handle, LongPointer, nullHandle, Unmap, virtualMemory],
  UnsafeSTP: TYPE USING[Error, FileInfo, GetFileInfo, Handle],
  STPSubr: TYPE USING [EnumerateForRetrieve, 
  	RetrieveProcType, StopSTP],
  Subr: TYPE USING [AbortMyself, EndsIn, errorflg,
  	FileError, MakeTTYProcs, Prefix, PrintGreeting, strcpy, SubrStop, TTYProcs],
  Time: TYPE USING[Current],
  TypeScript: TYPE USING[TS, Create, UserAbort, ResetUserAbort],
  UECP: TYPE USING[Argv, Parse],
  UserExec: TYPE USING[AskUser, CommandProc, RegisterCommand],
  ViewerClasses: TYPE USING[Viewer],
  ViewerEvents: TYPE USING[EventProc, EventRegistration, RegisterEventProc,
  	UnRegisterEventProc],
  ViewerIO: TYPE USING[CreateViewerStreams];

RStatsImpl: PROGRAM
IMPORTS ConvertUnsafe, CS, CWF, DFSubr, Directory, FileIO, FQ,
	IFSFile, IO, LeafSubr, Process, Rope, RStatsSupport,
	Space, STP: UnsafeSTP, STPSubr, Subr, Time, 
	TypeScript, UECP, UserExec, ViewerEvents, ViewerIO = {

Stat: TYPE = {nBcd, nBcdDefn, nBcdConfig, nBcdImpl, nBcdCodeBytes, 
	nBcdFileBytes, nSrc, nSrcConfig, nSrcMesa, nSrcImpl, nSrcDefn, nSrcLines, nSrcBytes, 
	nDF, nOtherFiles, nOtherBytes, nTotalFiles, nTotalBytes};

StatArray: TYPE = ARRAY Stat OF INT;

DFRec: TYPE = RStatsSupport.DFRec;
DFDep: TYPE = RStatsSupport.DFDep;
BcdDep: TYPE = RStatsSupport.BcdDep;

-- check ResetCtrs[] before adding more global state
Global: TYPE = RECORD[
	fileListName: Rope.Text ← NIL,		-- this is readonly
	xrefFileName: Rope.Text ← NIL,		-- this is readonly
	space: Space.Handle ← Space.nullHandle,
	stat: REF StatArray ← NIL,
	totalStat: REF StatArray ← NIL,
	dfrec: LIST OF REF DFRec ← NIL,	-- stores list of DF files
	lastHost: Rope.Text ← NIL,
	lastDirectory: Rope.Text ← NIL,
	lastShortname: Rope.Text ← NIL,
	lastTopDirectory: Rope.Text ← NIL,
	lastWholeDirectory: Rope.Text ← NIL,
	--
	-- viewer data
	typeScript: TypeScript.TS ← NIL,
	in: IO.Handle ← NIL,
	out: IO.Handle ← NIL,		-- dribblestream
	fout: IO.Handle ← NIL,		-- backing file
	tty: Subr.TTYProcs ← NIL
	];

-- MDS usage
g: REF Global ← NEW[Global ← [space: Space.Create[1, Space.virtualMemory],
	stat: NEW[StatArray], totalStat: NEW[StatArray], fileListName: "ReleaseStats.Files",
	xrefFileName: "XRef.DFXRef"]];
destroyEventRegistration: ViewerEvents.EventRegistration;
-- end of MDS usage

Choice: TYPE = {none, all, bcd, df, bcdAnalysis, bcdAnalysisSpecialFiles, init, src,
	detailSrc, total};

Main: UserExec.CommandProc = TRUSTED
	{
	argv: UECP.Argv;
	token, filename: Rope.Text;
	choice: Choice ← none;
	p: PROCESS;
	CS.Init[];	-- to make sure module is started
	argv ← UECP.Parse[event.commandLine];
	FOR i: CARDINAL IN [1 .. argv.argc) DO
		token ← CS.Flat[argv[i]];
		IF token.Fetch[0] = '- THEN 
			SELECT token.Fetch[1] FROM
			'a => choice ← all;
			'b => choice ← bcd;
			'd => choice ← df;
			'e => choice ← bcdAnalysis;
			'f => choice ← bcdAnalysisSpecialFiles;
			'i => choice ← init;
			'r => choice ← detailSrc;
			's => choice ← src;
			't => choice ← total;
			ENDCASE => NULL
		ELSE IF choice = init OR choice = df OR choice = all THEN filename ← token;
		ENDLOOP;
	IF choice = all THEN
		p ← FORK RunAll[filename]
	ELSE
		p ← FORK ChooseOne[choice, filename];
	Process.Detach[p];
	};

-- this procedure is FORKed
RunAll: PROC[filename: Rope.Text] = {
	ChooseOne[init, filename];
	ChooseOne[df, filename];
	ChooseOne[src, filename];
	ChooseOne[bcd, filename];
	ChooseOne[total, filename];
	};
	
-- this procedure is FORKed (or called by RunAll)
ChooseOne: PROC[choice: Choice, filename: Rope.Text] = {
	timeStarted: LONG CARDINAL ← Time.Current[];
		
	Cleanup: PROC = {
		TypeScript.ResetUserAbort[g.typeScript];
		PrintStat[g.out, g.stat, choice];
		PrintStat[g.out, g.totalStat, choice];
		ResetCtrs[];
		LeafSubr.StopLeaf[];
		STPSubr.StopSTP[];
		Subr.SubrStop[];
		timeStarted ← Time.Current[] - timeStarted;
		g.out.PutF["Elapsed time for RStats: %r\n", IO.card[timeStarted]];
		g.out.Put[IO.string["-----------------------\n\n"L]];
		g.fout.Flush[];	-- since g.out.Flush may not flush it (bugs)
		g.out.Flush[];	-- cannot g.out.Close, since it destroys the viewer
		};
	
	{
	ENABLE {
		UNWIND => Cleanup[];
		 STP.Error => {
			lcode: LONG CARDINAL ← LOOPHOLE[code, CARDINAL];
			g.out.Put[IO.string["FTP Error. "L]];
			IF error ~= NIL THEN 
				g.out.PutF["message: %s, code %d in Stp.Mesa\n", IO.string[error],
					IO.card[lcode]];
			Subr.errorflg ← TRUE;
			GOTO leave;
			};
		IFSFile.Error, IFSFile.UnableToLogin => {
			LeafSubr.PrintLeafProblem[reason];
			GOTO leave;
			};
		IFSFile.CantOpen => {
			LeafSubr.PrintLeafAccessFailure[reason];
			GOTO leave;
			};
		Subr.AbortMyself, ABORTED, IO.UserAborted => {
			g.out.ResetUserAbort[];
			g.out.Put[IO.string["RStats Aborted.\n"L]];
			GOTO leave;
			};
		};

	Subr.PrintGreeting["RStats"L];
	ResetCtrs[];
	IF choice = init THEN 
		RStatsSupport.ComputeFileList[LOOPHOLE[filename], g.fileListName, g.typeScript, g.out, g.tty]
	ELSE AnalyzeAll[choice, filename, g.typeScript, g.out, g.tty];
	EXITS
	leave => NULL;
	};
	Cleanup[];
	};
	
AnalyzeAll: PROC[choice: Choice, fileName: Rope.Text, typeScript: TypeScript.TS, out: IO.Handle, 
	tty: Subr.TTYProcs] =
	{
	file: IO.Handle ← FileIO.Open[g.fileListName, read];
	sfn: Rope.Text;
	version: CARDINAL;
	createtime: LONG CARDINAL;
	host: STRING ← [100];
	directory: STRING ← [100];
	shortname: STRING ← [100];
	out.PutF["Reading list of files from %s\n", IO.rope[g.fileListName]];
	WHILE NOT file.EndOf[] DO
		[sfn, createtime] ← RStatsSupport.BreakUp[file.GetSequence[LinePlusCR]];
		version ← DFSubr.StripLongName[LOOPHOLE[sfn], host, directory, shortname];
		IF choice = src
		AND (Subr.EndsIn[shortname, ".Mesa"L] OR Subr.EndsIn[shortname, ".Config"L]) THEN {
			IF SkipFile[host, directory, shortname] THEN LOOP;
			CheckForTopDirSwitch[directory, out, choice];
			AnalyzeSrc[host, directory, shortname, version, createtime, out, tty]
			}
		ELSE IF choice = detailSrc
		AND (Subr.EndsIn[shortname, ".Mesa"L] OR Subr.EndsIn[shortname, ".Config"L]) THEN {
			IF SkipFile[host, directory, shortname] THEN LOOP;
			CheckForWholeDirSwitch[directory, out, choice];
			AnalyzeSrc[host, directory, shortname, version, createtime, out, tty]
			}
		ELSE IF choice = bcd AND Subr.EndsIn[shortname, ".Bcd"L] THEN {
			IF SkipFile[host, directory, shortname] THEN LOOP;
			CheckForTopDirSwitch[directory, out, choice];
			AnalyzeBcd[host, directory, shortname, version, createtime, out, tty]
			}
		ELSE IF (choice = df OR choice = bcdAnalysis OR choice = bcdAnalysisSpecialFiles) 
		  AND Subr.EndsIn[shortname, ".DF"L] THEN
			g.dfrec ← CONS[CS.z.NEW[DFRec ← [host: ConvertUnsafe.ToRope[host],
				directory: ConvertUnsafe.ToRope[directory], 
				shortname: ConvertUnsafe.ToRope[shortname], 
				version: version, dep: NIL, refBy: NIL]], g.dfrec]
		ELSE IF choice = total THEN {
			IF SkipFile[host, directory, shortname] THEN LOOP;
			CheckForTopDirSwitch[directory, out, choice];
			AnalyzeTotals[LOOPHOLE[sfn], host, directory, shortname, version, createtime, out, tty]
			};
		IF TypeScript.UserAbort[typeScript] THEN SIGNAL Subr.AbortMyself;
		out.Flush[];
		ENDLOOP;
	file.Close[];
	out.Flush[];
	IF choice = df THEN 
		g.stat[nDF] ← RStatsSupport.ProcessDFList[fileName, typeScript, out, tty,
			g.dfrec, g.xrefFileName];
	IF choice = bcdAnalysis OR choice = bcdAnalysisSpecialFiles THEN 
		RStatsSupport.ProcessBcdAnalysis[typeScript, out, tty,
			g.dfrec, choice = bcdAnalysisSpecialFiles];
	};
	
LinePlusCR: IO.CharProc = TRUSTED {
	RETURN[quit: char = '\n, include: TRUE];
	};
	
SkipFile: PROC[host, directory, shortname: LONG STRING] RETURNS[skipIt: BOOL] =
	{
	IF g.lastHost ~= NIL THEN 
		skipIt ← CS.EquivalentRS[g.lastShortname, shortname]
			AND CS.EquivalentRS[g.lastDirectory, directory]
			AND CS.EquivalentRS[g.lastHost, host]
	ELSE skipIt ← FALSE;
	IF NOT skipIt THEN {
		g.lastShortname ← ConvertUnsafe.ToRope[shortname];
		IF NOT CS.EquivalentRS[g.lastDirectory, directory] THEN
			g.lastDirectory ← ConvertUnsafe.ToRope[directory];
		IF NOT CS.EquivalentRS[g.lastHost, host] THEN
			g.lastHost ← ConvertUnsafe.ToRope[host];
		};
	};
	
CheckForWholeDirSwitch: PROC[directory: LONG STRING, out: IO.Handle, choice: Choice] = 
	{
	IF NOT CS.EquivalentRS[g.lastWholeDirectory, directory] THEN {
		IF g.lastWholeDirectory ~= NIL THEN
			PrintStat[out, g.stat, choice];
		g.lastWholeDirectory ← ConvertUnsafe.ToRope[directory];
		};
	};
	
CheckForTopDirSwitch: PROC[directory: LONG STRING, out: IO.Handle, choice: Choice] = 
	{
	len: CARDINAL ← directory.length;
	dir: STRING ← [100];
	FOR i: CARDINAL IN [0 .. len) DO
		IF directory[i] = '> THEN {
			len ← i;
			EXIT;
			};
		ENDLOOP;
	Subr.strcpy[dir, directory];
	dir.length ← len;
	IF NOT CS.EquivalentRS[g.lastTopDirectory, dir] THEN {
		IF g.lastTopDirectory ~= NIL THEN
			PrintStat[out, g.stat, choice];
		g.lastTopDirectory ← ConvertUnsafe.ToRope[dir];
		};
	};
	
-- total analysis
AnalyzeTotals: PROC[filename, host, directory, shortname: LONG STRING, version: CARDINAL,
	createtime: LONG CARDINAL, out: IO.Handle, tty: Subr.TTYProcs] = 
	{
	fres: FQ.Result;
	targetFileName: STRING ← [125];
	vnum: CARDINAL;
	byteLength: LONG CARDINAL;
	-- out.PutF["<%s>%s", IO.string[directory], IO.string[shortname]];
	[fres: fres, remoteVersion: vnum, remoteByteLength: byteLength] ←
		FQ.FileQuery[host, directory, shortname, version, createtime, FALSE, tty, targetFileName];
	SELECT fres FROM
	foundCorrectVersion => {
		IF Subr.EndsIn[shortname, ".Mesa"L] OR Subr.EndsIn[shortname, ".Config"L] THEN {
			g.stat[nSrc] ← g.stat[nSrc] + 1;
			g.stat[nSrcBytes] ← g.stat[nSrcBytes] + byteLength
			}
		ELSE IF Subr.EndsIn[shortname, ".Bcd"L] THEN {
			g.stat[nBcd] ← g.stat[nBcd] + 1;
			g.stat[nBcdFileBytes] ← g.stat[nBcdFileBytes] + byteLength
			}
		ELSE  {
			g.stat[nOtherFiles] ← g.stat[nOtherFiles] + 1;
			g.stat[nOtherBytes] ← g.stat[nOtherBytes] + byteLength;
			};
		g.stat[nTotalFiles] ← g.stat[nTotalFiles]  + 1;
		g.stat[nTotalBytes] ← g.stat[nTotalBytes] + byteLength;
		};
	foundWrongVersion, notFound => 
		out.PutF["  Error - cannot open %s.", IO.string[targetFileName]];
	ENDCASE => ERROR;
	-- out.PutChar['\n];
	};
	
-- source procedures

AnalyzeSrc: PROC[host, directory, shortname: LONG STRING, version: CARDINAL,
	createtime: LONG CARDINAL, out: IO.Handle, tty: Subr.TTYProcs] = 
	{
	line: STRING ← [2000];
	
	-- this is only called once
	OneFile: STPSubr.RetrieveProcType = {
		info: STP.FileInfo ← STP.GetFileInfo[stp];
		nImpl, nDefs: INT ← 0;
		nLines: INT ← 0;
		checkForType: BOOL ← TRUE;
		IF Subr.EndsIn[shortname, ".Config"L] THEN g.stat[nSrcConfig] ← g.stat[nSrcConfig] + 1
		ELSE g.stat[nSrcMesa] ← g.stat[nSrcMesa] + 1;
		-- out.PutF["<%s>%s", IO.string[directory], IO.string[shortname]];
		WHILE RStatsSupport.GetLine[remoteStream, line, out] DO
			nLines ← nLines + 1;
			IF checkForType THEN {
				IF RStatsSupport.SubString[line, "PROGRAM"L] THEN {
					checkForType ← FALSE;
					nImpl ← nImpl + 1;
					};
				IF RStatsSupport.SubString[line, "DEFINITIONS"L] THEN {
					checkForType ← FALSE;
					nDefs ← nDefs + 1;
					};
				IF RStatsSupport.SubString[line, "MONITOR"L] THEN {
					checkForType ← FALSE;
					nImpl ← nImpl + 1;
					};
				};
			IF line[line.length - 1] ~= '\n THEN EXIT;	-- eof
			ENDLOOP;
		g.stat[nSrc] ← g.stat[nSrc] + 1;
		g.stat[nSrcBytes] ← g.stat[nSrcBytes] + info.size;
		IF nImpl > 0 AND nDefs > 0 THEN 
			out.Put[IO.string["Warning - counted twice as impl and defs.\n"L]];
		IF nImpl > 0 THEN g.stat[nSrcImpl] ← g.stat[nSrcImpl] + 1;
		IF nDefs > 0 THEN g.stat[nSrcDefn] ← g.stat[nSrcDefn] + 1;
		g.stat[nSrcLines] ← g.stat[nSrcLines] + nLines;
		-- out.PutChar['\n];
		};
		
	{
	fres: FQ.Result;
	targetFileName: STRING ← [100];
	vstring: STRING ← IF Subr.Prefix[host, "maxc"L] THEN ";"L ELSE "!"L;
	[fres: fres, remoteVersion: version] ← FQ.FileQuery[host, directory, shortname, version,
				createtime, FALSE, tty, targetFileName];
	SELECT fres FROM
	foundCorrectVersion => {
		sfn: STRING ← [100];
		CWF.SWF4[sfn, "<%s>%s%s%u"L, directory, shortname, vstring, @version];
		STPSubr.EnumerateForRetrieve[host, sfn, OneFile, tty, TRUE];
		};
	foundWrongVersion, notFound => 
		out.PutF["%s not found.\n", IO.string[targetFileName]];
	ENDCASE => ERROR;
	}};
	

-- BCD procedures

AnalyzeBcd: PROC[host, directory, shortname: LONG STRING, version: CARDINAL,
	createtime: LONG CARDINAL, out: IO.Handle, tty: Subr.TTYProcs] = 
	{{
	bcd: BcdOps.BcdBase;
	fh: IFSFile.FileHandle;
	sfn: Rope.ROPE;
	mth: BcdOps.MTHandle;
	g.stat[nBcd] ← g.stat[nBcd] + 1;
	sfn ← IO.PutFR["<%s>%s", IO.string[directory], IO.string[shortname]];
	IF version > 0 THEN sfn ← Rope.Cat[sfn, IO.PutFR["!%d", IO.card[version]]];
	-- out.PutF["%s ", IO.rope[sfn]];
	fh ← RStatsSupport.LeafOpenWithCreate[host, directory, shortname, version, createtime, out, tty
		! Subr.FileError => {
			out.PutF["Error - cannot open %s\n", IO.rope[sfn]];
			GOTO leave;
			}];
	IF fh = NIL THEN GOTO leave;
	LeafSubr.RemoteMap[g.space, fh, 0];
	bcd ← Space.LongPointer[g.space];
	IF bcd.definitions THEN g.stat[nBcdDefn] ← g.stat[nBcdDefn] + 1
	ELSE IF bcd.nConfigs > 0 THEN g.stat[nBcdConfig] ← g.stat[nBcdConfig] + 1
	ELSE {
		mth ← @LOOPHOLE[bcd + bcd.mtOffset, BcdDefs.Base][FIRST[BcdDefs.MTIndex]];
		g.stat[nBcdCodeBytes] ← g.stat[nBcdCodeBytes] + mth.code.length;
		g.stat[nBcdImpl] ← g.stat[nBcdImpl] + 1;
		};
	Space.Unmap[g.space];
	g.stat[nBcdFileBytes] ← g.stat[nBcdFileBytes] + IFSFile.GetLength[fh];
	IFSFile.Close[fh];
	EXITS
	leave => NULL;
	};
	-- out.PutChar['\n];
	};
	
-- support procedures

SetUpTypeScriptStreams: PROC =
	{
	vout: IO.Handle;
	g.typeScript ← TypeScript.Create[info: [name: "RStats Log Viewer", iconic: FALSE]];
	destroyEventRegistration ← ViewerEvents.RegisterEventProc[DestroyProc, destroy];
   g.fout ← FileIO.Open["RStats.Log", overwrite];
	[g.in, vout] ← ViewerIO.CreateViewerStreams[viewer: g.typeScript, name: "RStats Log Viewer",
		editedStream: FALSE];
	g.out ← IO.CreateDribbleStream[vout, g.fout];
	g.tty ← Subr.MakeTTYProcs[g.in, g.out, g.typeScript, MyConfirm];
	[] ← CWF.SetWriteProcedure[ViewerTTYProc];
	};
	
-- cannot print anything in this, monitor is locked
DestroyProc: ViewerEvents.EventProc = TRUSTED
	{
	IF g = NIL OR event ~= destroy OR viewer ~= g.typeScript THEN RETURN;
   g.out.Flush[];
   g.fout.Close[];	-- needed because g.out.Close[] does not close the backing file
	-- g.out.Close[]; can't close, since this will destroy the viewer
	g↑ ← [];	-- erase all pointers
	viewer.newVersion ← FALSE;		-- so it won't ask to confirm new edits
	ViewerEvents.UnRegisterEventProc[destroyEventRegistration, destroy];
   };
	
ViewerTTYProc: PROC[ch: CHAR] = {
	g.out.PutChar[ch];
	};
	
MyConfirm: PROC[in, out: IO.Handle, data: REF ANY, msg: Rope.ROPE, dch: CHAR]
	RETURNS[CHAR] = {
	value: ATOM;
	value ← UserExec.AskUser[msg: msg, viewer: NARROW[data], 
		keyList: LIST[$Yes, $No, $All, $Local, $Quit]];  -- order is important
	SELECT value FROM
	$All => RETURN['a];
	$Local => RETURN['l];
	$No => RETURN['n];
	$Quit => RETURN['q];
	$Yes => RETURN['y];
	ENDCASE => ERROR;
	};


	
PrintStat: PROC[out: IO.Handle, st: REF StatArray, choice: Choice] = 
	{
	out.PutChar['\n];
	SELECT choice FROM
	bcd =>  {
		out.PutF["Bcd: %d files, %d defs, %d impls, %d configs,\n\t",
			IO.int[st[nBcd]], IO.int[st[nBcdDefn]], IO.int[st[nBcdImpl]], IO.int[st[nBcdConfig]]];
		out.PutF["%d code bytes, %d file bytes\n", 
			IO.int[st[nBcdCodeBytes]], IO.int[st[nBcdFileBytes]]];
		};
	df => {
		out.PutF["DF: %d dffiles\n", IO.int[st[nDF]]];
		};
	src, detailSrc => {
		out.PutF["Source: %d files, %d mesa, %d defs, %d impls, %d config,\n\t",
			IO.int[st[nSrc]], IO.int[st[nSrcMesa]], IO.int[st[nSrcDefn]],IO.int[st[nSrcImpl]], 
			IO.int[st[nSrcConfig]]];
		out.PutF["%d lines, %d file bytes\n", IO.int[st[nSrcLines]], IO.int[st[nSrcBytes]]];
		};
	total => {
		out.PutF["Total: %d files, %d total bytes, %d bcd files, %d bcd file bytes,\n\t",
			IO.int[st[nTotalFiles]], IO.int[st[nTotalBytes]], IO.int[st[nBcd]], IO.int[st[nBcdFileBytes]]];
		out.PutF["%d src files, %d src file bytes, %d other files, %d other bytes\n",
			IO.int[st[nSrc]], IO.int[st[nSrcBytes]], IO.int[st[nOtherFiles]], IO.int[st[nOtherBytes]]];
		};
	init, bcdAnalysis => NULL;
	ENDCASE => ERROR;
	FOR s: Stat IN Stat DO
		g.totalStat[s] ← g.totalStat[s] + g.stat[s];
		g.stat[s] ← 0;
		ENDLOOP;
	out.PutChar['\n];
	};
	
ResetCtrs: PROC =
	{
	FOR s: Stat IN Stat DO
		g.totalStat[s] ← g.stat[s] ← 0;
		ENDLOOP;
	g.dfrec ← NIL;
	g.lastHost ← g.lastDirectory ← g.lastShortname ← g.lastTopDirectory ← NIL;
	};
	
Init: PROC =
	{
	Directory.DeleteFile[fileName: "RStats.Log"L ! Directory.Error => CONTINUE];
	SetUpTypeScriptStreams[];	-- must be first
	UserExec.RegisterCommand["RStats.~", Main];
	};

Init[];
}.